All studies

2026-10 · Navigation · GitHub

Mode dial

A selector you turn: choices round a knurled dial set in a cloth-dotted plate, carried one detent at a time under a mark that never moves. Underneath, it is a radio group.

Three ways to label it

The same radio group under each: names that stay upright, icons that say their name when reached for, and words engraved round the arc that turn with it.

Names · views for an archive. Arrow keys turn it a detent; the word under the mark is the choice.

.mode-dial

View

Icons · the same views. Hover or focus raises the name; on touch the chosen one keeps its name up.

.mode-dial--icons

View

Curled text · a dashboard’s time range, Day to All, read clockwise.

.mode-dial--curled

Range

With a display

The display reads what is under the mark, and what is under the pointer before you turn to it.

Range · hover a word to preview its span; turn to apply it.

.mode-dial--curled + __display

Range

Views · the screen says what each view is for before you pick it.

.mode-dial--icons + __display

View

A key in the middle

For a choice that costs something. Turning browses, the key equips, Escape turns it back.

Kit · a game’s item wheel. The mark is what you are looking at; the pip is what you are holding.

.mode-dial--icons + __display + __commit

Kit

Equipped:

At two sizes

The geometry is a share of the dial and the cloth is in pixels, so a small dial has fewer dots, not smaller ones.

Compact · 8.5rem, the size it takes beside a search field in a site header.

--mode-dial-size: 8.5rem

View

Large · 26rem, a HUD control a thumb can turn; on a phone it takes the column.

--mode-dial-size: 26rem

Kit

Equipped:

With the material taken off

The same dial and the same mechanics, flat: white fills, hairlines and plain type, the version you would drop into an ordinary website. Switch Sound on above and the two finishes part by ear as well: a ringing metal tick against a dry plastic one.

Plain names · the archive’s views in a system face on a white plate. Where the 120 teeth were, one tick per detent.

.mode-dial--plain

View

Plain curled · the time range still turns with the dial, in the same sans and no longer cut into it.

.mode-dial--plain + --curled

Range

Plain icons · with the display: no glass and no dot matrix, the same readout in the text face.

.mode-dial--plain + --icons + __display

View

Plain kit · the key still travels, and still takes a ring while a choice is pending. Only the material went.

.mode-dial--plain + --icons + __display + __commit

Kit

Equipped:

Heard

One variation with a voice of its own, opt-in like every sound on this page: a scratch for the teeth going past the mark, and a snap as the dial lands in a detent.

Ratchet · switch Sound on and turn it. The knurl scratches tooth by tooth, quicker the quicker it turns and silent when it stops, and the face snaps into each detent it lands in.

.mode-dial--ratchet

Range

Decisions

Type
Navigation

A rotary selector for a mode you stay in. Its choices sit round a knurled charcoal dial set into a cloth-dotted plate, and turning it brings one at a time under a fixed index mark. Underneath it is a radio group in a form, so arrow keys, a screen reader and a server all have it with no script; turning it by dragging, and the feel of each detent, are what component.js adds. Three item sets show where it belongs: an archive's views as icons that say their name when reached for, a dashboard's time range engraved round the arc, and a game's kit with a display and a centre key that commits. A second finish, .mode-dial--plain, is the same dial with the material taken off, and .mode-dial--ratchet gives either finish a voice of its own.

  1. One number turns everything.

    :has() rules write --mode-dial-sel and --mode-dial-n on the root, and the knurl, the engraving and the options layer all turn to -(sel + laps·n + drag) · 360deg/n on one transition, each option turning back by the same angle, so names and icons stay upright in lockstep without @property. A detent is an overshoot: 420ms on a linear() curve that passes the stop by 6% of the move and settles. CSS alone spins first-to-last the long way round; component.js keeps a winding count, --mode-dial-laps, so every turn takes the near way.

  2. One light, and the plain finish shows what it adds by taking it away.

    --mode-dial-light: 27deg sets every shadow, the knurl's rim light, the plate's chamfer, the face's streak, the cloth and the engraving lip, and it sits on the face, which never turns, so the teeth slide through a highlight that stays put. .mode-dial--plain keeps the mechanism and every mark that says something — the index, the chosen weight, the hollow index and ringed key of a pending choice, the pip, the focus rings, the tooltips — and takes off the cloth, the teeth, the light and every shadow: white fills, 1px hairlines, a system face in sentence case, and one tick per detent on the rim so it still reads as something that turns. Its signal is darker, #cf5a22, because the polished orange is about 2:1 on white.

  3. A detent is felt as well as seen.

    A drag goes through x − k·sin(2πx)/2π, with k = --mode-dial-notch (.6): slow near a stop, quick between, and continuous when the radio flips. Every detent the face passes is a tick — an arrow key, each detent a drag crosses, each one a click or an Escape sweeps over, timed to when the eased angle reaches it — and a tick dips the index, fires mode-dial:tick, buzzes on touch and pen, and clicks if --mode-dial-click is above 0. The key travels down, away from the light, and springs back, clicking on the way down and more softly on the way up. The sound is opt-in, synthesised with Web Audio, and follows only a real key, pointer or click on the dial; at 0 no AudioContext is ever made. The polished finish rings like metal and the plain one snaps like plastic. .mode-dial--ratchet is a voice of its own on either: a grain of bright noise for every tooth the face carries past the index, so the scratch's rate is the turn's speed, and a snap as the face lands in a detent — a transient, a thock falling from 170Hz, the ball settling 15ms later — lighter for a detent passed on the way.

  4. Browsing and choosing are two marks.

    With the centre key present, the checked radio is the pending choice and :default is the committed one: the index goes hollow and the key takes a ring while they differ, and a pip stays with what was committed. A hidden status line names what is held, and the key is described by it. Without script the key submits the form as GET; with script it rewrites defaultChecked, Enter on a radio commits, and Escape is form.reset(). A view or a filter has no key and applies as it turns.

  5. Nothing small, no hover to spend, and nothing muted.

    An option’s target is its whole slice of the face: between the half-way lines to its neighbours, from the key’s edge, or the middle’s without one, out to the plate’s, turning with the face. A tap on the knurl picks the option at that angle, and the key is never under 44px across. Names rise as tooltips on :hover, on :focus-visible and, under (hover: none), on :active and on the checked item, moving toward the centre so a wide word clears its own icon. A sideways swipe across the face turns the dial and a vertical one still scrolls the page. Every text pair clears 4.5:1 and every signal mark 3:1, in both finishes: the display is one ink on one glass, because a second, dimmer ink was the first thing to fall under the floor.

Inspiration
fujifilm-x.com/global/products/cameras/x-t50 — the dial on its top plate (the knurled rim, the fixed index, the detent) and the textured covering round it.

notes.md — the full record, including what was tried and dropped