Skip to content
Native Animation
Scrub Magnify Dock animation preview

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

npx nativeanimation add scrub-magnify-dock
Glass

Scrub Magnify Dock

An action dock that magnifies drag previews and executes on release, with configurable actions, 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

An action dock that magnifies drag previews and executes on release, with configurable actions, labels, palette, and fonts.

Why this one

A two-cell proximity kernel enlarges the nearest icon to 1.32× and its neighbor to about 1.12×, lifting the candidate by 12 pt. A blurred violet lens follows the finger and contracts over 280 ms on execution. Leaving the vertical band cancels permanently for that gesture. Direct taps, keyboard activation, and screen reader actions remain available. Extra actions wrap instead of shrinking touch targets.

How it works

  1. The dock measures its own width with onLayout; up to five cells fit each row, each at least 48×64 pt.
  2. A Gesture Handler pan writes one continuous scrubPosition. Icon scale, lift, and lens position derive on the UI thread. Pressure uses an independent spring.
  3. Crossing a candidate boundary sends one discrete onPreview change to JS. Previewing never calls onAction.
  4. Release commits the enabled candidate once. Moving outside the horizontal bounds or more than 64 pt vertically from the starting row center cancels until the next gesture.
  5. A tap competes with the pan through Gesture.Exclusive. Springs have explicit mass; a separate 280 ms clock contracts the lens.
  6. BlurTargetView supplies real content behind the glass, and the BlurView has its own clipping radius. All icons and the blurred lens share one Skia canvas.

Install

npx expo install @shopify/react-native-skia expo-blur expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets

Use a GestureHandlerRootView at the app root. Follow the host Expo SDK's Reanimated/worklets setup. On web, initialize CanvasKit before mounting this component (the demo requires the catalog's Skia loader). No fonts or assets are fetched remotely. Icons: Lucide (ISC).

Usage

import ScrubMagnifyDock from './scrub-magnify-dock';

<ScrubMagnifyDock
  actions={[
    { id: 'search', label: 'Search', icon: 'search' },
    { id: 'notes', label: 'Notes', icon: 'sticky-note' },
  ]}
  onAction={id => openAction(id)}
  cancelLabel="Canceled. No action taken."
  accessibilityLabel="Workspace actions"
  labels={{
    idle: 'Slide to explore. Release to choose.',
    empty: 'No actions available.',
    unavailable: 'This action is unavailable.',
    executed: 'Opened',
    hint: 'Activate or slide and release to choose.',
  }}
/>

Add selectedId={selected} and update it inside onAction for controlled selection. Add magnification={1.4}, colorScheme="light", a complete palette, or fontFamily to customize. A controlled selection change plays the same lens confirmation without invoking onAction.

const dock = useRef<ScrubMagnifyDockRef>(null);
// Request previews without executing an action:
dock.current?.preview('notes');
dock.current?.clearPreview();
dock.current?.reset();

Props

PropTypeDefaultPurpose
actionsreadonly DockAction[]required{id,label,icon,disabled?} entries
onAction(id: string) => voidrequiredOne callback for valid user activation
onPreview(id: string | null) => void—Candidate changes, including clearing
selectedIdstringuncontrolledControlled selection takes precedence
magnificationnumber1.32Finite values clamped to 1–1.5
cancelLabelstringrequiredVisible and spoken cancellation message
labelsobjectrequiredidle, empty, unavailable, executed, hint
colorSchemelight / dark / autoautoComplete appearance
paletteDockPaletteNight or Paperbackground, surface, text, muted, border, accent, success, error
fontFamilystringsystemStatus and preview typography
titleFontFamily / monoFontFamilystringsystemReserved integration font slots; this dock has no heading or numeric text
accessibilityLabelstringrequiredGroup description
onInteractionStart() => void—Stop host autoplay on real input
styleStyleProp<ViewStyle>—Root layout override
testIDstringscrub-magnify-dockRoot; buttons append -action-ID

The icon keys shipped in icons.ts are search, sticky-note, calendar-days, folder, settings, cloud, check, and arrow-up-right. Unknown icon keys use search. Empty lists show labels.empty. Empty IDs and duplicate IDs after the first are omitted. Unknown/disabled preview IDs clear the preview and announce labels.unavailable. Unknown selected IDs highlight nothing. Nonfinite magnification uses 1.32. Disabled actions never execute. reset() clears transient state and uncontrolled selection; controlled selection remains owned by the host. No ref method executes onAction.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, and Skia 2.14.0. Uses installed Expo Go modules; no additional native dependency is introduced.

The autoplay confirmation is scheduled at 1.8 seconds. The lens is a blurred Skia circle beneath the icons, never a rectangular overlay across text.

The demo uses intrinsic sizing with nonshrinking caption, icon, and result regions. The caption can grow for larger text without covering the project card. The scroll host handles short viewports, including 320×568; 320×694 and 390×844 use the same flow. The upper-right close area, 1.8-second autoplay confirmation, and blurred lens remain unchanged.

Icons remain 22 pt and action cells remain at least 48×64 pt. The demo measures its host height: at 660 pt and above it uses a plain View; shorter hosts use one outer ScrollView with its indicator hidden. The dock is positioned before the secondary feedback, theme controls, and footer so it is visible on entry at 320×568. The upper-right close area stays clear, text remains in flow, and the 1.8-second autoplay confirmation and blurred lens are preserved.

No error was reported in this piece.

Accessibility

The root uses flex: 1; it applies no safe area. For intrinsic height inside a scrolling host, use style={{flex: 0, flexBasis: 'auto', flexShrink: 0}}; flex: 0 alone collapses the dock's layout space on web. The caption reserves at least two lines above the shell with a 16 pt gap, and neither the caption nor the icon row shrinks. The demo places its result in a separate, stable single-line row 4 pt below the shell. At 300 pt, all five actions fit in one row. Larger lists wrap; the host should scroll very tall lists. Labels stay in flow and support font scaling. Each action exposes a button role, selected/disabled state, an activation action, and a stable test ID. Web uses Enter/Space and native readers use accessible activation. Cancellation and execution are announced.

Reduced motion removes lift, scaling, and the traveling lens; status changes fade in over 200 ms. No idle animation runs. The demo alone owns autoplay: preview at 600–1500 ms, host action result at 1800 ms, reset at 3800 ms, repeat at 5000 ms. The host demonstrates its controlled action result without fabricating a user callback; real input cancels timers, and unmount cleans them up.