KQ-1501 · LEDGER/SWIPE

Ledger Swipe

A list of ledger rows with their actions parked underneath: a row follows a sideways drag 1:1 while the tiles on that side are uncovered one after another, each one's icon and label scaling in as its slot comes into view. Past the threshold the primary arms with a snap and springs across the whole uncovered width on springs.snap, its label riding the row's edge; letting go (or a flick whose projected rest gets there) commits it, and the row slides out on the exit ease while its height folds shut on springs.glide and the rows below close the gap. A shorter swipe snaps open to show the tiles on a spring whose bounce is the elastic setting. From the keyboard, each row's Actions button opens a menu of every action, where arrows move, Enter chooses and Escape closes.

KQ-1501 · LEDGER/SWIPE
  • Fernworks NurseryGarden · Tue 14−42.80
  • Waylight RailTransport · Tue 14−3.40
  • Gaugeworks PayrollIncome · Mon 13+2,140.00
  • Basin CoffeeEating out · Mon 13−4.60

4 to review · swipe a row

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/ledger-swipe

Props

items
{ id: string; title: string; subtitle?: string; amount?: number; icon?: ReactNode; flagged?: boolean }[]
default —
The rows. Amounts are signed (money in is positive) and shown through format; a flagged row shows a flag beside its amount.
trailingActions
LedgerSwipeAction[]
default —
Revealed by swiping left, from the right edge in; the first is the primary that a long swipe commits.
leadingActions
LedgerSwipeAction[]
default —
Revealed by swiping right, from the left edge in; the first is the primary.
actions
"both" | "trailing" | "leading"
default "both"
Which sides swipe; the other side barely gives. Every action stays in the keyboard menu either way.
onAction
(actionId: string, itemId: string) => void
default —
An action was chosen by a long swipe, a tap on its tile or the menu; fires at the moment of choice. Remove the row from items for a removing action — it stays drawn until it has folded away.
onOpenChange
(itemId: string | null) => void
default —
A row was swiped open (its id) or closed (null).
threshold
number
default 0.5
How far a swipe must travel to arm and commit the primary, as a share of the row's width.
elastic
number
default 0.5
How much the row gives, 0 to 1: the rubber band past the end and on a side with no actions, and the bounce of the snap back.
actionWidth
number
default 64
Each tile's width when a row is swiped open, in px (at least 44).
hint
boolean
default false
The first time the list is on screen, the first row slides open a little and settles back: the gesture, shown once.
format
(amount: number) => string
default signed, thousands, two decimals
How an amount is written; the default is built by hand so the server and the page agree.
label
string
default —
The list's accessible name.
emptyLabel
string
default "Nothing left to review"
Shown and said when every row has gone.
size
"sm" | "md" | "lg"
default "md"
Row height 48, 56 or 64px.
sound
boolean
default false
A snap as a row snaps open or arms its primary, a swish as one slides away; only ever in answer to a drag, a press or a key.
disabled
boolean
default false
Rows stop following the finger and every action is disabled.
className
string
default —
Classes for the root (w-full max-w-md).