Markdown:
Components

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.

Svelte
<Sortable {...props} />

Anatomy

The class attribute values represent part names. Other attributes represent their corresponding props.

Svelte
<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

NameTypeDefaultDescription
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
groupSortableGroupController-Shared controller from createSortableGroup(...) for connected lists
idstring-List id used by accept
ghostSnippet-Custom floating preview rendered while dragging
mode<enum>"move"move moves items, clone copies to the target, and swap swaps with the target item
acceptstring[]-Accepted source list ids; omitted accepts any list in the group, [] accepts none
sortbooleantrueEnables sorting within the same list
multiplebooleanfalseEnables click selection; dragging a selected item carries the other selected items as followers
draggablebooleantrueAllows the whole item to start a drag when no custom handle is attached
appendablebooleanfalseAllows items to append when entering the list area, including empty lists
confirmbooleanfalseAdds a hover delay before committing a move
draggingboolean-Bindable group drag activity
messagesPartial<SortableMessages>-Live-region announcement formatters for keyboard actions; ignored when a group controller supplies its own
ariaLabelstring-Accessible label for the list
ariaRoleDescriptionstring"Sortable Item"aria-roledescription applied to each item
stylingSVSClass-Styling override
variantSVSVariantNEUTRALBase 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

KeyDescription
ArrowUp / ArrowDownMoves focus to the previous or next item; while an item is picked up, moves the picked-up item
Space / EnterPicks up the focused item, or drops the picked-up item
EscapeCancels the pick-up and returns the item to its original position
Ctrl + SpaceToggles selection of the focused item (when multiple is true)

ARIA Roles and Attributes

The component applies these automatically:

  • List (whole <ul>): aria-label from ariaLabel.
  • Item (main <li>): 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

TypeScript
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

TypeScript
function createSortableGroup(
  presentation?: {
    get variant(): SVSVariant;
    get styling(): SVSClass | undefined;
  },
  motion?: {
    duration?: number;
    easing?: EasingFunction;
  },
  messages?: Partial<SortableMessages>,
): SortableGroupController;
Svelte
<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

Preset for Demo
Enter variant as you like
  • 🍎 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