Actions

DecisionDeck

Stack of decision cards for working through a review queue on a phone: drag a card to accept or reject it, or press the equal round buttons, with Undo, progress and an all-caught-up state.

import { DecisionDeck } from '@lostgradient/cinder';
reviewqueueswipedecision
01

Overview

Reference usage

DecisionDeck is a stack of decision cards for working through a review queue on a phone, where a grid of buttons reads worse than one card at a time. Drag the top card right to accept it and left to reject it: a stamp fades in as the card moves, a release past the threshold (110px by default) decides, and a shorter release springs the card back. Under the card sit equal round buttons for reject, later and accept, which do exactly what a swipe does, and the focused card also takes the arrow keys. Every decision is announced, an Undo appears after each one, and a progress bar and an "All caught up" state frame the queue.

A swipe is only a shortcut. Every decision is reachable by button and by keyboard, a reject is never hidden or folded into a menu, and an accidental swipe is never final: the Undo stays visible until the next decision, and it brings back the exact card and returns focus to the control that made the decision.

Use ApprovalCard for one pending request that needs its full detail. Use DecisionDeck for a queue of short cards.

Usage

svelte
<script lang="ts">
  import { DecisionDeck } from '@lostgradient/cinder';

  const items = [
    {
      id: 'receipts',
      title: 'Archive receipts',
      description: 'Move mail from receipts@shop.example to Receipts.',
    },
    { id: 'digest', title: 'Label the weekly digest', eyebrow: 'News Weekly' },
  ];
</script>

<DecisionDeck
  label="Review queue"
  {items}
  onDecision={({ item, outcome }) => console.info(item.id, outcome)}
  onUndo={({ item }) => console.info('undone', item.id)}
/>

Decisions and Undo

items is the whole queue, decided cards included. The deck records each decision against the item's id and hides the card, so keep decided items in the list: Undo brings the card back from it, and the progress bar counts against its length. onDecision fires once per decision, from a swipe, a button or a key alike, with { item, outcome } where outcome is accept, reject or later. Treat the decision as provisional, because the person can still undo it, and reverse it in onUndo. A card decided later leaves the deck like the others, and the empty state says how many were left for later.

Undo steps back through the decisions one at a time, most recent first, and stays visible after the last card, next to the "All caught up" state.

Keyboard

The top card is a focus stop. With it focused, ArrowRight accepts, ArrowLeft rejects and ArrowDown sets the card aside for later. The three round buttons are ordinary buttons in the tab order, so a keyboard decision never needs the arrow keys. After a decision, focus stays on the button that made it, or moves to the next card when a key made it, or to the empty state when the last card is gone. Undo returns focus to the control that made the undone decision.

Custom card content

By default a card shows an eyebrow, a title and a description. Pass a card snippet to draw your own body for each item; the card's accessible name, stamps and gestures remain the deck's. A link, button or field inside a card keeps working: a drag does not start on it.

Live preview

A review queue

1 of 3

Receipts

Archive receipts

Move mail from receipts@shop.example to the Receipts label.

Press the right arrow key to accept, the left arrow key to reject, or the down arrow key to decide later.

02

When to use

Use when
  • Working through a queue of items one at a time on a phone, where each item gets an accept, reject or later decision.
  • Offering a swipe as a shortcut for decisions that also have buttons and keys, with a visible Undo after each one.
Avoid when
03

Examples

A review queue

Drag the top card right to accept and left to reject, or press the round buttons. Each decision shows an Undo, and the deck shows progress and an All caught up state.

Reacting to decisions

onDecision fires as soon as a decision is made, and onUndo when it is reversed, so the page can keep its own record in step with the deck.

04

Props

Props for decision-deck
Name
Type
Default
Description
items required TItem[] The whole queue in reading order, decided cards included: the deck tracks decisions itself and hides a card once it is decided, so keep decided items in this list, because Undo brings the card back from it.
label text Accessible name of the deck, such as "Review queue". Left unset, the deck is unnamed.
threshold number How far in pixels a card must be dragged sideways before releasing it decides. Defaults to 110. A shorter release springs the card back.
acceptLabel text 'Accept' Text of the accept button, the accept stamp and the accept announcement. Defaults to "Accept".
rejectLabel text 'Reject' Text of the reject button and the reject stamp. Defaults to "Reject".
laterLabel text 'Later' Text of the later button. Defaults to "Later".
undoLabel text 'Undo' Text of the undo button. Defaults to "Undo".
emptyTitle text 'All caught up' Title of the empty state shown when every card is decided. Defaults to "All caught up".
emptyDescription text Supporting line of the empty state. Left unset, it says how many cards were left for later.
keyboardHint text 'Press the right arrow key to accept, the left arrow key to reject, or the down arrow key to decide later.' Hidden text that names the keys on a focused card, read after the card's name. Defaults to a sentence naming ArrowRight, ArrowLeft and ArrowDown.
progressText (position: number, total: number) => string Builds the progress text from the position of the top card and the total, such as "2 of 5".
describeDecision (decision: DecisionDeckDecision<TItem>) => string Builds the sentence the undo bar shows and the live region announces for a decision, such as "Accepted: Archive receipts".
onDecision (decision: DecisionDeckDecision<TItem>) => void Called once for each decision, from a swipe, a button or the keyboard alike, as soon as it is made. The card stays undoable until the next decision, so treat the decision as provisional and reverse it in onUndo.
onUndo (decision: DecisionDeckDecision<TItem>) => void Called when a decision is undone, with the decision that was reversed.
card snippet Renders inside each card, replacing the built-in eyebrow, title and description. The card's name, stamps and gestures stay the deck's.
05

Accessibility

ArrowRight
Accepts the focused top card.
ArrowLeft
Rejects the focused top card.
ArrowDown
Sets the focused top card aside for later.
Every swipe decision is also a labelled button and an arrow key, and each decision is announced and can be undone.