Getting Started
Install
Section titled “Install”npm install @rpxl/riffleYour first stack
Section titled “Your first stack”Give your page an empty container, <div id="stack"></div>, then run this script:
import { createRiffle } from '@rpxl/riffle'
const stack = document.getElementById('stack')!// One grid cell for every card: Riffle positions them from there with transforms.// justify-content keeps that cell the card's own width (centred), so the fan// scales about the card itself.stack.style.cssText = 'display: grid; justify-content: center; padding: 32px 0'// Name the carousel: screen readers announce this label with it.stack.setAttribute('aria-label', 'Films')
const riffle = createRiffle(stack, { count: 5, cardWidth: 300, cardHeight: 400 })
for (let i = 0; i < 5; i++) { const card = stack.appendChild(document.createElement('div')) card.textContent = String(i + 1) card.style.cssText = `grid-area: 1 / 1; width: 300px; height: 400px; border-radius: 16px; background: hsl(${i * 72} 65% 45%); color: white; display: grid; place-items: center` riffle.registerNode(i, card)}
// Drag the front card, or wire riffle.next(), riffle.prev() and riffle.goTo(index)// to controls of your own.That exact file, running. Drag the front card:
What just happened
Section titled “What just happened”- The container is a single grid cell. Every card sits in it (
grid-area: 1 / 1), and the engine moves each one from there with transforms. createRiffle(stack, { count, cardWidth, cardHeight })creates the engine and marks the container up as a carousel, with a live region and keyboard support.registerNode(i, card)tells the engine which element is cardi.
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".
Controls, events and cleanup
Section titled “Controls, events and cleanup”Buttons call riffle.next() and riffle.prev() (or riffle.goTo(index)), riffle.on('change')
reports the active card and returns an unsubscribe, and riffle.destroy() undoes everything the
engine did to your markup. From the full movie-stack example:
function onPrev(): void { riffle.prev()}function onNext(): void { riffle.next()}prevButton.addEventListener('click', onPrev)nextButton.addEventListener('click', onNext)
const unsubscribe = riffle.on('change', ({ index }) => updateMeta(index))updateMeta(riffle.getSnapshot().activeIndex)
return function unmount(): void { mql.removeEventListener('change', onMobileChange) prevButton.removeEventListener('click', onPrev) nextButton.removeEventListener('click', onNext) unsubscribe() riffle.destroy() el.innerHTML = ''}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.
- The full example:
examples/vanilla-movie-stack, with posters, prev and next buttons, and a phone-width resize. For a page with no build step at all, seeexamples/vanilla-basic. - API:
@rpxl/riffle.