Getting Started
Install
Section titled “Install”npm install @rpxl/riffleYour first stack
Section titled “Your first stack”A complete component. Pass it any array of objects with a title:
import { Riffle } from '@rpxl/riffle/react'
export function Stack({ films }: { films: { title: string }[] }) { return ( // justifyContent keeps the stack's one grid column, and so every card, the // card's own width (centred), so the fan scales about the card itself. <Riffle aria-label="Films" cards={films} cardWidth={300} cardHeight={400} style={{ justifyContent: 'center' }} > {(film, index) => ( <div style={{ width: 300, height: 400, borderRadius: 16, background: `hsl(${index * 72} 65% 45%)`, color: 'white', display: 'grid', placeItems: 'center', }} > {film.title} </div> )} </Riffle> )}That exact file, running. Drag the front card:
Neon Harbor
The Quiet Orbit
Salt and Static
Paper Moons
Last Train to Vela
Glasshouse
Northbound
Kite Season
What just happened
Section titled “What just happened”<Riffle>renders one card per item incards, from your render function, and stacks them in a single grid cell for the engine to move.- Every Riffle option is a prop:
cardWidthandcardHeighthere. Any other prop, such asaria-labelorstyle, goes to the root element.
cardWidthandcardHeightdescribe each card’s geometry to the engine (how far a drag travels, how the fan is laid out) and do not size or style any element: the cards’ own styles give them their size, background and radius.- The engine marks the stack as a carousel but cannot name it: give it an accessible name with
aria-label, as inaria-label="Films".
Headless: useRiffle and useRiffleState
Section titled “Headless: useRiffle and useRiffleState”For your own markup instead of <Riffle>, useRiffle returns prop getters for the root and each
card, plus next, prev and goTo. useRiffleState subscribes to one slice of the state, so
this component re-renders when the active card changes, never on a drag frame:
const riffle = useRiffle({ count: films.length, cardWidth: WIDTH, cardHeight: HEIGHT, getLabel })// Re-renders when the active card changes, never on a drag frame.const activeIndex = useRiffleState(riffle, (state) => state.activeIndex)
<section {...riffle.getRootProps({ 'aria-label': 'Films', style: { width: WIDTH } })}> {films.map((film, index) => ( <article key={film.title} {...riffle.getCardProps(index, { style: { width: WIDTH, height: HEIGHT, borderRadius: 16, background: posterGradient(film), }, })} /> ))}</section>Next steps
Section titled “Next steps”- Guides: Concepts, Gestures (with a live tuning panel), Layouts, Layout and overflow and Accessibility.
- Recipes: four worked patterns, Infinite feed, Clamp with controls, Programmatic control and Forms inside cards.
- Examples: the gallery of runnable apps for this framework, each with its source.
- Adapter details: how options update between renders, refs, the imperative handle, and server rendering.
- The full example:
examples/react-movie-stack, with posters, prev and next buttons, and a phone-width resize. - API:
@rpxl/riffle/react.