Skip to content
Native Animation
Shop Grid Skeleton animation preview

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

npx nativeanimation add shop-grid-skeleton
Skeleton

Shop Grid Skeleton

A shopping grid with moving skeleton bands and product-to-cart flights, with configurable products, labels, colors, 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 shopping grid with moving skeleton bands and product-to-cart flights, with configurable products, labels, colors, and fonts.

Why this one

The image placeholders carry soft vertical bands rather than a diagonal shimmer. Search fades the existing cards out in reverse order; results settle in alternating columns, with image focus and a drawn discount strike.

How it works

  1. A shared UI-thread clock moves an oversized horizontal gradient behind each image crop. Text placeholders share a separate opacity pulse.
  2. Search and chip selection interrupt a single reveal progress. Its stagger unwinds the last card first, then rebuilds the grid at 70 ms intervals.
  3. An actual TextInput drives search. Local filtering matches titles and tags; loading lets a host control remote requests.
  4. Card presses open products. Long presses and the plus button measure the image and animate a copy along a short arc to the cart.
  5. The tab underline and chip fill use interruptible springs. Cart counts enter through a clipped rolling window.

Install

npx expo install @expo/vector-icons expo-image expo-linear-gradient react-native-reanimated react-native-worklets

Configure Reanimated as required by your Expo version. Icons: Feather from @expo/vector-icons, the Lucide predecessor.

Usage

import { useRef } from 'react';
import { ShopGridSkeleton, type ShopGridSkeletonRef } from './shop-grid-skeleton';

const shop = useRef<ShopGridSkeletonRef>(null);

<ShopGridSkeleton
  ref={shop}
  products={products}
  chips={[
    { id: 'all', label: 'All days', count: products.length },
    { id: 'today', label: 'Today', count: 4, tag: 'today' },
  ]}
  onSearch={query => searchProducts(query)}
  onAddToCart={id => cart.add(id)}
  onOpenProduct={id => openProduct(id)}
/>;

shop.current?.setQuery('wool');
shop.current?.selectChip(1);
shop.current?.addToCart('product-id');
shop.current?.reload();

For remote results, set loading to true in onSearch, then replace products and set it false. The host should discard stale requests. Omit loading to use simulated local loading. A controlled true state remains a skeleton until the host clears it.

<ShopGridSkeleton
  products={products}
  loading={loading}
  loadDuration={1100}
  fontFamily="YourInterfaceFont"
  numberFontFamily="YourNumberFont"
  colors={{
    background: '#111114', surface: '#1c1c21', ink: '#faf8fc', muted: '#aaa6b0',
    skeleton: '#25242b', stripe: '#34333c', border: '#3e3b45',
    accent: '#ee9c7e', onAccent: '#171116',
  }}
  labels={{ placeholder: 'Find your next favorite', tabs: ['Shop', 'Top', 'Videos', 'Sellers', 'Live'] }}
  formatPrice={price => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price)}
/>

Props

PropTypeDefaultDescription
productsShopProduct[]Requiredid, title, local image, price, optional oldPrice, rating, sold, and searchable tags.
chipsShopChip[][]id, label, count, and optional tag filter.
labelsPartial<ShopLabels>Generic EnglishSearch, placeholder, tabs, back, camera, cart, more, add, open, empty, loading, sold.
colorsPartial<ShopColors>Light neutral paletteBackground, surface, ink, muted, skeleton, stripe, border, accent, onAccent. Pass all roles for dark themes.
loadingbooleanUncontrolledExternal network loading state.
initialCartCountnumber0Starting cart badge value.
initialQuerystring''Initial search text.
loadDurationnumber950Delay after the latest local search edit; initial load is 1400 ms.
fontFamilystringSystemUI font loaded by the host.
numberFontFamilystringSystemPrice and count font.
formatPrice(number) => string$0.00Currency formatter.
onSearch(query) => void—Receives text edits, including imperative edits.
onAddToCart(id) => void—Receives each add immediately.
onOpenProduct(id) => void—Card tap handler.
onHeaderAction(action) => void—Back, camera, cart, or more.
onTabChange(index) => void—Tab selection; each tab also reloads results.
onInteraction() => void—Lets a demo stop its autoplay on touch or input focus.
styleStyleProp<ViewStyle>—Root container.
testIDstringshop-grid-skeletonStable control prefix.

The ref exposes setQuery(text), selectChip(index), addToCart(id), and reload(). The demo accepts autoplay; its clock calls these same methods and stops on interaction. Header callbacks and tab content routing belong to the host.

Compatibility

Expo SDK 58, React Native 0.88, Reanimated 4.7. iOS, Android, and web; compatible with Expo Go. No Skia, extra native module, or browser-only import is required. Use local images at least three times the painted size. Cards are capped at 230 pt and centered on wide layouts. The grid scrolls vertically with hidden indicators; filter chips scroll horizontally. A 56 pt top reserve leaves room for a host viewer close button.

Accessibility

Controls expose roles, labels, selected states, and stable test IDs. Tap targets are at least 48 pt. Long press has an equivalent plus button. Reduced motion removes band drift, card travel, scale focus, and cart flight in favor of 220 ms fades. All visible structural copy and currency formatting can be replaced through props.