> man sorta11y
Sorting,
out loud.
A vanilla-JS sortable list that works with a mouse, a keyboard, and a screen reader — out of the box. Every move is spoken. No framework, no dependencies.
v0.1.0-alpha — pre-release: install via the alpha tag, or
grab
src/sorta11y.js from GitHub.
Playground
// PLAYGROUND — FLIP THE SETTINGS, WATCH THE CODE
every move is spoken through the live region
Basic list
Sorta11y.create()Plain items, no handle — the whole item is the grab target. Toggle animation, add a grabbedClass / draggingClass state hook, or turn off clickToGrab so the pointer can only drag — a tap no longer picks up.
▸ spoken: —
Drag handle
handle: "…"A real <button> braille handle (⠿) becomes the grab target — the recommended path for full screen-reader support. Turn it off and the whole item becomes the grab target instead. Or keep the handle and add dragOnItem: pointer drags and taps may then start anywhere on the item, while keyboard and screen readers keep working through the handle. On touch and pen, the body stays tap-only so long lists still scroll — add dragOnItemTouch to make the whole item a finger/stylus drag surface too (best for short, unscrollable lists).
▸ spoken: —
From <select multiple>
Sorta11y.fromSelect()Point fromSelect at a <select multiple> and it builds an accessible sortable list, hides the select but keeps it submittable, and mirrors the new order back. Choose which side the handle sits on with handlePosition.
▸ spoken: —
Announcements & i18n
labels / localeEvery move is spoken through one polite live region. Switch the language and the same interactions announce in English or German — the real locale option, swapped live.
TAB focus · SPACE grab · ↑↓ move · HOME/END jump · SPACE drop · ESC cancel
▸ spoken: —
// WHY
One list, done well — for everyone.
Popular drag-and-drop libraries were built mouse-first; a keyboard or screen-reader user was often left stranded. sorta11y treats those paths as first-class — the same grab / move / drop model, spoken every step of the way.
It grew out of a real-world form integration at ConfTool, where those paths were exercised hands-on with a screen reader rather than against a checklist.
| Capability | Typical drag lib | sorta11y |
|---|---|---|
| Reorder with the keyboard | partial | yes |
| Screen-reader announcements | no | yes |
| Single-pointer (tap) path · WCAG 2.5.7 | no | yes |
| Zero runtime dependencies | partial | yes |
| No build step | no | yes |
| i18n announcement labels | no | yes |
// KEYBOARD MODEL
Grab · move · drop.
| Tab / Shift+Tab | move focus between items |
|---|---|
| Space | pick up the focused item, or drop it |
| Enter | same as Space on a <button> handle (its native activation) — plain items stay Enter-inert |
| ↑ / ↓ | move the held item one position |
| Home / End | jump the held item to start / end |
| Esc | cancel and restore the original order |
// QUICKSTART
Progressive enhancement.
The server renders a plain list; sorta11y enhances it in place. No build step — one script, one stylesheet, and go.
Label precedence, lowest to highest: built-in English → setDefaultLabels → { locale } → { labels }.
What you get
Zero deps · no build
One hand-written file. No framework, no bundler, no runtime deps.
Grab · move · drop
The same model for mouse, touch, and keyboard.
One polite live region
Every reorder is spoken — drops and cancels alike.
Focus stays put
Focus stays on the moved item — the same element — after keyboard, pointer and programmatic reorders, so it never falls to <body>.
Reduced-motion aware
Respects prefers-reduced-motion — instant, no slide.
i18n locales
Swap announcement languages, or supply your own labels.