# Sortable List

A component that lets users customize the order of list items.

Tabs: [Guidelines](/components/web/sortable-list/sortable-list.guidelines.md) · [Code](/components/web/sortable-list/sortable-list.code.md)

## Basic usage

`SortableList` renders each item with `renderItem` and adds a drag handle after it. It is controlled: keep the items in state and update them in `onSortEnd`, which receives the items in their new order.

Every item needs a unique `id`. It is used to track the item while it is dragged.

```tsx
import {SortableList, Text} from '@pleo-io/telescope'

const ColumnOrder = () => {
    const [columns, setColumns] = React.useState([
        {id: 'name', label: 'Name'},
        {id: 'email', label: 'Email'},
        {id: 'role', label: 'Role'},
    ])

    return (
        <SortableList
            items={columns}
            onSortEnd={setColumns}
            renderItem={(column) => <Text>{column.label}</Text>}
        />
    )
}
```

Items can only be dragged vertically, and only within the list.

## Interactive items

`renderItem` can return interactive content, such as a `Checkbox` to show or hide a column. Only the drag handle starts a drag, so the content stays usable.

```tsx
<SortableList
    items={columns}
    onSortEnd={setColumns}
    renderItem={(column) => (
        <Checkbox
            name={column.id}
            checked={column.visible}
            onChange={(event) => toggleColumn(column.id, event.target.checked)}
        >
            {column.label}
        </Checkbox>
    )}
/>
```

## Separators

Use `renderSeparator` to render content between two items, for example a divider. It is not rendered after the last item and is not draggable.

```tsx
<SortableList
    items={rules}
    onSortEnd={setRules}
    renderItem={(rule) => <RuleCard rule={rule} />}
    renderSeparator={() => <Divider />}
/>
```

## Accessibility

- Each drag handle is a `button`, labelled with `SortableList.DragHandle`, that can be reached with Tab. Press Space to pick up the item, the arrow keys to move it, Space to drop it and Escape to cancel.
- The handle is described by `SortableList.ScreenReaderInstructions`, which explains these keys.
- Screen readers announce when an item is picked up, moved, dropped or put back. The announcements read out the item's `id`, its position and the number of items.

## Localisation

The handle label, instructions and announcements come from the `SortableList` keys of the `translations` passed to `TelescopeProvider`. The announcements are functions that receive the item's `id`, its position (starting at 1) and the number of items:

```tsx
<TelescopeProvider
    translations={{
        ...translations,
        SortableList: {
            DragHandle: 'Drag to reorder.',
            ScreenReaderInstructions: 'To pick up a sortable item, press the space bar. …',
            PickedUp: (id, position, count) =>
                `Picked up sortable item ${id}. Sortable item ${id} is in position ${position} of ${count}`,
            Moved: (id, position, count) =>
                `Sortable item ${id} was moved into position ${position} of ${count}`,
            Dropped: (id, position, count) =>
                `Sortable item ${id} was dropped at position ${position} of ${count}`,
            Cancelled: (id, position, count) =>
                `Sorting was cancelled. Sortable item ${id} was dropped and returned to position ${position} of ${count}.`,
        },
    }}
>
```

## API reference

#### `items` (required)

Type: `T[]`

Default: -

The items to render, in their current order. Each `id` must be unique and is read out in the screen reader announcements.

#### `onSortEnd` (required)

Type: `(items: T[]) => void`

Default: -

Called with the reordered items when an item is dropped in a new position.

#### `renderItem` (required)

Type: `(data: T) => Element`

Default: -

Renders the content of an item. The drag handle is rendered after it.

#### `renderSeparator`

Type: `((item: T, index: number) => ReactNode) | undefined`

##### Allowed values

- `(item: T, index: number) => ReactNode`

Default: -

Rendered between two consecutive items (never after the last one), for example an "if no match, try next" divider for a list whose items are evaluated in order. It sits outside each item's draggable wrapper, so it isn't itself draggable.
