Sortable
A single list that allows users to reorder items through drag-and-drop interactions.
- Item 1
- Item 2
- Item 3
- Item 4
Usage
Use standalone, or inside SortableGroup to connect lists.
<Sortable {...props} />Anatomy
The class attribute values represent part names. Other attributes represent their corresponding props.
<ul class="whole" aria-label {...rest}>
{#each items as value (key(value))}
<li class="main" tabindex aria-roledescription>
{item(value, variant, handle)}
</li>
{/each}
</ul>
<span aria-live="polite"><!-- keyboard action announcements --></span>A position: fixed floating shadow is rendered while dragging.
The <li> 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 | <enum> | "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<SortableMessages> | - | 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 <ul> 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.
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<ul>):aria-labelfromariaLabel. - Item (
main<li>): rovingtabindex(0for the focused or active item,-1otherwise) andaria-roledescriptionfromariaRoleDescription. - A visually hidden
aria-live="polite"region announces keyboard pick-up, move, drop, cancel, and selection actions. Customize the wording withmessages(or themessagesargument ofcreateSortableGroupfor connected lists).
Exports
Types
interface SortableProps<T = string> extends Omit<HTMLAttributes<HTMLUListElement>, "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<SortableMessages>; // live-region announcement formatters
ariaLabel?: string;
ariaRoleDescription?: string; // ("Sortable Item")
styling?: SVSClass;
variant?: SVSVariant; // (VARIANT.NEUTRAL)
// other ul attributes are passed to <ul> 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<T>(member: SortableMember<T>): () => void;
prepare<T>(member: SortableMember<T>, key: string, ev: PointerEvent, el: HTMLElement, followers: string[]): boolean;
setGhost(ghost: boolean, ev?: PointerEvent): void;
move(ev: PointerEvent): void;
commit(): void;
cancel(): void;
grabKeyboard<T>(member: SortableMember<T>, key: string, followers: string[]): boolean;
stepKeyboard(dir: -1 | 1): string;
dropKeyboard(): void;
cancelKeyboard(): void;
announce(message: string, ownerId: string): void;
over<T>(member: SortableMember<T>, key: string): void;
leave(): void;
enterGroup<T>(member: SortableMember<T>): 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
function createSortableGroup(
presentation?: {
get variant(): SVSVariant;
get styling(): SVSClass | undefined;
},
motion?: {
duration?: number;
easing?: EasingFunction;
},
messages?: Partial<SortableMessages>,
): SortableGroupController;<script lang="ts">
import { Sortable, createSortableGroup } from "svseeds";
const group = createSortableGroup();
let left = $state(["Alpha", "Beta"]);
let right = $state(["Gamma"]);
</script>
<Sortable bind:items={left} {group}>
{#snippet item(value)}
{value}
{/snippet}
</Sortable>
<Sortable bind:items={right} {group}>
{#snippet item(value)}
{value}
{/snippet}
</Sortable>Examples
The example code uses Tailwind CSS along with SVSClass.
Basic
- Item 1
- Item 2
- Item 3
- Item 4
With Interactive Variant
- 🍎 apple
- 🍌 banana
- 🍊 orange
- 🍇 grape
- 🍑 peach
- 🍈 melon
With Drag Handle
Only the attached handle starts a drag when draggable={false}.
- :: Draft outline
- :: Review examples
- :: Publish notes
- :: Archive follow-up
Multiple Selection
Clicking items selects them, and dragging one selected item moves the selection together.
Click items to select them, then drag one selected item to move the selection together.
- Collect requirements
- Sketch states
- Implement demo
- Run checks