feat(design): batch A recorded, batch B in the design lab (E28 S2b)
DESIGN.md: the Controls chapter decided (A1-A6) with the decision-log rows; density stays open as B1. The lab keeps only the chosen A renderings as reference and adds batch B: density, the scheme set (measured accent hue gaps and light contrast), state layers, the page-chrome model, surface per role, the public watch page, the nav rail, the loading form and where the header + fade edge goes.
This commit is contained in:
+49
-22
@@ -54,8 +54,8 @@ style guide.
|
||||
`.reveal` class), or it is removed on touch because the same action is reachable another way
|
||||
(`.reveal-hover`). [WCAG 1.4.13, Nielsen #6]
|
||||
- **Targets:** at least 2.75rem (44px) on a coarse pointer, as a real box, not an invisible hit area:
|
||||
overlapping hit areas fire the wrong control. [HIG, WCAG 2.5.8] _Which controls this covers beyond
|
||||
`Button` is decision A3._
|
||||
overlapping hit areas fire the wrong control. It covers every control primitive, not only `Button`
|
||||
(A3, under Controls). [HIG, WCAG 2.5.8]
|
||||
- **Text inputs** use at least 1rem text on a coarse pointer, so a tablet browser doesn't zoom the page
|
||||
when an input gets focus.
|
||||
- **Below 768px** (a narrow window, split screen): what replaces the side rails is **Pending (C)**.
|
||||
@@ -63,23 +63,40 @@ style guide.
|
||||
## Tokens
|
||||
|
||||
- **Colour:** only semantic tokens (`--bg --surface --card --fg --muted --accent --accent-fg --danger
|
||||
--warn --success --info --border`, each with a `-fg` partner where text sits on it). Six schemes × dark
|
||||
--warn --success --info --border`, each with a `-fg` partner where text sits on it). Six schemes × dark
|
||||
and light. Normal text ≥ 4.5:1 and a control's edge ≥ 3:1 in **every** scheme and mode. [WCAG 1.4.3,
|
||||
1.4.11] _The light-theme accent fix is decision B; the input edge token is decision A4._
|
||||
1.4.11] _The scheme set and the light-theme accent are decision B2._ The input edge token is set under
|
||||
Controls (A4).
|
||||
- **Type and size:** everything is rem-derived off `--font-scale` (the Settings text-size slider), so a
|
||||
px font size or a px-frozen box is an eslint error.
|
||||
- **Radius:** chips and segmented controls `rounded-full`; tabs `rounded-lg`. _Buttons, inputs, triggers
|
||||
and icon buttons: decisions A1, A2, A4, A5. Surfaces per role: decision B._
|
||||
- **Radius:** chips and segmented controls `rounded-full`; tabs `rounded-lg`. Buttons, icon buttons and
|
||||
form fields `rounded-lg`; toolbar triggers and search a pill (Controls). _Surfaces per role: decision B5._
|
||||
- **Layers:** the fixed z-index scale in `index.css` (`base < menu < chrome < paneltab < panel < rail <
|
||||
overlay < popover < tooltip`). No ad-hoc z-index. [maintainability]
|
||||
overlay < popover < tooltip`). No ad-hoc z-index. [maintainability]
|
||||
|
||||
## Controls — **Pending (batch A)**
|
||||
## Controls — _Decided_ (batch A; density **Pending**, B1)
|
||||
|
||||
Open in the lab: **A1** button shape and size rule · **A2** icon-button sizes and the on-media button ·
|
||||
**A3** the touch size beyond `Button` · **A4** the input primitive · **A5** the dropdown trigger ·
|
||||
**A6** checkbox, switch, slider.
|
||||
The chosen rendering of each rule is in the lab under "Decided". The primitives are built to it in S3.
|
||||
|
||||
- **Buttons (A1):** `rounded-lg` (r8): a control's corner is smaller than the surface it sits on (card r12,
|
||||
dialog r16). The rung follows the context: `md` in forms and dialog footers, `sm` in toolbars and panel
|
||||
headers, `xs` inside a table row. Glass is a `Button` variant, not a hand-built glass button. _The rung
|
||||
heights (standard 38/32/26 or compact 32/28/24) are decision B1._ [HIG, Material]
|
||||
- **Icon buttons (A2):** two sizes, r8: the smaller in rows and dense toolbars, the larger on its own. On a
|
||||
picture, a round, dark translucent button, readable on any thumbnail. Round means "on media" only.
|
||||
- **Touch (A3):** on a coarse pointer every control primitive (`Button`, `SegmentedToggle`, `FilterSelect`,
|
||||
`Tabs`, `Switch`, chips, the nav rail) grows to 2.75rem (44px) through one token, as a real box. Mouse
|
||||
sizes don't change. [HIG, WCAG 2.5.8]
|
||||
- **Input (A4):** one bordered input primitive. The edge is `color-mix(in srgb, var(--fg) 55%, var(--bg))`,
|
||||
≥ 3:1 against the page in every scheme and mode. A form field is r8 at the `md` height; a toolbar search
|
||||
is a pill. Text is 1rem on a coarse pointer. [WCAG 1.4.11]
|
||||
- **Dropdown trigger (A5):** a pill in a toolbar (where the pill means "a filter or a choice"), r8 in a form
|
||||
(it lines up with the inputs).
|
||||
- **Checkbox, switch, slider (A6):** the browser's own controls tinted with `accent-color`; the whole
|
||||
labelled row is the 44px target. Keyboard, screen reader and forced-colours support come from the browser.
|
||||
|
||||
Also in force:
|
||||
|
||||
Already in force:
|
||||
- An action button is `ui/Button` with a `SIZES` rung. A hand-rolled `<button className="px-3 …">` is a
|
||||
violation. A text-less button must have an accessible name, and the type enforces it. [WCAG 4.1.2]
|
||||
- A choice between a few values is `SegmentedToggle`; a long list or a user's own list is `FilterSelect`.
|
||||
@@ -90,11 +107,14 @@ Already in force:
|
||||
|
||||
## Surfaces and page chrome — **Pending (batch B)**
|
||||
|
||||
Open: the page-chrome model (a sticky toolbar vs controls in the content, collapsing, the page title), the
|
||||
glass role per surface, the public watch page, the light-theme accent, menu items and popover surfaces, the
|
||||
nav rail, and the loading form (spinner vs skeleton).
|
||||
Open in the lab: **B1** button density · **B2** the scheme set (and the light-theme accent) · **B3** state
|
||||
layers (hover, pressed) · **B4** the page-chrome model (a sticky band vs controls in the content, folding on
|
||||
a short screen, the page `h1`) · **B5** the surface and radius per role (card, dialog, menu, tooltip) · **B6**
|
||||
the public watch page · **B7** the nav rail · **B8** the loading form (spinner vs skeleton) · **B9** where
|
||||
the header + fade edge goes.
|
||||
|
||||
Already in force:
|
||||
|
||||
- The glass role is load-bearing: a dropdown or menu is `.glass-menu` (it floats over undimmed content, so a
|
||||
card alpha would bleed through), a dialog is `.glass`, and a grid card is `.glass-card`.
|
||||
- Every overlay is a `Modal`/`Overlay` or an anchored popover from the one popover engine. Escape closes the
|
||||
@@ -124,6 +144,7 @@ Which actions are visible and which live in a menu.
|
||||
### Text stays selectable — _Decided_
|
||||
|
||||
Wherever there is text, the user can select it and copy it, and no click action overrides that.
|
||||
|
||||
- A title or a name is never the text content of a `<button>`. The play/open target is the thumbnail, an
|
||||
icon, or a link-styled element.
|
||||
- A click that ends a text selection does nothing: if the selection inside the element is non-empty, or
|
||||
@@ -146,7 +167,7 @@ Wherever there is text, the user can select it and copy it, and no click action
|
||||
An error sits next to what failed and says how to recover; a field error is under its field and linked
|
||||
with `aria-describedby`. Colour is never the only signal. [Nielsen #9, WCAG 3.3.1, 1.4.1]
|
||||
- **Loading:** anything slower than ~300ms shows a loading state; a near-instant load doesn't flash one.
|
||||
Space is reserved for late content so the layout doesn't jump. _Spinner vs skeleton: batch B._
|
||||
Space is reserved for late content so the layout doesn't jump. _Spinner vs skeleton: decision B8._
|
||||
- **Reordering:** anything you can drag also has a button or menu way to move it (Top / Up / Down / Bottom),
|
||||
and a drag handle carries `touch-action: none` so a finger drags instead of scrolling. [WCAG 2.5.7]
|
||||
- **Motion:** 150–300ms micro-interactions, transform/opacity only; `prefers-reduced-motion` is respected.
|
||||
@@ -169,9 +190,15 @@ Wherever there is text, the user can select it and copy it, and no click action
|
||||
|
||||
## Decision log
|
||||
|
||||
| Date | Decision | Chosen | Over | Why |
|
||||
|---|---|---|---|---|
|
||||
| 2026-09-24 | Device contract | Tablet portrait + landscape and up; phones out | A 1024px floor with a "too small" message | Portrait tablet use is real, and the app already renders at 820 portrait |
|
||||
| 2026-09-24 | Card actions on touch | Fewer visible, the rest in a "…" menu | 44px buttons that no longer fit the card | Six 44px targets need more width than a card has |
|
||||
| 2026-09-24 | Action placement | One menu with several triggers, never gesture-only, a desktop background menu that mirrors the toolbar | Visible buttons for everything | Fewer non-critical buttons on screen, without hiding any function |
|
||||
| 2026-09-24 | Text selection | Text stays selectable; a click that ends a selection does nothing | Click-to-play on titles | The user must be able to copy any text the app shows |
|
||||
| Date | Decision | Chosen | Over | Why |
|
||||
| ---------- | --------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| 2026-09-24 | Device contract | Tablet portrait + landscape and up; phones out | A 1024px floor with a "too small" message | Portrait tablet use is real, and the app already renders at 820 portrait |
|
||||
| 2026-09-24 | Card actions on touch | Fewer visible, the rest in a "…" menu | 44px buttons that no longer fit the card | Six 44px targets need more width than a card has |
|
||||
| 2026-09-24 | Action placement | One menu with several triggers, never gesture-only, a desktop background menu that mirrors the toolbar | Visible buttons for everything | Fewer non-critical buttons on screen, without hiding any function |
|
||||
| 2026-09-24 | Text selection | Text stays selectable; a click that ends a selection does nothing | Click-to-play on titles | The user must be able to copy any text the app shows |
|
||||
| 2026-09-24 | A1 Button shape | r8 + a rung per context; glass is a Button variant | r12; a pill | Nested corners (r8 in r12 in r16); the pill already means "a filter or a choice" |
|
||||
| 2026-09-24 | A2 Icon buttons | Two sizes, r8; a round button only on media | One size; all round | A lone toolbar icon at the row size looks lost; round rows read as tags |
|
||||
| 2026-09-24 | A3 Touch size | 44 for every control primitive | 36 + gaps; Button only | One rule (HIG 44) instead of two sizes nobody remembers |
|
||||
| 2026-09-24 | A4 Input | Bordered, a 55% fg-into-bg edge (≥ 3:1) | Filled; today's look as a primitive | The only option that clears WCAG 1.4.11 without a heavy fill on glass |
|
||||
| 2026-09-24 | A5 Dropdown trigger | Pill in a toolbar, r8 in a form | Pill everywhere; r8 everywhere | Matches the neighbours in each context |
|
||||
| 2026-09-24 | A6 Checkbox, switch, slider | Native, accent-tinted, the row is the target | Drawn primitives | Free keyboard / screen-reader / forced-colours support; only 3 sites |
|
||||
|
||||
Reference in New Issue
Block a user