Swapy
Rows you put in order by hand. A move word is the handle; the row you hold reverses out of ink and travels with you, or a proofreader's loop marks where it went.
Report an issue with SwapyExample
- Editor’s letterOpening
- HaldenIdentity
- TidewaterMotion
- MarramWeb
- ColophonClosing
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
Press move and use the arrow keys, or drag it. The issue opens with Editor’s letter and closes with Colophon.
- Slow harbour
- Northlight
- Tacet
- Oda
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
- Editor’s letterOpening
- HaldenIdentity
- TidewaterMotion
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
- Editor’s letterOpening
- HaldenIdentity
- TidewaterMotion
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
- Editor’s letterOpening
- HaldenIdentity
- TidewaterMotion
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
- Slow harbour
- Northlight
- Tacet
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
- Editor’s letterOpening
- HaldenIdentity
- TidewaterMotion
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
- رسالة المحرر
- هالدن
- الخاتمة
Press to pick the row up, then the arrow keys to move it, and press again to set it down. Escape puts it back.
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base and rows along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/swapy.jsonUsage
"use client"import * as React from "react"import { Swapy, type SwapyItem } from "@/components/ui/swapy"const issue: SwapyItem[] = [{ id: "letter", label: "Editor’s letter", kind: "Opening", meta: "2 pp" },{ id: "halden", label: "Halden", kind: "Identity", meta: "8 pp" },{ id: "tidewater", label: "Tidewater", kind: "Motion", meta: "6 pp" },{ id: "marram", label: "Marram", kind: "Web", meta: "4 pp" },{ id: "colophon", label: "Colophon", kind: "Closing", meta: "1 p" },]const set: SwapyItem[] = [{ id: "a", label: "Slow harbour", meta: "4:12" },{ id: "b", label: "Northlight", meta: "3:40" },{ id: "c", label: "Tacet", meta: "0:58" },{ id: "d", label: "Oda", meta: "5:06" },]const rtl: SwapyItem[] = [{ id: "a", label: "رسالة المحرر", meta: "2" },{ id: "b", label: "هالدن", meta: "8" },{ id: "c", label: "الخاتمة", meta: "1" },]export default function Example() {const [order, setOrder] = React.useState(issue.map((i) => i.id))const names = order.map((id) => issue.find((i) => i.id === id)?.label)return (<div className="grid w-full max-w-3xl" style={{ gap: "var(--ot-space-8)" }}><div className="grid" style={{ gap: "var(--ot-space-4)" }}><Swapy label="Running order" items={issue} order={order} onOrderChange={setOrder} /><p className="ot-pp" style={{ color: "var(--ot-graphite)" }}>Press <i>move</i> and use the arrow keys, or drag it. The issue opens with {names[0]} and closes with {names.at(-1)}.</p></div><div className="grid" style={{ gap: "var(--ot-space-4)" }}><span className="ot-label">Transpose</span><Swapy label="Set list" variant="transpose" items={set} /></div></div>)}const w = "w-[34rem] max-w-full"Contract
- Underneath: native buttons on
rows, plus a hook. - Anatomy:
<div class="ot-swap" data-variant="transpose">holding aot-rowslist (aria-label), each<li data-id data-held>holding.ot-swap-n(the place, aria-hidden),.ot-rows-title,.ot-rows-kind,.ot-rows-metaand<button class="ot-swap-move" aria-pressed>(the words move and moving in one cell, then.ot-swap-arrow); a hint and a polite status in.ot-sr. Transpose addssvg.ot-swap-arcand.ot-swap-tr, both aria-hidden.data-holdingon the root while a row is held;data-draggingwhile the pointer carries it. - Rows you put in order by hand. A word is the handle: "move ↕", graphite, the arrow pencil; pointing inks both and nudges the arrow. Press it and the row is held, the word says moving, the arrow keys carry it, and pressing again sets it down. Or drag the word: the held row goes to the place under your hand, read from the layout so nothing jitters, and is set down where you let go. There is no drag ghost: the row itself travels.
- Default, after "the uncreative": the held row reverses out of an ink block that prints across it from the inline start, the words turn to paper, and the name steps forward and sets heavier and tighter (300 to 560, -0.035em). Every row that changes place glides there (moderato, breath) and its number turns over the way it went, the numbers being data (01, 02 in pencil tabular figures, always left to right). Only the held row inks, so one block travels with your hand.
- Transpose, after the proofreader's transposition mark: no ink. The held name turns italic (yours now, at the expression scale on the row's own line height, so nothing reflows) and the others step back to pencil. In the margin (
--ot-swap-margin, space-7, space-6 below 40rem) a hairline loop runs from the place it was picked up to the place it is now, ending in an ink dot, with a pencil italic tr by its middle. Set down, the mark fades: the correction is made. - Screen readers hear each step once, politely: "Halden picked up, 2 of 5.", "Halden, 3 of 5.", "Halden set down, 3 of 5.", "Halden put back, 2 of 5.". The word's name says the row and its place.
- States: rest, pointed at (
data-force="hover"pins the first row's word), held, disabled (pencil, no handles). Right to left the block prints from the right, the loop hangs in the right margin and the figures still read left to right. Forced colours: the held row isHighlightwithHighlightText, the loopCanvasText. Reduced motion: rows and numbers change place without travel. - Keyboard on the word: Space or Enter picks up and sets down; Up and Down carry the row (picking it up if needed); Home and End take it to the ends; Escape puts it back; leaving the word sets it down.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
label | string | Names the list for screen readers: "Running order". | |
items | { id: string; label: string; kind?: ReactNode; meta?: ReactNode }[] | The rows. label is the name, set large, and what is said aloud as the row moves; kind is what it is, in graphite; meta is the quiet fact at the end (a length, a page count). | |
order | string[] | The ids in order, when you hold the order. An id missing from it keeps its place at the end; an id no longer in items is dropped. | |
defaultOrder | string[] | the order of items | The first order, when Swapy holds it. |
onOrderChange | (order: string[]) => void | Called on every move, with the new order. | |
variant | "default" | "transpose" | "default" | default: the row you hold reverses out of an ink block that prints across it, and its name sets heavier and tighter, after "the uncreative". transpose: the proofreader's transposition mark: no ink, the held name turns italic and the rest step back to pencil, and a hairline loop in the margin runs from where it was picked up to where it is, ending in a dot, with tr written by it. Set down, the mark fades. |
disabled | boolean | false | The rows stay where they are and step back to pencil. |
defaultHeld | string | Starts with this row picked up, as if its move word had just been pressed. | |
Keyboard | on the move word | Space or Enter picks the row up and sets it down. Up and Down carry it (and pick it up if it wasn't), Home and End take it to the ends, Escape puts it back where it was, and leaving the word sets it down. Each step is said: "Halden, 3 of 5." |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Swapy | "the uncreative" | The row you hold reverses out of ink and travels with your hand |
| Transpose swapy | The proofreader's transposition mark | A loop in the margin from where the row was to where it is, the held name in italic |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Swapy | Glide | Held, the ink prints across the row; the rows glide to their new places and their numbers turn over |
| Transpose swapy | Mark | The loop follows the held row through the margin, and fades when it is set down |