Add this component

npx zentauri-ui add sortable-list

Accessibility notes

Keyboard accessible by default with semantic markup, visible focus treatment, and tokenized states. Add descriptive labels for icon-only or decorative usage.

Dependency notes

Check Installation for shared peers. This component keeps styling in Tailwind classes and the --zui-* token contract.

Data

Sortable list for drag-to-reorder interfaces

SortableList provides a small, dependency-free primitive for reordering application data with native drag events and accessible move controls.

  • Design tokens
  • Build component
  • Publish package

Sortable list playground

Choose a visual variant and item set, then drag rows or use the move buttons to reorder the list. The generated snippet updates with every selection.


  • Design tokens
  • Build component
  • Publish package

Sortable List API

Generated from the package prop types and variant definitions.

SortableList

SortableListProps

Variants

PropTypeDefault
appearance
amberbluecardcyandefaultemeraldghostglassgradient-bluegradient-greengradient-purplegreen+9
default
size
lgmdsm
md

Behavior

PropTypeDefault
defaultItemsreadonly T[] | undefinednone
disabledboolean | undefinednone
getItemId*(item: T, index: number) => stringnone
items*readonly T[]none
labelstring | undefinednone
onItemsChange((items: T[]) => void) | undefinednone
renderItem*SortableListRenderItem<T>none
showMoveButtonsboolean | undefinednone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLUListElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SortableListItem

SortableListItemProps

Variants

PropTypeDefault
appearance
amberbluecardcyandefaultemeraldghostglassgradient-bluegradient-greengradient-purplegreen+9
default
size
lgmdsm
md

Behavior

PropTypeDefault
draggingboolean | undefinednone

Content

PropTypeDefault
children*ReactNodenone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLLIElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SortableListItemVariant

SortableListItemVariantProps

Behavior

PropTypeDefault
appearance'default' | 'outline' | 'ghost' | 'card' | 'separated' | 'blue' | 'cyan' | 'green' | 'red' | 'slate' | 'purple' | 'pink' | 'orange' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-purple' | 'subtle' | 'glass' | 'amber' | 'primary' | null | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone

SortableListVariant

SortableListVariantProps

Behavior

PropTypeDefault
appearance'default' | 'outline' | 'ghost' | 'card' | 'separated' | 'blue' | 'cyan' | 'green' | 'red' | 'slate' | 'purple' | 'pink' | 'orange' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-purple' | 'subtle' | 'glass' | 'amber' | 'primary' | null | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone
CSS variable overrides

Sortable List CSS variables

Override these sortable-list variables on :root, a theme selector, or a component wrapper. Each appearance publishes its own border and background pair, the gradients add from/to stops plus a foreground so text on them stays themeable, and the drag handle has its own colour.

84 variables

Pattern: --zui-<component>-<slot?>-<variant?>-<property>-<state?>-dark?

:root {
  --zui-sortable-list-amber-bg: color-mix(in oklch,var(--zui-color-amber,#d97706) 8%,transparent);
  --zui-sortable-list-amber-border: #d97706;
  --zui-sortable-list-bg: white;
  --zui-sortable-list-blue-bg: color-mix(in oklch,var(--zui-color-blue,#2563eb) 8%,transparent);
  --zui-sortable-list-blue-border: #2563eb;
  --zui-sortable-list-border: oklch(92.9% 0.013 255.508);
  --zui-sortable-list-card-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
  --zui-sortable-list-cyan-bg: color-mix(in oklch,var(--zui-color-cyan,#0891b2) 8%,transparent);
  --zui-sortable-list-cyan-border: #0891b2;
  --zui-sortable-list-default-hover: oklch(96.8% 0.007 247.896);
  --zui-sortable-list-emerald-bg: color-mix(in oklch,var(--zui-color-emerald,#059669) 8%,transparent);
  --zui-sortable-list-emerald-border: #059669;
  --zui-sortable-list-ghost-hover: oklch(96.8% 0.007 247.896);
  --zui-sortable-list-glass-bg: rgba(255,255,255,0.6);
  --zui-sortable-list-glass-hover: rgba(255,255,255,0.8);
  --zui-sortable-list-gradient-blue-fg: #ffffff;
  --zui-sortable-list-gradient-blue-from: #2563eb;
  --zui-sortable-list-gradient-blue-to: #0891b2;
  --zui-sortable-list-gradient-green-fg: #ffffff;
  --zui-sortable-list-gradient-green-from: #16a34a;
  --zui-sortable-list-gradient-green-to: #65a30d;
  --zui-sortable-list-gradient-handle: #ffffff;
  --zui-sortable-list-gradient-purple-fg: #ffffff;
  --zui-sortable-list-gradient-purple-from: #9333ea;
  --zui-sortable-list-gradient-purple-to: #db2777;
  --zui-sortable-list-green-bg: color-mix(in oklch,var(--zui-color-green,#16a34a) 8%,transparent);
  --zui-sortable-list-green-border: #16a34a;
  --zui-sortable-list-handle: oklch(55.6% 0.043 257.417);
  --zui-sortable-list-item-border: oklch(92.9% 0.013 255.508);
  --zui-sortable-list-orange-bg: color-mix(in oklch,var(--zui-color-orange,#ea580c) 8%,transparent);
  --zui-sortable-list-orange-border: #ea580c;
  --zui-sortable-list-outline-hover: oklch(96.8% 0.007 247.896);
  --zui-sortable-list-pink-bg: color-mix(in oklch,var(--zui-color-pink,#db2777) 8%,transparent);
  --zui-sortable-list-pink-border: #db2777;
  --zui-sortable-list-primary-hover: #eff6ff;
  --zui-sortable-list-purple-bg: color-mix(in oklch,var(--zui-color-purple,#9333ea) 8%,transparent);
  --zui-sortable-list-purple-border: #9333ea;
  --zui-sortable-list-red-bg: color-mix(in oklch,var(--zui-color-red,#dc2626) 8%,transparent);
  --zui-sortable-list-red-border: #dc2626;
  --zui-sortable-list-slate-bg: color-mix(in oklch,var(--zui-color-slate,#475569) 8%,transparent);
  --zui-sortable-list-slate-border: #475569;
  --zui-sortable-list-subtle-bg: oklch(96.8% 0.007 247.896);
}

/* Dark theme variables follow the same names with -dark appended. */
.dark {
  --zui-sortable-list-amber-bg-dark: color-mix(in oklch,var(--zui-color-amber-dark,#fbbf24) 15%,transparent);
  --zui-sortable-list-amber-border-dark: #fbbf24;
  /* ...same variables with -dark at the end */
}

What it does

Rows use native HTML5 drag events and call onItemsChange whenever an item changes position.

Accessibility

Provide a meaningful label and keep the move buttons enabled for users who do not drag.

Common use cases

  • Prioritize tasks
  • Reorder navigation items
  • Arrange playlist tracks
  • Manage workflow steps

FAQ

Can I control the order externally?

Yes. Pass items and update them from onItemsChange to keep the list controlled.

Does it support keyboard users?

The up and down move buttons provide an accessible keyboard alternative to dragging.