
Huila, Colombia
Stone fruit and brown sugar · washed
Huila, Colombia
In your box: nothing yet
'use client';
import { SwipeDeck } from '@brand-studio/ui';
import { useState } from 'react';
import { beans, cup, pour } from '@/demos/assets';
const lots = [
{ id: 'huila', title: 'Huila, Colombia', asset: cup, body: <p>Stone fruit and brown sugar · washed</p> },
{ id: 'sidama', title: 'Sidama, Ethiopia', asset: pour, body: <p>Jasmine and bergamot · natural</p> },
{ id: 'kirinyaga', title: 'Kirinyaga, Kenya', asset: beans, body: <p>Blackcurrant and cola · washed</p> },
];
export default function SwipeDeckDemo() {
const [box, setBox] = useState<string[]>([]);
return (
<div style={{ width: '100%', maxWidth: 420 }}>
<SwipeDeck label="Build this month’s box" cards={lots} leftLabel="Skip" rightLabel="Add to box"
onSwipe={(card, direction) => { if (direction === 'right') setBox(current => current.includes(card.title) ? current : [...current, card.title]); }} />
<p style={{ margin: '16px 0 0', textAlign: 'center', fontSize: 14, color: 'var(--bs-muted)' }}>In your box: {box.length ? box.join(', ') : 'nothing yet'}</p>
</div>
);
}Installation
pnpm add @brand-studio/uinpm install @brand-studio/uiyarn add @brand-studio/uibun add @brand-studio/uiInstall the peer dependencies
npm install react react-domImport the stylesheet once
import '@brand-studio/ui/styles.css';Wrap your app in a brand theme
import { BrandTheme, SwipeDeck } from '@brand-studio/ui'; import brand from './brand.json'; <BrandTheme palette={brand.tokens} mode="system"> {/* your app */} </BrandTheme>
Usage
import { SwipeDeck } from '@brand-studio/ui';<SwipeDeck label="Build this month’s box" cards={lots} leftLabel="Skip" rightLabel="Add to box"
onSwipe={(card, direction) => direction === 'right' && add(card)} />Swiped cards go to the back of the deck, so it never runs out. onSwipe receives the card and 'left' or 'right'.
Accessibility
- Every swipe has a matching button with a visible label, and the arrow keys work once focus is in the deck.
- The title of the card on top is announced after each choice.
- Cards under the top one are hidden from screen readers. Reduced motion skips the throw.
API Reference
| Prop | Type | Default |
|---|---|---|
cards | DeckCard[] | – |
label? | string | 'Cards' |
leftLabel? | string | 'Skip' |
rightLabel? | string | 'Keep' |
onSwipe? | ((card: DeckCard, direction: 'left' | 'right') => void) | – |
className? | string | '' |

