Reorder List
betaA list you put in order, like an on-call order or a meeting's running order. Drag a row by its handle, or pick it up with Space and move it with the arrows.
Preview
- Intros
- Roadmap update
- Design review
- Sales numbers
- Demo of the week
- Wrap-up
- Sam Carter
- Noah Kerr
- Cal Nash
- Dan Kerr
- Ben Ward
- Owen Price
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/reorder-list
Use it for
- An on-call order, a meeting's running order, agenda items: one short list, in a Card.
- Pass items with an id each. A person shows with their photo. Anything else shows its label and meta, or your renderItem.
- Keep the order in state with onReorder. It hands back your own objects, in their new order, after each drop.
- Tell people about the new order in onSettle. It runs once the 8-second ⌘Z window ends, so ⌘Z never takes back a message already sent.
- Turn on numbered when the rank matters. Ranks and dividers belong to the slots and stay put while rows move.
- Set disabled once the order is final, like once the order is sent. The handles go, so it no longer looks as if it could move.
Avoid it for
- Placing people onto a team sheet's positions. That is a slot for each position, like PositionSlot.
- Long lists that scroll. A drag does not scroll the page for you.
- Your own undo button or timer. Each drop joins the page's ⌘Z stack, and moves made within 8 seconds undo together.
Props
- items
The rows, in order.
T[] where T extends { id: string; person?: string; photo?: string; label?: string; meta?: string }
- onReorder
The items in their new order after each drop, and the order before when ⌘Z puts it back.
(items: T[]) => void
- onSettle
The order is final: its ⌘Z window ended. Tell people here.
(items: T[]) => void
- renderItem
Row content. Default a Person for people, otherwise the label and meta.
(item: T, index: number) => ReactNode
- numbered
Shows each rank at the row's left.
boolean · default false
- label
Names the list, like "On call, in order".
string
- disabled
Locked. The rows show no handles.
boolean · default false
- className
Extra classes. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| items | T[] where T extends { id: string; person?: string; photo?: string; label?: string; meta?: string } | - | The rows, in order. |
| onReorder | (items: T[]) => void | - | The items in their new order after each drop, and the order before when ⌘Z puts it back. |
| onSettle | (items: T[]) => void | - | The order is final: its ⌘Z window ended. Tell people here. |
| renderItem | (item: T, index: number) => ReactNode | - | Row content. Default a Person for people, otherwise the label and meta. |
| numbered | boolean | false | Shows each rank at the row's left. |
| label | string | - | Names the list, like "On call, in order". |
| disabled | boolean | false | Locked. The rows show no handles. |
| className | string | - | Extra classes. Later classes win. |
Accessibility
- The list is an ordered list named by label, so screen readers say each row's place.
- Each row's handle is a button named "Move" and the row's name. The handles share one Tab stop, and ↑↓, Home, and End move between them.
- Space picks the focused row up, and its handle reads as pressed. ↑↓ move it, Home and End take it to the ends, Space or Enter drops it, and Esc puts it back.
- A row that is up owns Esc, even inside a Sheet. The next Esc closes the sheet.
- Focus leaving a row that is up drops it where it is.
- A polite live region says each step: picked up, each place it passes, and where it dropped or went back.
- Every drag has the same moves on the keys. A mouse lifts a row after 4 px, a finger after holding still for 350 ms, and Esc cancels a drag.
- Reduced motion turns off the glide. The rows step to their new places.
Examples
A running order
const [plan, setPlan] = useState(PLAN);
<Card className="gap-0 py-1.5">
<ReorderList items={plan} label="Tuesday's agenda, in order" numbered onReorder={setPlan} onSettle={save} />
</Card>People in ranked order
<ReorderList
items={[
{ id: "sam", person: "Sam Carter", meta: "Engineering" },
{ id: "noah", person: "Noah Kerr", meta: "Engineering" },
]}
label="On call, in order"
numbered
onReorder={setOnCall}
/>