Skip to content
ShineUI
Install

OrderList

Reorder with clicks or drags.

<sui-order-list>DataStable

About

Reorderable list for priorities such as statement sections or dashboard cards. Three equal paths move a row: the top, up, down and bottom controls, pointer drag with an insertion indicator, and a keyboard lift. Drag is never the only path; dragOnly hides the controls but the keyboard lift stays.

Usage

<sui-order-list heading="Statement sections" height="200"></sui-order-list> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-order-list"); el.items = [ { value: "income", label: "Income" }, { value: "cost-of-goods-sold", label: "Cost of goods sold" }, { value: "expenses", label: "Expenses" }, { value: "other-income-and-expense", label: "Other income and expense" }, ]; /* SuiReorderItem[] */ el.addEventListener("suiSelectionChange", (e) => console.log(e.detail)); </script>
import { SuiOrderList } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const items = [ { value: "income", label: "Income" }, { value: "cost-of-goods-sold", label: "Cost of goods sold" }, { value: "expenses", label: "Expenses" }, { value: "other-income-and-expense", label: "Other income and expense" }, ]; /* SuiReorderItem[] */ return <SuiOrderList heading="Statement sections" height="200" items={items} onSuiSelectionChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiOrderList } from "@shineui/vue"; import "@shineui/core/shineui.css"; const items = [ { value: "income", label: "Income" }, { value: "cost-of-goods-sold", label: "Cost of goods sold" }, { value: "expenses", label: "Expenses" }, { value: "other-income-and-expense", label: "Other income and expense" }, ]; // SuiReorderItem[] const onChange = (e) => console.log(e.detail); </script> <template> <SuiOrderList heading="Statement sections" height="200" :items="items" @sui-selection-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiOrderList } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiOrderList], template: `<sui-order-list heading="Statement sections" height="200" [(selection)]="selection" [items]="items"></sui-order-list>`, }) export class Example { selection: SuiKey[] = []; items = [ { value: "income", label: "Income" }, { value: "cost-of-goods-sold", label: "Cost of goods sold" }, { value: "expenses", label: "Expenses" }, { value: "other-income-and-expense", label: "Other income and expense" }, ]; // SuiReorderItem[] }

Basic

Rows are options in a listbox with one tab stop, on the selected row or else the first. Nothing is selected until the reader picks a row, so the four controls wait for one. Every keyboard move is announced in a live region, so the whole reorder works without a pointer.

<div style="max-width:380px"><sui-order-list heading="Statement sections" height="200"></sui-order-list></div>
// setup(root): properties and events setup(root) { root.querySelector('sui-order-list').items = statementSections(); }

Rows with detail

A row takes the fields a sui-list row draws: description under the label, meta at the end in tabular figures, and a disc from severity, icon, avatar or initials. The filter reads the description and the meta too.

<div style="max-width:420px"><sui-order-list id="ol-detail" heading="Month-end pack" height="260"></sui-order-list></div>
// setup(root): properties and events setup(root) { root.querySelector('#ol-detail').items = reports(); }

Checkbox selection

selection-mode="checkbox" puts a box on every row under a select-all header, and the four controls carry the whole ticked set at once, keeping its order. Space ticks the focused row and Enter takes over the lift, so the arrow keys still belong to the list.

Nothing ticked.
<div style="display:grid;gap:10px;max-width:380px"> <code id="ol-check-log" style="font:460 12px/1.6 var(--font-sans);color:var(--ink-muted)">Nothing ticked.</code> <sui-order-list id="ol-check" selection-mode="checkbox" heading="Statement sections" height="200"></sui-order-list> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#ol-check'); const log = root.querySelector('#ol-check-log'); el.items = statementSections(); el.addEventListener('suiSelectionChange', (e) => { log.textContent = e.detail.length ? `Ticked: ${e.detail.join(', ')}.` : 'Nothing ticked.'; }); }

Filter

The filter box narrows what the list draws; the items array never changes. A move is defined against the whole list, so while the box has text the four controls, the pointer drag and the keyboard lift all wait. Ticking rows does not.

<div style="max-width:380px"><sui-order-list id="ol-filter" filter selection-mode="checkbox" heading="Ledger columns" height="220"></sui-order-list></div>
// setup(root): properties and events setup(root) { root.querySelector('#ol-filter').items = ledgerColumns(); }

Drag only, controls only

drag-only hides the four controls, and no-drag removes the pointer drag and the insertion indicator for a touch surface where a long press already means something else. The keyboard lift stays in both.

<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:640px"> <sui-order-list id="ol-drag" drag-only heading="Drag only" height="200"></sui-order-list> <sui-order-list id="ol-static" no-drag heading="Controls only" height="200"></sui-order-list> </div>
// setup(root): properties and events setup(root) { root.querySelector('#ol-drag').items = dashboardCards(); root.querySelector('#ol-static').items = dashboardCards(); }

Empty and loading

An empty list reads "Nothing here yet.", or empty-message when set; a filter that finds nothing reads empty-filter-message, a second state with its own line. loading holds the controls and puts one loading row in the list while the items are on their way.

<div style="display:grid;gap:16px;max-width:380px"> <sui-order-list id="ol-empty" heading="Dashboard cards" empty-message="Add a card to arrange the dashboard." height="120"></sui-order-list> <sui-order-list id="ol-nomatch" heading="Ledger columns" filter empty-filter-message="No column by that name." height="160"></sui-order-list> <sui-order-list heading="Statement sections" height="120" loading></sui-order-list> </div>
// setup(root): properties and events setup(root) { root.querySelector('#ol-empty').items = []; root.querySelector('#ol-nomatch').items = ledgerColumns(); }

Events

suiSelectionChange carries the new selection, the selected values, one in single mode: values, not positions, so a reorder does not stale it. suiItemsChange carries the whole reordered list after a control, a drop or a keyboard move. Neither bubbles: listen on the element.

Nothing selected.
<div style="display:grid;gap:10px;max-width:380px"> <code id="ol-log" style="font:460 12px/1.6 var(--font-sans);color:var(--ink-muted)">Nothing selected.</code> <sui-order-list id="ol-events" heading="Statement sections" height="180"></sui-order-list> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#ol-events'); const log = root.querySelector('#ol-log'); el.items = statementSections(); el.addEventListener('suiSelectionChange', (e) => { log.textContent = e.detail.length ? `Selected ${e.detail[0]}.` : 'Nothing selected.'; }); el.addEventListener('suiItemsChange', (e) => { log.textContent = `Order: ${e.detail.map((it) => it.label).join(', ')}`; }); }

Keyboard

KeyAction
TabSteps through the four reorder buttons, then the list. The list is one tab stop, at the row that has it, and in checkbox mode the select-all header is the stop before it.
SpaceIn single mode, lifts the focused row, and drops a lifted row where it is. In checkbox mode, ticks the focused row. On the select-all header, ticks every row the filter shows, or clears them.
EnterLifts the focused row, and drops a lifted row where it is, in both modes.
ArrowDown ArrowUpMoves the tab stop to the next or previous row, and the selection with it in single mode. With a row lifted, moves the row one place.
Home EndMoves to the first or last row. With a row lifted, moves the row to the top or bottom.
EscapeCancels a lift and returns the row to where it started.

API

<sui-order-list>

Properties

PropertyAttributeTypeDefaultDescription
dragOnlydrag-onlybooleanfalseHides the click controls; pointer drag and the keyboard lift remain. Reflected.
emptyFilterMessageempty-filter-messagestringstring | undefinedRow shown when the filter box narrowed the rows to none. An empty list and a search that found nothing are two states, so this is a second prop; defaults to "No matches.".
emptyMessageempty-messagestringstring | undefinedRow shown when items holds nothing. Defaults to "Nothing here yet.".
filterfilterbooleanfalseShows the filter box above the list. It narrows what is drawn (by label, description and meta), never the items array, and it holds the reorder paths until the box is empty again. Reflected.
headingheadingstringstring | undefinedHeading above the list, the settled name for a heading across the library.
heightheightnumber240Maximum list height in px before it scrolls.
itemsproperty onlySuiReorderItem[][]Rows in order, { value, label, description?, meta?, icon?, avatar?, initials?, severity? }, rendered as a listbox of option rows with a roving tab stop. value is the row's identity, unique in the list, so two rows with one label stay two rows. Mutable: every reorder writes the new order back and emits suiItemsChange. Property, not attribute.
loadingloadingbooleanfalseThe rows are still on their way: one loading row stands in for them, the controls are held and the list is aria-busy. Reflected, so a page can style the wait.
noDragno-dragbooleanfalseTurns off the pointer drag and its insertion indicator, for a touch surface where a long press already means something else. The controls and the keyboard lift stay. Reflected.
selectionproperty onlySuiKey[][]Values of the selected rows, in list order: one at most in single mode, any number in checkbox mode. Nothing is selected until the reader picks a row. Values, not positions, so a reorder never stales it; a value no row holds is ignored and dropped at the next change. Mutable; updated before suiSelectionChange fires. Property, not attribute.
selectionModeselection-modeSuiListSelectionMode"checkbox" | "single"'single'single picks one row at a time. checkbox draws a box on every row under a select-all header, and the four controls carry the whole ticked set at once. Spelled as on sui-tree, which names the same two ways of picking rows. Reflected.

Events

EventDetailReactVueDescription
suiItemsChangeSuiReorderItem[]onSuiItemsChange@sui-items-changeFires after every reorder (control click, drop, keyboard move); detail is the new items array. A state report: the list is not a form field, so a form around it never hears it. Does not bubble.
suiSelectionChangeSuiKey[]onSuiSelectionChange@sui-selection-changeFires when the selection changes; detail is the new selection, the selected rows' values in list order. Does not bubble.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the list's single tab stop (the selected row, else the first).

Types

SuiKey typeA record's or an item's key wherever the library identifies one: a row, a tree node, a list item, a selected value. Numbers stay numbers, so a numeric id round-trips through selection, the events and a framework binding without turning into a string.
No members.
SuiListSelectionMode typeHow a reorderable list picks rows: one at a time, or many through checkboxes under a select-all header. sui-order-list and sui-pick-list share it, and it is the same vocabulary sui-tree's selectionMode uses, minus the none a tree can offer and a reorder list cannot: the move controls act on a selection, so a list with no way to select is inert.
No members.
SuiReorderItem interfaceOne row of sui-order-list and sui-pick-list. value is the row's identity, which selection holds and which keeps two rows with one label apart; label is what the row reads. The optional fields are the ones a sui-list row draws: a supporting line, a trailing figure and a leading disc.
valueSuiKeyIdentity, unique in the list (in both panes of a pick list), kept as given.
labelstringThe row's text, and what the filter box, the announcements and a drag carry.
description?stringSupporting line under the label.
meta?stringTrailing text on the label line in tabular figures: a count, a date.
icon?stringFont Awesome class drawn in a neutral disc before the text.
avatar?stringImage URL for an avatar disc before the text; wins over severity and icon.
initials?stringLetters for an avatar disc without an image.
severity?SuiSeverityStatus disc before the text, on the shared ladder; wins over icon.
SuiSeverity type
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

Color29

--accentthe one chart highlight: the flagged series, down candles, the peak cell--accent-inkorange shaded for small text on light--bordercard + table outer borders--border-strongcontrol borders, emphasized rules--brand--brand-contrasttext and glyphs sitting on solid brand fills--control-borderthe resting edge of every field and control: 3:1 on its grounds--danger--danger-soft--disabled-bg--disabled-ink--focus-halo--focus-ring--ink--ink-faintmetadata beside body ink: 4.8:1 on the page grounds (5.4:1 in dark)--ink-mutedsecondary text: 7:1 on the page grounds and 1.5 times faint--ink-muted-on-washthe muted ink a selected, current or pressed row paints: 4.5:1 on that wash--ink-placeholdera field's placeholder, not a text tier: 4.5:1 on the page grounds--status-attention-bg--status-attention-inkOpen, Pending, In transit--status-neutral-bg--status-neutral-ink--status-positive-bg--status-positive-inkPaid, Cleared, Active--surfacecards, tables, inputs--surface-hovera hovered row or quiet control on any ground: the ink, translucent--surface-pressed--surface-selecteda selected row or option: the primary's hue, always heavier than a hover--surface-sunkentable headers, wells

Typography1

Ag--font-sans

Spacing and size4

42--control-lgform-page inputs34--control-mdDEFAULT toolbar/filter controls12--control-pad-x28--control-sm

Radius3

--radius-lgcards, tables, panels, dialogs--radius-mdALL controls: inputs, buttons, selects--radius-xscheckboxes, mini swatches

Elevation2

--focus-shadow--shadow-raisedhover-lift, dragged rows

Motion2

--duration-fasthovers, presses, every exit--ease-out

Every name above is listed with its value and its role in Foundations.

See also