Skip to content
Native Animation
Grid List Layout Morph animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add grid-list-layout-morph

Grid List Layout Morph

A media layout that morphs between grid and list while keeping its scroll anchor, with configurable items, labels, palette, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A media layout that morphs between grid and list while keeping its scroll anchor, with configurable items, labels, palette, and fonts.

How it works

  1. The host width and measured text heights produce stable grid and list rectangles for every item ID.
  2. A single UI-thread progress value interpolates each photo's position, size, crop, and corner radius.
  3. The scroll offset is compensated from the visible anchor rectangle, so changing layout does not jump to the top.
  4. Grid labels fade out before list metadata fades in; the selected image and its check badge travel together.
  5. Failed images keep their reserved cell and expose a retry action. Data changes cancel the current flight and rebuild by ID.

Install

npx expo install expo-image react-native-reanimated react-native-worklets

No native configuration, Skia, blur, or demo assets are required by the component. Reanimated must already be configured by the host app.

Usage

import { GridListLayoutMorph } from './grid-list-layout-morph';

<GridListLayoutMorph
  items={[{ id: 'one', title: 'Quiet object', source: require('./one.jpg'), accessibilityLabel: 'Quiet object' }]}
  labels={{ grid: 'Grid', list: 'List', select: 'Selected items', empty: 'No items', loadError: 'Image unavailable', retry: 'Retry' }}
/>;
<GridListLayoutMorph
  ref={libraryRef}
  items={items}
  defaultSelectedIds={['basalt-bowl']}
  columns={2}
  colorScheme="dark"
  motion={{ durationScale: 0.85, glowOpacity: 0.2 }}
  fontFamily="YourBodyFont"
  titleFontFamily="YourTitleFont"
  monoFontFamily="YourMonoFont"
  onLayoutChange={(next) => trackLayout(next)}
  testID="library"
/>;

libraryRef.current?.scrollToId('basalt-bowl');
libraryRef.current?.setLayout('list');

Props

PropTypeDefaultDescription
itemsReadonlyArray<GridListItem>requiredStable media IDs, titles, optional subtitles, sources, and accessible labels.
layout'grid' | 'list'uncontrolledControlled layout mode.
defaultLayout'grid' | 'list''grid'Initial uncontrolled mode.
selectedIdsReadonlyArray<string>uncontrolledControlled selection.
defaultSelectedIdsReadonlyArray<string>[]Initial uncontrolled selection.
columns2 | 32Grid columns; hosts narrower than 330 points use one.
labelsGridListLabelsneutral English labelsGrid/list controls, empty/error/retry copy, and optional announcements.
palettePartial<Palette>theme paletteSemantic color overrides.
colorScheme'light' | 'dark' | 'auto''auto'Paper, Night, or system palette.
motion{ durationScale?: number; glowOpacity?: number }1, 0.25Transition duration and selection glow.
fontFamily, titleFontFamily, monoFontFamilystringsystemHost typography.
onLayoutChange, onSelectionChange, onItemPresscallbacks—Discrete interaction callbacks.
style, testID, accessibilityLabelstandard host props—Root styling and stable test/accessibility identity.

Compatibility

Designed for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, and Expo Image 58.

The component fills its host and does not add safe-area insets. It adapts to compact containers, landscape, and tablet widths. Supply local images at least 3× their largest painted size. Duplicate or empty IDs are ignored; unknown selection IDs and imperative scroll targets are ignored. Invalid motion values use safe defaults.

Accessibility

Each item is one checkbox action with a stable accessible label and checked state. A failed image becomes a retry button without changing the reserved layout. Grid/list controls expose selected state, and optional announcement formatters are called once after committed changes. Reduced motion swaps the flight for an immediate layout update. Font scaling remains enabled; keyboard activation follows React Native Web control semantics.

Why this one

The layout is the animation: the same editorial image keeps its identity while its geometry changes. Unlike a simple grid/list toggle, the component preserves selection, scroll context, image loading state, and item order so it can be dropped into a real media library.