- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
Order: Alex, Jordan, Sam
"use client"
import * as React from "react"Use a sortable list when people set their own order, such as the sections on a home screen, the accounts in a switcher or the contacts to notify first. Each row has a drag handle, an optional action and a muted state, and two lists that share a drag type can exchange rows for show and hide. Pick a plain Item list when the order is fixed.
Installation#
pnpm dlx shadcn@latest add @dots/sortable-list
Usage#
import { SortableList, SortableListItem } from "@/components/ui/sortable-list"const [accounts, setAccounts] = React.useState([
{ id: "main", name: "Main account" },
{ id: "savings", name: "Savings" },
])
<SortableList
aria-label="Account order"
items={accounts}
getItemText={(account) => account.name}
onReorder={setAccounts}
>
{(account) => (
<SortableListItem id={account.id} textValue={account.name} />
)}
</SortableList>The list is controlled. It reports the new order through onReorder and renders the items you pass back.
Composition#
SortableList
└── SortableListItem
├── leading
├── title and description
├── action
└── drag handleFeatures#
- Built on React Aria
GridListdrag and drop - Drag a row with the pointer or a finger, or focus its handle and use the keyboard
- Screen readers announce where a row is picked up and dropped
- A leading slot, a description line and an action slot on every row
- A muted state for rows in a "Hidden" group
- Lists that share a
dragTypeaccept each other's rows - An empty state that is also a drop target
isDragDisabledto lock the order, for example while saving
Show and hide#
Give two lists the same dragType and handle onInsert on both. Rows can be dragged between them, and each row's action button moves it without dragging. Set muted on the hidden rows.
On Home
Hidden
"use client"
import * as React from "react"<SortableList
aria-labelledby={hiddenLabel}
items={hidden}
dragType="application/x-home-sections"
onReorder={setHidden}
onInsert={(keys, index) => moveTo("hidden", keys, index)}
renderEmptyState={() => "Nothing hidden"}
>
{(section) => (
<SortableListItem id={section.id} textValue={section.title} muted />
)}
</SortableList>onInsert receives the dragged keys and the index they were dropped at. Remove the rows from the source list and insert them into the target list there.
Locked order#
Set isDragDisabled to turn dragging off. Rows cannot be dragged until it is turned back on.
"use client"
import * as React from "react"Accessibility#
React Aria renders the list as a grid with one row per item. The pointer drags the whole row, while the handle is a real button and the keyboard and screen reader entry point: focus it, press Enter, move to a drop position and press Enter again to drop, or Escape to cancel. Drops are announced. Row actions, such as the hide and show buttons, give keyboard users a second way to move rows between lists.
Name the list#
Give every list a name with aria-label, or with aria-labelledby pointing at a visible heading.
<SortableList aria-label="Contact order" items={people} onReorder={setPeople}>
{/* rows */}
</SortableList>Give rows a text value#
textValue is required. React Aria reads it in the handle's name and in drag announcements, so it should match the visible title.
Label row actions#
Action buttons are usually icon-only. Give each an aria-label with the action and the row.
<Button variant="ghost" size="icon-sm" aria-label={`Hide ${section.title}`}>
<EyeOffIcon />
</Button>API Reference#
SortableList#
The list. Renders a React Aria GridList with a stack layout and accepts its other props, such as aria-label and aria-labelledby.
| Prop | Type | Default | Description |
|---|---|---|---|
items | T[] | - | The rows, in order. Each needs a stable id. Required. |
children | (item: T) => React.ReactNode | - | Renders one row, usually a SortableListItem. Required. |
onReorder | (items: T[]) => void | - | Called with the whole list in its new order after a drop inside it. |
onInsert | (keys: Key[], index: number) => void | - | Accepts rows from another list with the same dragType. |
getItemText | (item: T) => string | String(item.id) | Text for the drag preview and for drags into other apps. |
dragType | string | unique to the list | Lists that share it accept each other's rows. Compared in lowercase. |
isDragDisabled | boolean | false | Turns dragging off. |
renderEmptyState | () => React.ReactNode | "No items" | Shown when the list has no rows. Rows can be dropped there. |
className | string | - | Additional classes to apply to the list. |
T is any object with an id of type Key (a string or a number).
SortableListItem#
One row with a drag handle at the end. Renders a React Aria GridListItem and accepts its other props.
| Prop | Type | Default | Description |
|---|---|---|---|
id | Key | - | The item's id. Must match the item in items. |
textValue | string | - | Plain-text name read in the handle and announcements. Required. |
children | React.ReactNode | textValue | The row's title. |
description | React.ReactNode | - | One quiet line under the title. |
leading | React.ReactNode | - | Avatar, logo or icon before the text. |
action | React.ReactNode | - | Controls before the handle, such as a hide or show button. |
muted | boolean | false | Dims the row, for items in a "Hidden" group. |
className | string | - | Additional classes to apply to the row. |