Skip to the page
TACTILE UI

Kit part · Lists

Sortable list

Plates in a rack that you reorder. Each has a knurled handle: drag it and the plate lifts off the rack on a shadow while the others slide aside, or pick it up from the keyboard and walk it up and down. Every step is spoken.

Element
ol.tui-sortable
Needs
kit.css + kit.mjs
Rules
sortable.rules.md · 6
Finishes
Aluminium · Graphite · Porcelain · Black
Volumes
Tactile · Quiet
On this page
  1. Variants
  2. Every state, at once
  3. API
  4. Markup
  5. Accessibility
  6. How it’s built
  7. Rules
  8. Related parts
01

Variants

  1. BillingPlans and invoices
  2. TeamMembers and roles
  3. SecuritySign-in and keys
Two lines
  1. Billing
  2. Team
  3. Security
  4. Notifications
Titles only
  1. BillingPlans and invoices
  2. TeamMembers and roles
  3. SecuritySign-in and keys
Picked up · keyboard
  1. Quarterly security review for the whole platform teamOwner: Ada Okafor, due in three weeks
  2. Short oneFine
Long text keeps to one line
02

Every state, at once

Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.

Aluminium

PartAt restHandle hoverHandle focusPicked upLifted by the pointer
Plate
  1. Billing
  2. Team
  1. Billing
  2. Team
  1. Billing
  2. Team
  1. Billing
  2. Team
  1. Billing
  2. Team

Graphite

PartAt restHandle hoverHandle focusPicked upLifted by the pointer
Plate
  1. Billing
  2. Team
  1. Billing
  2. Team
  1. Billing
  2. Team
  1. Billing
  2. Team
  1. Billing
  2. Team
03

API

NameKindValuesWhat it does
ol.tui-sortableelementaria-labelThe tray. An <ol> or <ul>; its <li> children are the plates. Name it.
.tui-sortable__itemclassper plateA plate. data-id names it in the event’s order; without one its title is used.
.tui-sortable__gripelementbuttonThe knurled handle. A real button named “Move Billing”; if you leave the name off, script writes it from the title.
.tui-sortable__text · __title · __subclasswordsThe title and an optional second line, each kept to one line.
.tui-sortable__posclassengraved numberPrints the position from a CSS counter, so it follows the order as plates move.
.tui-lamp[data-lamp="amber"]elementoptionalLit while the plate is in hand. Decorative, so aria-hidden.
data-picked · .is-liftedattributeset by scriptA plate picked up from the keyboard, and one lifted by the pointer. Set them by hand to hold the state.
tui-reordereventdetail.item · from · to · orderAfter a drop that changed the order. Bubbles from the list.
sortableMove · sortableTarget · sortableSaysfunctionkit.mjsThe rules, pure: an item moved in a list, where a dragged plate belongs, and what to say.
Space · Enterkeyboardon a handlePicks the plate up; pressed again, drops it.
↑ ↓ · Home · Endkeyboardwhile picked upOne place up or down, or to an end.
Escapekeyboardwhile picked upPuts the plate back where it was.
dragpointeron a handleThe plate follows on a shadow and the others slide aside as it passes their middle. Works with a finger.
04

Markup

<p class="tui-muted" id="order-help">Space picks a plate up, the arrow keys move it,
  Space drops it, Escape puts it back.</p>
<ol class="tui-sortable" aria-label="Settings order">
  <li class="tui-sortable__item" data-id="billing">
    <button type="button" class="tui-sortable__grip"
            aria-label="Move Billing" aria-describedby="order-help"></button>
    <span class="tui-sortable__text">
      <span class="tui-sortable__title">Billing</span>
      <span class="tui-sortable__sub">Plans and invoices</span>
    </span>
    <span class="tui-sortable__pos" aria-hidden="true"></span>
    <span class="tui-lamp" data-lamp="amber" aria-hidden="true"></span>
  </li>
  …
</ol>
import '/kit/kit.mjs';   // wires every .tui-sortable on the page, now or later

document.querySelector('.tui-sortable').addEventListener('tui-reorder', (e) => {
  e.detail.from;    // where the plate was
  e.detail.to;      // where it is now
  e.detail.order;   // ['team', 'billing', 'security', …]: each data-id
  save(e.detail.order);
});

Needs kit.css and kit.mjs. How to install the kit.

05

Accessibility

  • Each handle is a real button with a name (“Move Billing”) and a description that says how to use it. The plate itself is not a drag target, so a tap on its words does nothing surprising.
  • The keyboard does everything a pointer does. Space picks up, the arrows move, Space drops, Escape puts it back; Home and End go to the ends.
  • Each step is spoken in an assertive live region with the plate’s name and its place: “Picked up Billing. Position 2 of 5.”
  • The handle keeps the focus and the pointer capture all the way, because the plates around it are the ones that move.
  • A picked-up plate is shown by its lit lamp and its lifted shadow, and aria-pressed on its handle says it too.
  • With reduced motion the others do not slide; they are in their new places at once, and the announcements are the same.
06

How it’s built

A tray cut into the plate with a rack of raised plates in it, each one a key’s cap laid flat. A 4px inset makes the handle’s 4px corner concentric with the plate’s 8px, and a 5px gap makes the tray’s 13px. The handle is a slot with vertical ridges cut across it, each a dark line and a lit one. Dragging moves the plate with a transform that follows the pointer; when its middle passes a neighbour’s, that neighbour is moved around it, which keeps the handle’s focus and capture, and every plate that moved is slid from where it was to where it is (a FLIP, with the Web Animations API). Dropping settles the plate from where the pointer left it. The order is the document’s: nothing is stored in script, and tui-reorder is how you find out.

07

Rules

sortable.rules.mdIn the brain

For an order that means something, like a running order or priorities. Not a column sort.

6 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.

08
  1. IntroPiano, room mics1:12
  2. LodeFull band4:05
  3. Night reelVocal and tape3:42

List

Rows in a rack: a mark, a title, a second line and a fact or a key at the end. Chosen rows sink into a recess with their check key latched; in a running order, a row lifts out of the rack on its skirt and slides past the others, from the keyboard or by its grip.

  • parts
  • calendar.mjs
  • carousel.mjs

File tree

Folders and files as a real tree, printed like a wiring diagram: traces with an elbow into every item, chevrons that turn, and the chosen file set in a recess with its lamp lit. Written as a plain nested list.

Key

Every button, and every link that acts like one: a cap on a skirt that drops below the plate when pressed.

Docs
Finish
Volume