mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Quiet fields: base.css field baseline, --field tokens, 3:1 border-strong, app-wide migration
This commit is contained in:
@@ -54,6 +54,7 @@ were choices, not drift:
|
||||
| Color | "Blue at the core"; other hues only for purpose | **Dropped.** Color/theming is free and expressive; accent is a token, many themes welcome |
|
||||
| Neutrals | Carbon gray ramp | **Borrowed** — accessible, well-tuned, a good legible base |
|
||||
| Motion | Productive vs. expressive | **Productive only** — subtle, purposeful, reduced-motion-aware |
|
||||
| Secondary button | A dark gray **fill** (`$button-secondary`) | **Outlined** (Carbon's _tertiary_ shape) — one filled button per region stays the rule |
|
||||
|
||||
---
|
||||
|
||||
@@ -98,7 +99,8 @@ UI by swapping one set of values. Borrowed from Carbon's layering model:
|
||||
| ---------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `--bg` | App canvas (lowest layer) |
|
||||
| `--layer-01` / `--layer-02` | Raised surfaces (panels, cards, popovers) — elevation by lightness step, not shadow |
|
||||
| `--border` / `--border-strong` | Subtle and prominent separators |
|
||||
| `--border` / `--border-strong` | Subtle separators / component boundaries (3:1 non-text contrast) |
|
||||
| `--field-01` / `--field-02` (+ `--field-hover-*`) | Field fills: one step off the canvas / off a `--layer-01` surface |
|
||||
| `--text` / `--text-secondary` / `--text-placeholder` | Text hierarchy |
|
||||
| `--accent` / `--accent-hover` / `--accent-contrast` | The expressive accent — **swappable**; UI must never hardcode a hue |
|
||||
| `--accent-soft` / `--accent-soft-hover` | Low-emphasis accent **wash** (accent mixed into `--bg`) for a tinted-but-quiet surface |
|
||||
@@ -113,18 +115,26 @@ UI by swapping one set of values. Borrowed from Carbon's layering model:
|
||||
- **Status palette** (borrowed, stable): error `#da1e28`, success `#198038`,
|
||||
warning `#f1c21b`, info `#0043ce` — tuned per theme for contrast.
|
||||
- **Contrast:** target WCAG AA (4.5:1 text, 3:1 large/UI). Accent-on-`--bg` and
|
||||
text-on-`--accent` must both pass for any shipped theme.
|
||||
text-on-`--accent` must both pass for any shipped theme. **`--border-strong` is a
|
||||
component boundary, not decoration — it must hold 3:1 against the surface it
|
||||
bounds (WCAG 1.4.11)**: gray-50 `#8d8d8d` light (3.32:1 on `--bg`, 3.02:1 on
|
||||
`--layer-01`), gray-60 `#6f6f6f` dark (3.60:1 on `--bg`). Carbon's gray-30/gray-70
|
||||
"strong" values fail this; don't drift back to them.
|
||||
- **Soft accent is derived, not hardcoded.** `--accent-soft` /
|
||||
`--accent-soft-hover` are `color-mix(in srgb, var(--accent) 12–20%, var(--bg))`,
|
||||
so the wash follows whatever accent + theme is active rather than carrying a
|
||||
per-accent value. Use it for a surface that should be _noticed_ without competing
|
||||
with a primary action (the header's **Donate** button).
|
||||
- **Field-on-layer (Carbon layering).** A field is a layer _on top of_ the surface
|
||||
it sits on — so it steps **toward `--bg`**, never to a darker gray. On the white
|
||||
canvas and on a `--layer-01` panel alike, an input reads as **`--bg` fill +
|
||||
`--border-strong` outline**, not a `--layer-02` fill. Stacking a darker-gray field
|
||||
on a gray panel (canvas → `--layer-01` panel → `--layer-02` field) muddies into
|
||||
three indistinct grays — the bug this rule prevents (the snippet metadata panel).
|
||||
- **Field-on-layer (Carbon layering).** A field's fill is **one step off the
|
||||
surface it sits on**, alternating like Carbon's field set: `--field-01` on the
|
||||
canvas (gray on white / near-black on black), `--field-02` on a `--layer-01`
|
||||
surface (white on gray / a step lighter on dark). The alternation is what keeps
|
||||
a field visible without a box and prevents three indistinct grays from stacking
|
||||
(canvas → panel → field — the snippet-metadata-panel bug). Mechanism: components
|
||||
consume the contextual **`--field`** / `--field-hover` tokens only; an elevated
|
||||
surface sets `--field: var(--field-02)` (and the hover variant) once on its
|
||||
container — the same pattern as `--control-hover-fill`. Setters today: the modal
|
||||
chrome and body (ModalShell), the library metadata panel, the settings popover.
|
||||
|
||||
### 3.4 Shape & elevation
|
||||
|
||||
@@ -149,7 +159,15 @@ UI by swapping one set of values. Borrowed from Carbon's layering model:
|
||||
|
||||
## 4. Component conventions
|
||||
|
||||
- **The control scale (two heights, enforced by tokens + primitives).** Every
|
||||
- **How a shared look travels.** Exactly four mechanisms, in escalating order:
|
||||
**design tokens** (`styles/tokens.css`) for values; **contextual custom
|
||||
properties** set once by a surface and consumed by everything on it
|
||||
(`--control-hover-fill`, `--field`); **`base.css` element baselines** at zero
|
||||
specificity for looks every instance of an element shares (the focus ring, the
|
||||
field recipe); **React primitives** (`Button`, `IconButton`) when shared
|
||||
_behavior_ or enforced variants justify a component. Nothing else — no
|
||||
CSS-module `composes`, no utility classes, no mixin layer. A fifth mechanism is
|
||||
drift, even when it's locally cleaner. Every
|
||||
interactive control is `--control-height` (32px) or `--control-height-lg`
|
||||
(40px) — tokens.css. 32px is THE control height: buttons, fields, selects,
|
||||
segmented controls, icon buttons, anything in a toolbar, form row, or dialog
|
||||
@@ -162,9 +180,8 @@ UI by swapping one set of values. Borrowed from Carbon's layering model:
|
||||
_inside_ a 32px control — a search field's clear, a toast's dismiss); writing
|
||||
`height:` on a new ad-hoc button is the code smell.
|
||||
- **Buttons:** square, via the `Button` primitive. Variants: **primary** (filled
|
||||
`--accent`), **secondary** (1px `--border-strong`, `--bg` fill — the same
|
||||
field-on-layer treatment as inputs, so a bordered control on a gray panel goes
|
||||
white, never a darker gray), **ghost** (text-only, borderless; a transparent
|
||||
`--accent`), **secondary** (1px `--border-strong`, `--bg` fill —
|
||||
so a bordered control on a gray panel goes white, never a darker gray), **ghost** (text-only, borderless; a transparent
|
||||
border holds the box size), **soft-accent** (ghost on an `--accent-soft` wash —
|
||||
a low-emphasis solicitation, e.g. Donate), **danger** (filled
|
||||
`--support-error` — the confirm step), **danger-outline** (secondary geometry,
|
||||
@@ -176,16 +193,16 @@ UI by swapping one set of values. Borrowed from Carbon's layering model:
|
||||
control scale — content-sized, 11–12px, no box — so they read as part of the
|
||||
prose/panel they act on, not as toolbar controls. Don't "promote" them to
|
||||
Buttons; their smallness is the emphasis level.
|
||||
- **Borders mark function, not decoration.** A 1px `--border-strong` box belongs
|
||||
to things that _hold or receive a value_: fields, selects/disclosure triggers,
|
||||
segmented controls, secondary buttons, drop targets (dashed). Plain actions are
|
||||
ghost or filled — never outlined boxes. List rows are flat (hairline dividers +
|
||||
hover fill), not stacked boxes. With square chrome, every box makes alignment
|
||||
errors visible, so each border must earn its place; when a region looks
|
||||
"busy", remove boxes before shrinking anything. (Carbon goes further — fields
|
||||
are bottom-border-only and its secondary button is a gray fill; our fully-boxed
|
||||
fields are a recorded divergence, §2, which is exactly why box discipline
|
||||
matters more here, not less.)
|
||||
- **Borders mark function, not decoration.** Fields and select-like triggers are
|
||||
**not boxes** — they're the quiet-field recipe below (fill + bottom border). A
|
||||
1px `--border-strong` **box** is reserved for the few value-holding controls
|
||||
that need full enclosure: segmented controls, secondary buttons, drop targets
|
||||
(dashed), the color-swatch input. Plain actions are ghost or filled — never
|
||||
outlined boxes; passive chrome (tags, badges, type glyphs) takes `--border`,
|
||||
never `--border-strong`. List rows are flat (hairline dividers + hover fill),
|
||||
not stacked boxes. With square chrome, every box makes alignment errors
|
||||
visible, so each border must earn its place; when a region looks "busy",
|
||||
remove boxes before shrinking anything.
|
||||
- **Emphasis hierarchy (Carbon button/usage).** A region carries **one**
|
||||
high-emphasis (primary) button at most; everything else is lower emphasis. In
|
||||
toolbars/headers full of utilities, the utilities go **ghost** so they recede
|
||||
@@ -207,10 +224,22 @@ UI by swapping one set of values. Borrowed from Carbon's layering model:
|
||||
inherit the right step instead of each re-encoding it.
|
||||
- **Focus ring:** a 2px `--focus` outline (offset 1–2px). Always visible on
|
||||
keyboard focus — accessibility is non-negotiable (principle 4).
|
||||
- **Fields** (text, textarea, select, search): square, 1px `--border-strong`,
|
||||
**`--bg` fill** (field-on-layer, §3.3 — white on the canvas _and_ on a
|
||||
`--layer-01` panel; never a darker-gray fill), accent border + focus ring on
|
||||
focus. Mono font for spec/JSON inputs.
|
||||
- **Fields** (text, textarea, select-trigger, search): **the quiet field** —
|
||||
square, `--field` fill (one step off the surface, §3.3), **bottom border only**
|
||||
in `--border-strong`, no box; 2px `--focus` ring hugging the box (offset −2px)
|
||||
on focus. Mono font for spec/JSON inputs. The recipe is declared **once** in
|
||||
`styles/base.css` as a zero-specificity element baseline (text-like `input`
|
||||
types + `textarea`, excluding Monaco's internal widgets); component modules add
|
||||
only idiosyncrasies — width, padding, font size — never a competing border or
|
||||
fill. Select-like triggers are `<button>`s the baseline can't reach, so
|
||||
SelectControl/SortControl restate it (they are fields, not buttons: a trigger
|
||||
holds a value); their hover/open fill is `--field-hover`. Writing `border:` on
|
||||
an input is the code smell — the field look has exactly one home. The quiet
|
||||
treatment is Carbon's; the boxed alternative (GOV.UK's canon — 2px solid
|
||||
enclosure) is equally legitimate a11y-wise but spends a box on every field,
|
||||
and in a square-chrome editor UI boxes are reserved for the few controls that
|
||||
need full enclosure. A field's boundary is carried by its fill step plus the
|
||||
3:1 underline.
|
||||
- **List rows** (snippet library): compact, full-row hover (`--layer-01`),
|
||||
active row marked by an accent left-border + `--layer-01` fill, secondary
|
||||
metadata in `--text-secondary`. Row-level actions reveal on hover.
|
||||
|
||||
Reference in New Issue
Block a user