Selector
One mode of two to five: latching keys in a tray, one down at a time, each with a lamp that lights when chosen.
Kit part · Forms · Radio groups · Toggles & switches
A mode switch made of cloth. A row of tiles covered in stretched fabric stands in a machined tray, each with an icon and a word pressed into it. The chosen tile sits a touch lower and a lamp behind it lights its icon and word through the weave. Choose another and the first lamp fades down as the next comes up. Underneath it is a group of native radios, so it posts one value without any script.
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 | Chosen · not | Hover | Focus | Disabled |
|---|---|---|---|---|
| Twill | ||||
| Speaker |
Graphite
| Part | Chosen · not | Hover | Focus | Disabled |
|---|---|---|---|---|
| Twill | ||||
| Speaker |
| Name | Kind | Values | What it does |
|---|---|---|---|
| fieldset.tui-cloth-selector | element | role="radiogroup" | The selector. Named by its legend, or by aria-label when there is none. |
| data-lamp | attribute | "filament" · "green" · "amber" · "red" · "blue" · "white" | The lamp behind the cloth. Filament, a warm white, by default. |
| --own-lamp | class | with --lc-lamp | Your own lamp: set --lc-lamp to one hex and the pale core is mixed from it. Leave data-lamp off. |
| --aurora | class | lamp: 'aurora' · aurora: { pattern, colors, seed, speed } | The one lamp of more than one colour: a <tui-aurora> in every tile’s glow, a slow field of borealis, ember, lagoon, prism or mono, or two to five hex colours of your own. clothSelectorHTML writes it; the same seed is always the same aurora. Only the lit tile’s field moves. |
| paused | attribute | on an aurora selector | Holds the aurora where it is. Toggle it from a Pause key of your own, a real button with aria-pressed (the recipe is under Markup): an aurora drifts for longer than five seconds, so a way to stop it is part of using one. No key is built in. |
| data-weave | attribute | "twill" · "speaker" | The cloth. A fine diagonal twill by default, felt more than seen; speaker cloth is a coarser weave that shows the light as pinholes. |
| --compact | class | icons only | Square 44px tiles with the icon alone. The word stays in the label, for a screen reader, and shows as a tooltip on hover and focus. |
| --block | class | full width | The tray fills its width and the tiles share it. |
| legend.tui-cloth-selector__legend | element | printed | The name above the tray, like the legend over a mode switch. |
| label.tui-cloth-selector__tile | element | one per mode | A tile: a native radio and the cloth face that draws it. Two to six of them. |
| input.tui-cloth-selector__input | element | type="radio" | The radio. Give every one in a selector the same name; checked marks the mode that is lit, disabled one that cannot be chosen. |
| .__face · __scrim · __glow | class | inside a tile | The cloth over its block, the shade under the lit icon and word, and the light scattered in the fibres. |
| .__icon · __g--off · __g--bloom · __g--core | class | aria-hidden | The icon, three times: pressed into the cloth, and the bloom and the core of the lamp. |
| .__word[data-t] | class | the name | The word, which names the radio. data-t repeats it for the two lit copies, which are not read out. |
| --cs-w · --cs-h | custom property | length | A tile’s width at most and its height: 80px and 72px, 44px square when compact. |
| --lc-* | custom property | the material | The lit cloth: --lc-cloth, --lc-twill, --lc-speaker, --lc-deboss and the lamp cores, set per finish; --lc-lamp for your own. |
| tui-cloth-select | event | detail.value · name · label | From the fieldset whenever the mode changes, by pointer, touch or keyboard. Bubbles. Needs kit.mjs. |
| Home · End | keyboard | on a tile | The first or last mode that can be chosen. Needs kit.mjs; the arrow keys are the browser’s own. |
| clothSelectorHTML · clothLamp · clothWeave · clothSelectorEdge | function | kit.mjs | The markup, written for you, and the rules under it, pure: which lamp a value means, which weave, and where Home and End go. |
<fieldset class="tui-cloth-selector" role="radiogroup" data-lamp="filament" data-weave="twill">
<legend class="tui-cloth-selector__legend tui-label">Pager</legend>
<div class="tui-cloth-selector__tray">
<label class="tui-cloth-selector__tile">
<input class="tui-cloth-selector__input" type="radio" name="pager" value="silent">
<span class="tui-cloth-selector__face">
<span class="tui-cloth-selector__scrim"></span>
<span class="tui-cloth-selector__glow"></span>
<span class="tui-cloth-selector__icon" aria-hidden="true">
<svg class="tui-cloth-selector__g tui-cloth-selector__g--off" viewBox="0 0 16 16">…</svg>
<svg class="tui-cloth-selector__g tui-cloth-selector__g--bloom" viewBox="0 0 16 16">…</svg>
<svg class="tui-cloth-selector__g tui-cloth-selector__g--core" viewBox="0 0 16 16">…</svg>
</span>
<span class="tui-cloth-selector__word" data-t="Silent">Silent</span>
</span>
</label>
<label class="tui-cloth-selector__tile">
<input class="tui-cloth-selector__input" type="radio" name="pager" value="push" checked>
…
</label>
<!-- two to six tiles -->
</div>
</fieldset>import { clothSelectorHTML } from '/kit/kit.mjs'; // also adds Home, End and the event
// The markup above, written for you. The icon is a kit icon by name, or your own paths on the 16 grid.
form.insertAdjacentHTML('afterbegin', clothSelectorHTML({
name: 'pager', legend: 'Pager', value: 'push',
options: [
{ value: 'silent', label: 'Silent', icon: 'moon' },
{ value: 'digest', label: 'Digest', icon: 'mail' },
{ value: 'push', label: 'Push', icon: 'bell' },
{ value: 'call', label: 'Call', svg: '<rect x="4.5" y="2" width="7" height="12" rx="1.6"/>' },
],
lamp: 'filament', // 'green' · 'amber' · 'red' · 'blue' · 'white' · one hex: '#6fe0c8' · or 'aurora',
// with aurora: { pattern: 'borealis', seed: 7 }
weave: 'twill', // or 'speaker'
}));
form.addEventListener('tui-cloth-select', (e) => {
e.detail.value; // 'push'
e.detail.label; // 'Push'
});
new FormData(form).get('pager'); // 'push', with or without the script
// An aurora drifts for longer than five seconds, so give it a way to stop (WCAG 2.2.2).
// The selector takes a "paused" attribute; the key is yours, a real button with aria-pressed.
// There is no built-in key. The kit hides one marked data-cs-pause under reduced motion,
// where the aurora is already one still frame.
// <button type="button" class="tui-key tui-key--sm" aria-pressed="false" data-cs-pause>Pause</button>
const key = form.querySelector('[data-cs-pause]');
key.addEventListener('click', () => {
const held = form.querySelector('.tui-cloth-selector').toggleAttribute('paused'); // true while held
key.setAttribute('aria-pressed', String(held));
key.textContent = held ? 'Resume' : 'Pause';
});Needs kit.css and kit.mjs. How to install the kit.
role="radiogroup", named by its legend. Only the chosen radio is in the tab order, the arrow keys move and choose, a form posts one value and nothing needs a script.paused attribute. The selector does not draw one itself.Each tile is a native radio inside its label, drawn by the span after it, so every state is a sibling selector on :checked and needs no script. The cloth is the lit-cloth recipe from the Cloth Fader, in its --lc-* names: a four-pixel twill tile, a heather in soft-light and the grey of the cloth, with a shading layer over everything for the fabric pulled round the edges of the block. Behind it each tile has its own lamp in three layers, all plus-lighter: a bloom scattered in the fibres, the icon blurred in the lamp’s colour, and the icon again in the pale core, masked by the weave so the fibres cross the light. The word is lit the same way, from two copies of itself in ::before and ::after whose alternative text is empty. Unlit, the icon and word are pressed in: a darker shade of the cloth on aluminium and a paler one on graphite, with a shaded upper wall and a lit lower lip. Changing the mode is an opacity transition per tile, a little slower going out than coming on, so one lamp dims as the next warms. The tile drops 1.5px and loses its skirt. Corners are concentric: an 8px tile in a 6px margin, a 14px tray. An aurora lamp is the kit’s shared field in the glow: three to five soft blobs, each a radial gradient on its own layer, drifting on the compositor, shaped by the glow’s own falloff. On graphite it is added as light. On the pale aluminium cloth added light washes to white, so its colours are lifted a fifth toward white and laid on with hard-light, which lifts the channels a colour has and deepens the others: the lit tile stays a colour and is still brighter than the unlit ones. The icon and word are lit near white, tinted by the field’s white point.
For one mode of a few, when the mode is a state of the machine and worth lighting.
7 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.
One mode of two to five: latching keys in a tray, one down at a time, each with a lamp that lights when chosen.
A row of keys in a tray with one pressed down: pick a view, a range or a mode and it applies at once. It is the kit’s selector without the lamps, with sizes, a full-width row and icon segments.
One choice from a short list in a form: small round keys in a fieldset, the chosen one pressed with a printed dot.