Skip to content

Product Card

Display and motionNativeMIT
View in Mobile Storybook

E-commerce: image + title + price + rating + add-to-cart.

bun add @otfdashkit/ui-native

Code

import { useState } from 'react'
import {
  ProductCard,
  XStack,
  YStack,
  SizableText,
} from '@otfdashkit/ui-native'
import { ShowcaseFrame, Section } from '../../components/ShowcaseFrame'
import { PRODUCTS } from '../../lib/fixtures'

// Real, self-hosted product shots (R2) keyed by id.
const byId = (id: string) => PRODUCTS.find((p) => p.id === id)!.image
const SHOE = byId('sneaker')
const JACKET = byId('jacket')
const BAG = byId('backpack')

export default function ProductCardShowcase() {
  const [cart, setCart] = useState<string[]>([])

  return (
    <ShowcaseFrame
      title="Product Card"
      description="E-commerce listing card. Image-led, with rating, price, optional sale badge, and an in-card add-to-cart action."
      docPath="packages/ui-native/src/patterns/ProductCard.tsx"
    >
      <Section title="Vertical — default" hint={`Cart: ${cart.length}`}>
        <XStack gap="$3" flexWrap="wrap">
          <YStack flex={1} minWidth={220}>
            <ProductCard
              image={SHOE}
              title="Trail Runner GTX"
              price="$148"
              originalPrice="$180"
              rating={4.5}
              reviewCount={128}
              badge="-18%"
              onAddToCart={() => setCart((c) => [...c, 'shoe'])}
              onPress={() => {}}
            />
          </YStack>
          <YStack flex={1} minWidth={220}>
            <ProductCard
              image={JACKET}
              title="Featherweight Shell"
              price="$220"
              rating={4}
              reviewCount={64}
              onAddToCart={() => setCart((c) => [...c, 'jacket'])}
              onPress={() => {}}
            />
          </YStack>
        </XStack>
      </Section>

      <Section title="Horizontal — list-row variant">
        <YStack gap="$2">
          <ProductCard
            variant="horizontal"
            image={BAG}
            title="Daypack 22L — water-resistant, packable"
            price="$89"
            rating={4.8}
            reviewCount={312}
            onAddToCart={() => setCart((c) => [...c, 'bag'])}
            onPress={() => {}}
          />
          <ProductCard
            variant="horizontal"
            image={SHOE}
            title="Court Sneaker — leather upper, vulcanized sole"
            price="$120"
            originalPrice="$150"
            rating={4.2}
            reviewCount={45}
            badge="SALE"
            onAddToCart={() => setCart((c) => [...c, 'court'])}
          />
        </YStack>
      </Section>

      <Section title="Minimal — no rating, no cart">
        <XStack gap="$3">
          <YStack flex={1}>
            <ProductCard image={JACKET} title="Linen Overshirt" price="$98" />
          </YStack>
          <YStack flex={1}>
            <ProductCard image={BAG} title="Canvas Tote" price="$42" />
          </YStack>
        </XStack>
      </Section>

      <SizableText size="$2" color="$color11">
        Items added: {cart.length === 0 ? 'none' : cart.join(', ')}
      </SizableText>
    </ShowcaseFrame>
  )
}

Free with the SDK. This pattern ships in @otfdashkit/ui-native under the MIT license.