Skip to content

Sortable

Sortable owns an ordered List<string> of stable keys and re-renders into the new order on every reorder; the consumer iterates the same list to render one Item per key. Pointer drag visuals are painted by the engine straight onto the DOM, so they never fight a C# re-render, while a keyboard reducer (grab, move live, drop, cancel) implements the WAI-ARIA APG grab-and-move model and announces each transition through a visually-hidden live region. It can be controlled (@bind-Values) or uncontrolled (DefaultValues), and fires OnReorder with the old and new index on every committed reorder.

@using Navius.Primitives.Components.Sortable
<NaviusSortable>
<NaviusSortableItem>
<NaviusSortableItemHandle />
</NaviusSortableItem>
</NaviusSortable>

Owns the key order, the keyboard grab/roving state and the drag engine. Renders a div with role="list" plus a visually-hidden live region. Values is two-way bindable.

Prop Type Default Description
Values IReadOnlyList<string>? - The ordered item keys. Pair with ValuesChanged (@bind-Values); the consumer iterates this list to render items.
ValuesChanged EventCallback<IReadOnlyList<string>> - Fires when the controlled Values should change.
DefaultValues IReadOnlyList<string>? - Uncontrolled initial order, seeding the internal list when Values is not supplied.
Orientation SortableOrientation Vertical The reorder axis (Vertical, Horizontal, or Grid), driving the engine’s midpoint math.
Disabled bool false Disables the whole list.
OnReorder EventCallback<SortableReorderEventArgs> - Fires once per committed reorder with the old and new index (OldIndex, NewIndex), from both pointer and keyboard moves.
ChildContent RenderFragment? - The sortable’s items.
Attributes IDictionary<string, object>? - Forwarded to the rendered div.

One reorderable row: the drag target and the roving keyboard focus target. Value is the stable key. Renders a div with role="listitem".

Prop Type Default Description
Value string - (required) The stable key that identifies this item in the sortable’s order.
Label string? Value Accessible name used in live-region announcements.
Disabled bool false Disables this row; skipped by roving keyboard navigation.
ChildContent RenderFragment? - Row content.
Attributes IDictionary<string, object>? - Forwarded to the rendered div.

An optional drag grip; its presence scopes pointer drag to the handle. aria-hidden since keyboard reordering acts on the whole item, not the handle. Renders a span.

Prop Type Default Description
ChildContent RenderFragment? - Handle content (e.g. a grip glyph).
Attributes IDictionary<string, object>? - Forwarded to the rendered span.
Part Attribute Description
Root data-orientation "vertical" | "horizontal" | "grid".
Root data-disabled Present when disabled.
Root data-dragging Present while a drag or keyboard grab is active anywhere in the list.
Item data-dragging Painted by the engine on the item currently being dragged.
Item data-drop-target Painted by the engine on the hovered drop slot.
Item data-keyboard-grabbed Present while this item is grabbed for keyboard reordering.
Item data-disabled Present when the row or the whole list is disabled.
Key Behavior
Space / Enter Grabs the focused item, or drops a grabbed item at its new position.
Arrow keys While grabbed, moves the item live; otherwise moves the roving focus.
Home / End Moves the item (or focus) to the first / last position.
Escape Cancels a grab and restores the original order.

Follows the APG grab-and-move pattern. The Root carries aria-disabled when Disabled. Each Item is a roving tab stop with aria-roledescription="sortable item"; grabbing sets aria-grabbed="true" and data-keyboard-grabbed. Every grab, move, drop and cancel is announced through a role="status" polite live region. Cross-list transfer is not supported: the engine reports indices relative to a single container only. This mechanism is covered by the Playwright browser test suite and gated by axe-core in CI.