Flashcard Deck
A study flashcard deck React component. Tap or press Space to flip in 3D, drag or use arrow keys to sort into known and unknown, with a progress bar and an end screen showing an accuracy ring, restart and review-unknown options.
A flashcard deck for learning vocabulary or terms. Tap a card or press Space to flip it with a 3D spring; drag right or press → to mark it known, drag left or press ← to mark it unknown. Past the threshold, or with a fast enough throw, the card rotates and flies off screen while the next one rises from the preview slot. A progress bar with known / unknown counts sits underneath, and Backspace undoes the last card. When a round ends, the deck turns into a stats card with an accuracy ring, plus "restart all" and "review unknown only".
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add flashcard-deck.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<FlashcardDeck/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/flashcard-deck.en.jsonOr, once registries are configured in components.json, install it as @webberui/flashcard-deck.
Usage
import { FlashcardDeck } from "@/components/ui/flashcard-deck";
const cards = [
{ id: "persist", front: "堅持", back: "persist (v.)", hint: "keep going" },
{ id: "curious", front: "好奇", back: "curious (adj.)" },
{ id: "hesitate", front: "猶豫", back: "hesitate (v.)" },
];
<FlashcardDeck
cards={cards}
onResult={(id, known) => console.log(id, known ? "known" : "unknown")}
onComplete={(results) => console.log("round results", results)}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
cards | FlashcardItem[] | — | The cards |
onResult | (id: string, known: boolean) => void | — | Fires each time a card is sorted |
onComplete | (results: FlashcardResult[]) => void | — | Fires when a round is fully sorted; results are in answer order |
stiffness | number | 260 | Stiffness of the flip spring (damping is fixed at 24) |
showProgress | boolean | true | Show the bottom progress bar, position count and known / unknown tally |
allowUndo | boolean | true | Allow stepping back one card with Backspace / U or the undo button |
shuffle | boolean | false | Deterministically shuffle using seed |
seed | number | 1 | Shuffle seed; the same seed always yields the same order |
labels | Partial<FlashcardDeckLabels> | — | Interface string overrides (buttons, announcements, position text) |
className | string | — | Forwarded to the outermost container |
FlashcardItem
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id, returned by onResult |
front | ReactNode | — | Front content |
back | ReactNode | — | Back content |
hint | string | — | Small hint under the front content |
FlashcardResult
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Card id |
known | boolean | — | true for known, false for unknown |
Color variables
| Variable | Default | Description |
|---|---|---|
--wb-flashcard-deck-known | #10b981 | Color of the known button, drag label, tally and ring |
--wb-flashcard-deck-unknown | #f43f5e | Color of the unknown button, drag label and tally |
How it works
- Flip: the card container sets
perspective: 1200pxand an inner layer springsrotateYbetween 0° and 180°; both faces usebackface-visibility: hidden. - Stack: two preview cards sit under the current one (scale 0.95 / 0.9, offset 8 / 16px, opacity 0.7 / 0.4); after sorting, the next card springs up from the preview slot.
- Sorting: while dragging, the card rotates by 1/20 of its offset and "unknown" / "known" labels fade in on either side. On release, an offset over 120px or a velocity over 500px/s throws the card (300ms ease-in, rotation ending at ±20°); otherwise it springs back to the centre.
- On undo, the previous card flies back in from the side it was thrown to, and
onResultfires again when it is re-sorted. - "Review unknown only" on the end screen starts a new round using only the cards whose
knownwasfalse; it is disabled when every card was known. shuffleuses a seeded deterministic random generator, so server and browser produce the same order with no hydration mismatch. Passing a newcardsarray with the same set of ids does not reset progress.
Accessibility
- The card container is focusable (
tabIndex=0), withrole="group", anaria-roledescriptionof "flashcard" and anaria-labeldescribing the position (for example "Card 4 of 10") - Keyboard: Space / Enter flips, ← marks unknown, → marks known, Backspace or U undoes
- Visible "Unknown", "Flip" and "Known" buttons mean touch and assistive technology users never have to drag; the flip button carries
aria-pressed - The face that is not showing is
aria-hidden; when the card flips, the back content is announced through anaria-live="polite"region, and after sorting "Marked as known" or "Marked as unknown" is announced - The progress bar is a
role="progressbar"witharia-valuenowandaria-valuemax - When the user has reduce motion enabled at the system level, the flip becomes a 200ms cross-fade between faces, throws become fade-outs, dragging and all rotation are disabled, and the progress bar and ring jump straight to their values
Fanned Cards
A hand of cards fanned out like a held deck: hover lifts a card while neighbours make room, and dragging up or flicking plays it onto a pile. Card faces accept any content.
Node Wire Canvas
A node-based workflow canvas: freely draggable nodes, bezier wires between ports, drag-to-connect from any port, flowing pulses along edges while running, plus pan and zoom.