Button variants and the red ramp - #93
Merged
Merged
Conversation
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.
Deploying ui with
|
| Latest commit: |
e645433
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f8c956d7.ui-2wg.pages.dev |
| Branch Preview URL: | https://button-tones.ui-2wg.pages.dev |
| 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, |
Contributor
Author
There was a problem hiding this comment.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Variants express shape; colour comes from a tone or a token. No app-preset variant carries a colour any more.
Worth a look: the Button stories (Variants, Tones), and Foundations/Colors → Ramps.