Key
Every button, and every link that acts like one: a cap on a skirt that drops below the plate when pressed.
A key for work that takes time and must not run twice. It latches below the plate while the work runs, an LED matrix in its face scanning amber, and says how it ended.
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
| Part | Idle | Hover | Pressed | Busy | Done | Failed | Focus | Cover lifted |
|---|---|---|---|---|---|---|---|---|
| Contrast | — | |||||||
| Plain | — | |||||||
| Guarded | — |
Graphite
| Part | Idle | Hover | Pressed | Busy | Done | Failed | Focus | Cover lifted |
|---|---|---|---|---|---|---|---|---|
| Contrast | — | |||||||
| Plain | — | |||||||
| Guarded | — |
| Name | Kind | Values | What it does |
|---|---|---|---|
| <tui-commit> | element | wraps one kit key | Builds the cut, the matrix, the legends and a polite live region around the <button> it is given. |
| <button class="tui-key"> | element | any size, --primary, --danger | The key, kept real. Its text is the idle legend. |
| data-busy · data-done · data-failed | attribute | "Working…" · "Done" · "Failed" | The legend for each state. All four share one cell, so the key never changes width. |
| data-state | attribute | idle · busy · done · failed | Where it is. Written by the part; set it in markup only to show a state. |
| data-rest | attribute | 2400 (ms) | How long done stays before the key goes idle; failed stays twice as long. |
| data-hold | attribute | — | Done and failed stay until the next press or reset(). |
| data-guard | attribute | ms, 6000 | A hinged cover over the cut: the first press lifts it, the second commits; it drops after this long unused. |
| data-why | attribute | text | The reason a run failed, printed under the key. Written from the rejection’s message. |
| data-cover | attribute | "closed" · "open" | The guard’s cover. Written by the part. |
| commit | event | detail.wait(promise) | Fired on a press. Hand wait() the work’s promise and the key runs on it; hand it nothing and it was an ordinary press. |
| .run(work) | method | promise or function | Run on work directly (skips the guard). Resolves with its value; rejects with its error once the key has said what failed. A second call while busy returns the same promise. |
| .reset() · .state · .key | property | — | Back to idle now; read the state; reach the button. |
| Enter · Space | keyboard | — | Native: press the key. While busy nothing happens and focus stays. |
| Escape | keyboard | guarded | Drops a lifted cover. |
| .is-hover · .is-down · .is-focus | class | key · key · host | Forced states, for documentation and specimens only. |
<tui-commit data-busy="Deploying…" data-done="Deployed" data-failed="Deploy failed">
<button class="tui-key tui-key--primary tui-key--lg" type="button">Deploy to production</button>
</tui-commit>
<tui-commit data-guard data-busy="Deleting…" data-done="Deleted" data-failed="Delete failed">
<button class="tui-key tui-key--lg tui-key--danger" type="button">Delete workspace</button>
</tui-commit>import '/kit/kit.mjs'; // defines <tui-commit>
const deploy = document.querySelector('tui-commit');
// Hand it the real work: busy lasts exactly as long as the promise.
deploy.addEventListener('commit', (e) => e.detail.wait(api.deploy('main')));
// Or run it yourself. A rejection's message is what the key prints.
try {
await deploy.run(() => api.deploy('main')); // "Deployed"
} catch (err) {
// The key already says "Deploy failed — " + err.message
}Needs kit.css and kit.mjs. How to install the kit.
<button>. While busy it takes aria-busy="true" and aria-disabled="true" (not disabled) so focus stays on it and a screen reader hears that it is working.aria-hidden); the words carry the state, and each state has its own lamp colour as well as its own pattern: scan, tick, cross.The key is the kit key, unchanged, standing in its own cut: a recess 3px bigger than the cap whose lower lip sits where the skirt meets the plate. Busy moves the cap its skirt plus 2px (past a normal press, under the lip) and deepens the shading from the cut’s upper wall. The matrix is sixteen dots behind a small dark window in the cap, each with a lit layer whose opacity alone animates, delayed by its diagonal, so the amber wave runs corner to corner; done and failed light fixed patterns in green and red. The four legends share one grid cell, so the widest sets the key and a swap only fades. Done springs the cap back up 1px past rest; failed pops it straight back. The guard is a smoked cover on a hinge barrel, turned about its top edge with a 3D rotateX.
Busy lasts exactly as long as the promise you hand it. Deletions get a cover to lift first.
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.
Every button, and every link that acts like one: a cap on a skirt that drops below the plate when pressed.
The only colour in the kit: a lamp that is on. Green is on, amber is waiting, red is recording or failing, blue is linked, white is standing by. Always next to words.
Progress through a task: ink filling a groove, never a coloured bar.