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.
- 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).
| Prop | Type | Default | Description |
|---|---|---|---|
| items | { id: string; title: string; subtitle?: string; amount?: number; icon?: ReactNode; flagged?: boolean }[] | — | The rows. Amounts are signed (money in is positive) and shown through format; a flagged row shows a flag beside its amount. |
| trailingActions | LedgerSwipeAction[] | — | Revealed by swiping left, from the right edge in; the first is the primary that a long swipe commits. |
| leadingActions | LedgerSwipeAction[] | — | Revealed by swiping right, from the left edge in; the first is the primary. |
| actions | "both" | "trailing" | "leading" | "both" | Which sides swipe; the other side barely gives. Every action stays in the keyboard menu either way. |
| onAction | (actionId: string, itemId: string) => void | — | 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 | — | A row was swiped open (its id) or closed (null). |
| threshold | number | 0.5 | How far a swipe must travel to arm and commit the primary, as a share of the row's width. |
| elastic | number | 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 | 64 | Each tile's width when a row is swiped open, in px (at least 44). |
| hint | boolean | 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 | signed, thousands, two decimals | How an amount is written; the default is built by hand so the server and the page agree. |
| label | string | — | The list's accessible name. |
| emptyLabel | string | "Nothing left to review" | Shown and said when every row has gone. |
| size | "sm" | "md" | "lg" | "md" | Row height 48, 56 or 64px. |
| sound | boolean | 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 | false | Rows stop following the finger and every action is disabled. |
| className | string | — | Classes for the root (w-full max-w-md). |