# Reorder List

Accessible sortable list with pointer dragging, keyboard grab and move, cancellation, and commit announcements.

- Package: `@mielui/svelte`
- Component version: `1.0.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: utils.cn, utils.createContext, transition

## Install

~~~~sh
pnpm dlx @mielui/svelte add reorder-list
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Content

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Handle

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Item

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| id | string | - | Yes | No | The item's id from `getId`. It is not applied as the element's DOM id. |
| label | string | - | Yes | No | Name of the item, used as the handle's accessible name. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### ReorderList

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| labels | ReorderListLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| items | unknown[] | - | Yes | Yes | Items in their current order. Bindable. |
| getId | (item: unknown) => string | - | Yes | No | Returns a stable id for an item. |
| getLabel | (item: unknown) => string | - | Yes | No | Returns the name announced for an item. |
| children | Snippet<[unknown]> \| undefined | - | No | No | Renders an item's content beside the default handle. |
| row | Snippet<[unknown]> \| undefined | - | No | No | Replaces the default row. Compose `Item`, `Handle`, and `Content` inside it. |
| label | string | - | Yes | No | Accessible name of the list. |
| disabled | boolean \| undefined | false | No | No | Prevents interaction and dims the control. |
| onReorder | ((items: unknown[]) => void) \| undefined | - | No | No | Called with the new order on every move. |
| onCommit | ((items: unknown[]) => void) \| undefined | - | No | No | Called once when a move is dropped or confirmed. |
| class | (string & (ClassValue \| null)) \| undefined | - | No | No | Classes added to the element. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { Snippet } from 'svelte';
import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
import ReorderList from './reorder-list.svelte';
import Content from './reorder-list-content.svelte';
import Handle from './reorder-list-handle.svelte';
import Item from './reorder-list-item.svelte';

export type ReorderListLabels = {
    hint?: string;
    grabbed?: (item: string, position: number, total: number) => string;
    moved?: (item: string, position: number, total: number) => string;
    dropped?: (item: string, position: number) => string;
    cancelled?: string;
    cancelledByChange?: string;
};

export type ReorderListProps<T> = {
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: ReorderListLabels;
    /** Items in their current order. Bindable. */
    items: T[];
    /** Returns a stable id for an item. */
    getId: (item: T) => string;
    /** Returns the name announced for an item. */
    getLabel: (item: T) => string;
    /** Renders an item's content beside the default handle. */
    children?: Snippet<[T]>;
    /** Replaces the default row. Compose `Item`, `Handle`, and `Content` inside it. */
    row?: Snippet<[T]>;
    /** Accessible name of the list. */
    label: string;
    /** Prevents interaction and dims the control. */
    disabled?: boolean;
    /** Called with the new order on every move. */
    onReorder?: (items: T[]) => void;
    /** Called once when a move is dropped or confirmed. */
    onCommit?: (items: T[]) => void;
    /** Classes added to the element. */
    class?: string;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children'>;

export type ReorderListItemProps = HTMLAttributes<HTMLDivElement> & {
    /** The item's id from `getId`. It is not applied as the element's DOM id. */
    id: string;
    /** Name of the item, used as the handle's accessible name. */
    label: string;
};
export type ReorderListHandleProps = Omit<HTMLButtonAttributes, 'type'>;
export type ReorderListContentProps = HTMLAttributes<HTMLDivElement>;
export { Content, Handle, Item, ReorderList, ReorderList as Root };
export default ReorderList;
~~~~

## Examples

### Agenda

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import * as ReorderList from '@mielui/svelte/components/reorder-list';

    const initial = [
        {
            id: 'opening',
            name: 'Opening remarks',
            duration: '5 min'
        },
        {
            id: 'roadmap',
            name: 'Roadmap review',
            duration: '15 min'
        },
        {
            id: 'critique',
            name: 'Design critique',
            duration: '20 min'
        },
        {
            id: 'questions',
            name: 'Open questions',
            duration: '10 min'
        }
    ];

    let items = $state([...initial]);

    function reset() {
        items = [...initial];
    }
</script>

<div class="flex w-full max-w-md flex-col gap-3">
    <div class="flex justify-end">
        <Button variant="ghost" onclick={reset}>Reset</Button>
    </div>
    <ReorderList.Root
        bind:items
        getId={(item) => item.id}
        getLabel={(item) => item.name}
        label="Meeting agenda"
    >
        {#snippet children(item)}
            <span class="flex min-w-0 items-center justify-between gap-4">
                <span class="truncate text-sm font-medium">{item.name}</span>
                <span class="shrink-0 text-xs tabular-nums text-foreground-muted">
                    {item.duration}
                </span>
            </span>
        {/snippet}
    </ReorderList.Root>
    <p class="text-xs leading-relaxed text-foreground-muted">
        Drag a handle, or focus it and press Space to grab its row. Arrow keys move; Escape cancels.
    </p>
</div>
~~~~

### Events

~~~~svelte
<script lang="ts">
    import * as ReorderList from '@mielui/svelte/components/reorder-list';
    import { Switch } from '@mielui/svelte/components/switch';

    let items = $state([
        {
            id: 'triage',
            name: 'Triage'
        },
        {
            id: 'build',
            name: 'Build'
        },
        {
            id: 'review',
            name: 'Review'
        }
    ]);
    let locked = $state(false);
    let saved = $state('Order not saved yet');
</script>

<div class="flex w-full max-w-sm flex-col gap-3">
    <Switch bind:checked={locked} label="Lock the order" />
    <ReorderList.Root
        bind:items
        getId={(item) => item.id}
        getLabel={(item) => item.name}
        label="Workflow stages"
        disabled={locked}
        onReorder={(next) => {
            saved = `Saved: ${next.map((item) => item.name).join(', ')}`;
        }}
    >
        {#snippet row(item)}
            <ReorderList.Item id={item.id} label={item.name}>
                <ReorderList.Content>{item.name}</ReorderList.Content>
                <ReorderList.Handle />
            </ReorderList.Item>
        {/snippet}
    </ReorderList.Root>
    <p role="status" class="text-sm text-foreground-muted">{saved}</p>
</div>
~~~~

### Handles

~~~~svelte
<script lang="ts">
    import * as ReorderList from '@mielui/svelte/components/reorder-list';

    let items = $state([
        {
            id: 'brief',
            name: 'Project brief'
        },
        {
            id: 'design',
            name: 'Design review'
        },
        {
            id: 'release',
            name: 'Release notes'
        }
    ]);
</script>

<ReorderList.Root
    bind:items
    getId={(item) => item.id}
    getLabel={(item) => item.name}
    label="Project documents"
    class="w-full max-w-sm"
>
    {#snippet row(item)}
        <ReorderList.Item id={item.id} label={item.name}>
            <ReorderList.Content>{item.name}</ReorderList.Content>
            <ReorderList.Handle />
        </ReorderList.Item>
    {/snippet}
</ReorderList.Root>
~~~~

For the rendered reference, visit [/docs/components/reorder-list](/docs/components/reorder-list).
