Skip to content

Button variants and the red ramp - #93

Merged
microbit-matt-hillsdon merged 10 commits into
mainfrom
button-tones
Aug 15, 2026
Merged

Button variants and the red ramp#93
microbit-matt-hillsdon merged 10 commits into
mainfrom
button-tones

Conversation

@microbit-matt-hillsdon

@microbit-matt-hillsdon microbit-matt-hillsdon commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Variants express shape; colour comes from a tone or a token. No app-preset variant carries a colour any more.

  • warning/warningSolid are gone — destructive buttons are outline/solid with tone="danger".
  • neutral is the grey filled button that data-microbit-org and python-editor each had their own copy of.
  • tone is an allowlist (brand, danger, red) rather than Panda's open colorPalette prop: if a shape reads a stop the palette doesn't define, the button renders as nothing at all, with no error anywhere. See docs/hints.md. danger now aliases red whole for the same reason.
  • Red is graded onto the gray ladder. 500 was #E53E3E at 4.13:1 — the stop white button labels sit on — and is now #e22b2b at 4.55:1. Ink stops only; 400 was already exactly gray's 3:1 and is unchanged.
  • Form error text goes back to danger.500, which phase 1 had moved to 600 because 500 was too light for text. The error toast moves there too, so it reads as red rather than as a dark surface.

Worth a look: the Button stories (Variants, Tones), and Foundations/Colors → Ramps.

Phase 2 of the focus-and-colour plan: variants express shape, colour
comes from a palette or a token, and no variant exists per app to carry a
colour.

`warning`/`warningSolid` go. In their place `outline` and `solid` take
their colour from the new `tone` variant, whose entries set Panda's
`colorPalette`; the destructive pair is now `tone="danger"`. The same two
shapes absorb ml-trainer's `record`/`recordOutline`, which become a
`recording` tone in that app's preset — the vocabulary survives as a
palette rather than as a fork of the shape.

`tone` is an allowlist rather than Panda's open `colorPalette` prop
because a palette miss is silent: the key space is the union of every
stop name across all colour tokens, so `colorPalette.200` under a palette
without a 200 typechecks, emits a var that resolves to nothing, and the
declaration is dropped. The recipe states the four stops a tone must
carry (50/500/600/700) and the shapes read only those; adding a tone is
where a palette gets checked against it. Recorded in docs/hints.md.

`neutral` is the grey filled button data-microbit-org and python-editor
each had (`solid` and `zoom`), taking python-editor's hover/press stops,
which were re-graded for the current gray ramp. Deliberately not a
palette shape: it is a light fill under dark text, and its press stop
(gray.350) exists in no other ramp.

`outline` is 2px like `secondary` rather than ml-trainer's 1px record
outline, and puts text at 600 rather than 500 — 500 is the palette's
white-text fill stop and is not reliably readable as text on white.
Red is not a brand colour — it is the conventional colour for errors and
for recording — so it can be graded rather than negotiated, and the
family gray ramp's measured contract is the obvious ladder to put it on:
400 ≥ 3:1, 500 ≥ 4.5:1, the darker stops matched to gray's ratio at the
same name.

The stop that mattered is 500. At #E53E3E it measured 4.13:1, so white
text on it missed AA — and 500 is exactly the stop the `solid` shape
fills with under a white label, which today means every destructive
confirm and ml-trainer's record button. It is now #e22b2b at 4.55:1, a
small darkening; 600 and 700 (the outline's text, the error message, the
hover and press fills) go a clear grade darker.

Only the ink stops move. 50–300 are washes with no contrast contract and
keep their values, and 400 already measured 3.03:1 — exactly gray's — so
it stays verbatim and anchors the hue and saturation the darker stops
hold while their lightness solves for the ratio. 800 and 900 have no
consumer in the family; they are graded so the ramp stays one colour.

The Button recipe also regains the note about classroom's two restated
outline shapes, which the previous commit dropped with the `warning`
variant. Both are still waiting for a second consumer.
Rendered against ml-trainer's record pair, an outline drawn entirely at
600 reads darker than the `solid` it toggles with, where both states used
to show the same red. The border goes back to 500 and only the label
stays at 600, which is the stop that has to carry text: a 2px boundary
needs 3:1 and 500 measures 4.55:1, so nothing is lost. Hover shifts one
stop as before.
`danger` carried only the five stops something referenced. Nothing needs
a danger wash at 300 today, but a role that a shape can reach into
shouldn't have holes in it: a stop the palette lacks typechecks, emits a
var that resolves to nothing, and drops the declaration, with no error at
build or runtime. Aliasing the ramp whole costs a few lines and removes
the failure mode rather than documenting it.

The palette contract in Button.recipe.ts now says to prefer a whole ramp
rather than just the four stops the shapes read.
Recording is red by convention, not because it is destructive, so it
should not follow a brand preset's idea of an error colour — which is
exactly what `danger` is for. `red` is the ramp itself, and the
distinction is the one the recipe used to make by having `record` read
`red.*` while `warning*` read `danger.*`.

Pairs with dropping ml-trainer's `recording` palette: with this tone in
the family allowlist the app needs no button vocabulary of its own.
The story showed the greys alone, one swatch per row. It now shows both
graded ramps stacked, each laid out horizontally over shared columns —
the union of the stop names either uses — so a stop reads down the page
as well as across, and red's ink stops can be checked against the grey
ratios they were graded to. The columns are fixed-width, so a ramp that
wraps on a narrow canvas still lines up with the one above it, and the
stops gray alone defines (10, 75, 350) leave gaps rather than shifting
red left.

`danger` isn't drawn: it is red's values exactly, so a third identical
row would be nothing to compare. A line on red says it is aliased whole
as danger instead.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 15, 2026

Copy link
Copy Markdown

Deploying ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: e645433
Status: ✅  Deploy successful!
Preview URL: https://f8c956d7.ui-2wg.pages.dev
Branch Preview URL: https://button-tones.ui-2wg.pages.dev

View logs

Comment thread docs/hints.md
property the base declaration reads (`color: var(--avatar-color, …)`),
not in a higher-specificity state selector that outguns a call-site
`css`.
- **`colorPalette.<stop>` typechecks for stops the palette doesn't have,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can switch back if we fill in the extra stops uniformly or manage to drop them.

They had grown into explanations of the CSS beside them. What is left is
the part that isn't in the code: why a shape's stop choices are what they
are, why `tone` is an allowlist, and why red could be graded without a
brand conversation. The per-stop contrast figures go too — the colour
story measures them live from the rendered swatch.
The re-graded red puts danger.500 at 4.55:1, so calling it border-grade
at ~4.1:1 is no longer true. 600 stays for headroom, which is what the
other error slot already said.
Phase 1 moved error messages and required indicators to danger.600
because the inherited Chakra red put 4.13:1 in the text-safe slot — the
brand review recorded it as containment, not a fix. The graded ramp puts
500 at 4.55:1, so the containment goes and both slots return to the stop
the contract intends for text, matching the invalid border beside them.
White on it is 4.55:1, so it clears AA for the title and description, and
the graded ramp is what makes that true — before, 500 was 4.13:1 and the
toast had to sit at 600.

It no longer follows the teal toasts' 800: rendered beside one, the
saturated red carries the weight a darker maroon was giving it, and an
error toast reading as red is the point of the status.
@microbit-matt-hillsdon microbit-matt-hillsdon changed the title Button tones Button variants and the red ramp Aug 15, 2026
@microbit-matt-hillsdon
microbit-matt-hillsdon merged commit a5e3f55 into main Aug 15, 2026
3 checks passed
@microbit-matt-hillsdon
microbit-matt-hillsdon deleted the button-tones branch August 15, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant