# Sortable A single list that allows users to reorder items through drag-and-drop interactions. ## Usage Use standalone, or inside `SortableGroup` to connect lists. ```svelte ``` ## Anatomy The `class` attribute values represent part names. Other attributes represent their corresponding props. ```svelte ``` A `position: fixed` floating shadow is rendered while dragging. The `
  • ` items use roving `tabindex` and carry `aria-roledescription`; a visually hidden `aria-live="polite"` region announces keyboard drag actions. ## Props | Name | Type | Default | Description | |---|---|---|---| | `items*` | `T[]` | - | Bindable plain array; mutations happen in place through `bind:items` | | `item*` | `Snippet` | - | Item renderer; receives the value, variant, and drag handle | | `key` | `(item: T) => PropertyKey` | - | Identity extractor; required for object items | | `clone` | `(item: T) => T` | - | Clone factory; required for object items in clone mode | | `group` | `SortableGroupController` | - | Shared controller from `createSortableGroup(...)` for connected lists | | `id` | `string` | - | List id used by `accept` | | `ghost` | `Snippet` | - | Custom floating preview rendered while dragging | | `mode` | `` | `"move"` | `move` moves items, `clone` copies to the target, and `swap` swaps with the target item | | `accept` | `string[]` | - | Accepted source list ids; omitted accepts any list in the group, `[]` accepts none | | `sort` | `boolean` | `true` | Enables sorting within the same list | | `multiple` | `boolean` | `false` | Enables click selection; dragging a selected item carries the other selected items as followers | | `draggable` | `boolean` | `true` | Allows the whole item to start a drag when no custom handle is attached | | `appendable` | `boolean` | `false` | Allows items to append when entering the list area, including empty lists | | `confirm` | `boolean` | `false` | Adds a hover delay before committing a move | | `dragging` | `boolean` | - | Bindable group drag activity | | `messages` | `Partial` | - | Live-region announcement formatters for keyboard actions; ignored when a `group` controller supplies its own | | `ariaLabel` | `string` | - | Accessible label for the list | | `ariaRoleDescription` | `string` | `"Sortable Item"` | `aria-roledescription` applied to each item | | `styling` | `SVSClass` | - | Styling override | | `variant` | `SVSVariant` | `NEUTRAL` | Base variant for items | `*` required. Other `
      ` attributes are passed via rest props; `class` is merged onto the list. Group motion defaults to `300ms` / `cubicOut`; reduced motion resolves the duration to `0`. ## Styling To learn more, see [Styling](/docs/styling). ### Variant Management The item `variant` becomes `active` while an item is selected or dragged. ### Default Class Name `svs-sortable` ## Accessibility Name the list with `ariaLabel`, or pass `aria-labelledby` through rest props. ### Keyboard Interactions | Key | Description | | --- | --- | | `ArrowUp` / `ArrowDown` | Moves focus to the previous or next item; while an item is picked up, moves the picked-up item | | `Space` / `Enter` | Picks up the focused item, or drops the picked-up item | | `Escape` | Cancels the pick-up and returns the item to its original position | | `Ctrl` + `Space` | Toggles selection of the focused item (when `multiple` is true) | ### ARIA Roles and Attributes The component applies these automatically: - List (`whole` `
        `): `aria-label` from `ariaLabel`. - Item (`main` `
      • `): roving `tabindex` (`0` for the focused or active item, `-1` otherwise) and `aria-roledescription` from `ariaRoleDescription`. - A visually hidden `aria-live="polite"` region announces keyboard pick-up, move, drop, cancel, and selection actions. Customize the wording with `messages` (or the `messages` argument of `createSortableGroup` for connected lists). ## Exports ### Types ```ts interface SortableProps extends Omit, "children" | "aria-label" | "onpointerdown" | "onpointerover" | "onpointerout" | "onpointerenter" | "onpointerup" | "onkeydown"> { items: T[]; // bindable plain array item: Snippet<[T, SVSVariant, Attachment]>; // Snippet<[value, variant, dragHandle]> key?: (item: T) => PropertyKey; // identity extractor; required for object items clone?: (item: T) => T; // required for object items in clone mode group?: SortableGroupController; // shared controller from createSortableGroup() id?: string; // list id used by accept ghost?: Snippet<[T]>; // custom floating preview mode?: "move" | "clone" | "swap"; // ("move") accept?: string[] | ((fromId: string) => boolean); // undefined=any in group, []=none sort?: boolean; // enable sorting within the same list (true) multiple?: boolean; // enable multi-select followers (false) draggable?: boolean; // whole item is draggable when no handle is attached (true) appendable?: boolean; // append when entering the list area (false) confirm?: boolean; // hover delay before committing (false) dragging?: boolean; // bindable group drag activity messages?: Partial; // live-region announcement formatters ariaLabel?: string; ariaRoleDescription?: string; // ("Sortable Item") styling?: SVSClass; variant?: SVSVariant; // (VARIANT.NEUTRAL) // other ul attributes are passed to
          via ...rest; `class` is merged onto the list // ariaLabel is the recommended accessible-name prop; aria-labelledby may be passed via ...rest } type SortableReqdProps = "items" | "item"; type SortableBindProps = "items" | "dragging"; type SortableMode = "move" | "clone" | "swap"; interface SortableGroupController extends SVSContext { readonly identity: symbol; readonly dragging: boolean; readonly activeKey: string; readonly confirmKey: string; readonly keyboardActive: boolean; readonly message: string; readonly messageOwnerId: string; readonly messages: SortableMessages; readonly shadow: SortableShadowState; readonly send: TransitionFn; readonly receive: TransitionFn; readonly tp: TransitionParams; register(member: SortableMember): () => void; prepare(member: SortableMember, key: string, ev: PointerEvent, el: HTMLElement, followers: string[]): boolean; setGhost(ghost: boolean, ev?: PointerEvent): void; move(ev: PointerEvent): void; commit(): void; cancel(): void; grabKeyboard(member: SortableMember, key: string, followers: string[]): boolean; stepKeyboard(dir: -1 | 1): string; dropKeyboard(): void; cancelKeyboard(): void; announce(message: string, ownerId: string): void; over(member: SortableMember, key: string): void; leave(): void; enterGroup(member: SortableMember): void; itemVariant(key: string, selected: boolean, base: SVSVariant): SVSVariant; } interface SortableMessages { grabbed(key: string, index: number, total: number): string; atPosition(index: number, total: number): string; moved(index: number, total: number): string; dropped(index: number, total: number): string; cancelled(index: number, total: number): string; selected(key: string, count: number): string; deselected(key: string, count: number): string; } ``` ### Others ```ts function createSortableGroup( presentation?: { get variant(): SVSVariant; get styling(): SVSClass | undefined; }, motion?: { duration?: number; easing?: EasingFunction; }, messages?: Partial, ): SortableGroupController; ``` ```svelte {#snippet item(value)} {value} {/snippet} {#snippet item(value)} {value} {/snippet} ``` ## Examples The example code uses Tailwind CSS along with `SVSClass`. ### Basic **Code** ```svelte src=SortableBasic.svelte {#snippet item(value)} {value} {/snippet} ``` ### With Interactive Variant **Code** ```svelte src=SortablePract.svelte {#snippet item(name)} {@render inner(name)} {/snippet} {#snippet inner(name: string)}
          {fruits.get(name)?.img} {name}
          {/snippet} ``` ### With Drag Handle Only the attached handle starts a drag when `draggable={false}`. **Code** ```svelte src=SortableHandle.svelte {#snippet item(value, variant, handle)}
          :: {value.label}
          {/snippet}
          ``` ### Multiple Selection Clicking items selects them, and dragging one selected item moves the selection together. **Code** ```svelte src=SortableMultiple.svelte

          Click items to select them, then drag one selected item to move the selection together.

          {#snippet item(value)} {value.label} {/snippet}
          ```