KQ-2129 · RECORD/STEPPER
Record Stepper
A detail-page header that knows where the record sits in its list — "12 of 240" — with Previous and Next: stepping slides the title stack up or down by slideDistance on springs.glide in the direction you moved, clipped to the header so it reads as the list moving under a window, while the counter's digits roll the same way on the same glide, the record body cross-slides by half the distance and the peek of the next record updates. A neighbour the host has not loaded still moves the counter while the title and body show skeletons until it lands, and at either end the missing step leans the title 4 px on springs.flick and returns it on springs.snap. J and K step, Escape returns to the list, and below 30rem the counter becomes a small progress tick.
GW-4417
Can't add a second admin to our workspace
The invite goes out but the role drops back to member when they accept. I'm the only admin and I'm away from Monday.
12 of 240 · GW-4417 · loaded
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/record-stepper
Props
- order
- string[]
- default defaultRecordStepperOrder
- Every record id in the list, in list order; its length is the total the counter reads.
- records
- RecordStepperRecord[]
- default defaultRecordStepperRecords
- The records the host has loaded: { id, title, requester, company, state, priority, updatedAt, excerpt }. A position whose record is missing shows as a loading neighbour until it arrives.
- position / defaultPosition
- number
- default 12
- The 1-based position in the list, controlled or initial.
- onStep
- (direction: 1 | -1, position: number, record: RecordStepperRecord | null) => void
- default —
- Fires from the press or key that stepped, with the new position and its record — null while the host has not loaded it, which is the cue to fetch.
- onBackToList
- () => void
- default —
- Fires from the list button, the end-of-list link or Escape.
- listLabel
- string
- default "Open tickets"
- The list's name, on the back button and in the announcements.
- filters
- { id: string; label: string }[]
- default defaultRecordStepperFilters
- The filters the list is under: a count on the back button, the chips themselves from 52rem, and part of its accessible name.
- slideDistance
- number
- default 16
- How far a title travels when stepping, px; the body travels half of it.
- counterStyle
- "words" | "slash" | "track"
- default "words"
- How the place in the list reads: "12 of 240", "12/240" in an inset pill, or "12 of 240" over a small track whose thumb glides to the position. Below 30rem every style becomes a vertical progress tick.
- accent
- "cobalt" | "plum" | "mint" | "ink" | string
- default "cobalt"
- The track thumb, the requester mark and the end-of-list link: a named accent or any CSS colour.
- now
- number
- default defaultRecordStepperNow
- The time updates are measured from ("updated 12 min ago"), ms since the epoch.
- shortcuts
- boolean
- default true
- Listen for J, K and Escape while focus is inside the piece (ignored while typing in a field).
- status
- "ready" | "error"
- default "ready"
- Record state: error shows a retry in place of the record; stepping still works.
- onRetry
- () => void
- default —
- Called by the error state's Retry button.
- disabled
- boolean
- default false
- Turns stepping and the buttons off.
- className
- string
- default —
- Classes for the outer section.
| Prop | Type | Default | Description |
|---|---|---|---|
| order | string[] | defaultRecordStepperOrder | Every record id in the list, in list order; its length is the total the counter reads. |
| records | RecordStepperRecord[] | defaultRecordStepperRecords | The records the host has loaded: { id, title, requester, company, state, priority, updatedAt, excerpt }. A position whose record is missing shows as a loading neighbour until it arrives. |
| position / defaultPosition | number | 12 | The 1-based position in the list, controlled or initial. |
| onStep | (direction: 1 | -1, position: number, record: RecordStepperRecord | null) => void | — | Fires from the press or key that stepped, with the new position and its record — null while the host has not loaded it, which is the cue to fetch. |
| onBackToList | () => void | — | Fires from the list button, the end-of-list link or Escape. |
| listLabel | string | "Open tickets" | The list's name, on the back button and in the announcements. |
| filters | { id: string; label: string }[] | defaultRecordStepperFilters | The filters the list is under: a count on the back button, the chips themselves from 52rem, and part of its accessible name. |
| slideDistance | number | 16 | How far a title travels when stepping, px; the body travels half of it. |
| counterStyle | "words" | "slash" | "track" | "words" | How the place in the list reads: "12 of 240", "12/240" in an inset pill, or "12 of 240" over a small track whose thumb glides to the position. Below 30rem every style becomes a vertical progress tick. |
| accent | "cobalt" | "plum" | "mint" | "ink" | string | "cobalt" | The track thumb, the requester mark and the end-of-list link: a named accent or any CSS colour. |
| now | number | defaultRecordStepperNow | The time updates are measured from ("updated 12 min ago"), ms since the epoch. |
| shortcuts | boolean | true | Listen for J, K and Escape while focus is inside the piece (ignored while typing in a field). |
| status | "ready" | "error" | "ready" | Record state: error shows a retry in place of the record; stepping still works. |
| onRetry | () => void | — | Called by the error state's Retry button. |
| disabled | boolean | false | Turns stepping and the buttons off. |
| className | string | — | Classes for the outer section. |