Repository navigation
feat(design-tokens): adopt the new mittwald brand colors - #3459
Jan-Eimertenbrink wants to merge 47 commits into
Conversation
Brand (Global) holds the theme-neutral brand palette, Semantic (Alias 1) is the only tier that switches light/dark, Variant (Alias 2) pairs Semantic colors, and component tokens never reference Brand. A reference check in build-tokens.js enforces the rules. Closes #3450 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🚀 Preview DeploymentPreview environments are ready:
Images:
|
`pnpm nx audit:contrast tokens` builds the tokens and reports the WCAG 2.2 AA ratio of every content/background pair in light and dark: text 4.5:1, icons and handles 3:1, disabled states exempt. Pairs come from token names, not file paths, so the tier restructure (#3462) keeps them: a `…content-color`, `…icon-color` or `color` token pairs with the sibling named `…background-color`, per state, falling back to `default`. Translucent backgrounds are composited on the theme surface (`neutral.color.100`); `light` pairs on its inverse, `*-static` pairs on black or white. Report only; `--strict` exits 1 on a failure for a later CI gate. Closes #3452 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Today's colors move into the tiers of ADR 0007 without a visual change:
- `color-brand.yml`: the palette, theme-neutral (today's light values).
`static-black-alpha`/`static-white-alpha` are gone.
- `color-semantic.{light,dark}.yml`: the only light/dark switch. Ramps
reference the mirrored Brand step in dark; gray keeps today's values
(100–1000 take gray.1000–100, 1100/1200 stay white). `transparent`,
`categorical`, `code-syntax`, `gradient` and `background-gradient` move
here as `<group>.color` literals. Decorative colors get provisional
ramps `decorative-{blue,navy,violet,teal,lilac,red,green,orange}.color`
until #3455 defines the final roles.
- `color-variant.yml`: the variant pairs, referencing Semantic only.
The build emits Semantic per theme instead of Brand. The 81 component
references to Brand, Variant's `transparent` and the Brand CSS variables
in component and docs code are rewired. `build-tokens.js` now fails on a
reference ADR 0007 forbids, a literal outside the Semantic literal groups
and a Semantic key defined for one theme only.
Every CSS variable resolves to the same value as before in light, dark
and both system variants, and so does every json-runtime token. Only the
108 Brand variables differ in dark: they keep their light value. A
manual migration entry covers consumers reading Brand variables.
Part of #3462
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
❌ Visual Regression Tests FailedAt least one shard did not pass. If snapshots differ from the committed baselines, download the visual-diffs-* artifacts from this run to inspect the actual/diff images (a shard that failed before comparison, e.g. during install or browser setup, leaves none). If the differences are intentional, update the baselines by adding the |
Brand takes the Figma "mittwald. Global Tokens" Color Primitives 1:1 (#3453): 19-step ramps mittwald-blue/-navy/-green/-lilac/-orange and system-danger/-warning/-success, a 12-step mittwald-neutral and 10-step black-alpha/white-alpha. The brand's Info ramp duplicates mittwald-navy and is left out. gray, hosting-blue, the old mittwald-navy, soft-contrast-violet, bright-lilac, super-teal and the 8-step alpha ramps are removed. Semantic references the Brand step closest in luminance to each value before, per theme, until #3455 defines the roles: info moves to mittwald-navy, decorative-violet to mittwald-lilac, decorative-teal to mittwald-green. The legacy status ramps stay until #3454. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an-4c5709 # Conflicts: # eslint.config.js
…igration The entry said every other --color--<ramp>--N still exists and Flow renders as before. Since #3453 gray, hosting-blue, soft-contrast-violet, bright-lilac and super-teal are gone, mittwald-navy and the alpha ramps carry new values, and Flow picks up the brand palette. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR 0007: code reads Semantic or Variant colors, never Brand. Brand is
theme-neutral, so a Brand variable stays light in dark.
`flow/no-brand-color-variable` rejects `--color--<brand-group>--…` in
stylesheets (stylelint) and TS/TSX strings (ESLint) in packages/components
and apps/docs. The groups are read from design-tokens/src/color-brand.yml,
so a new group is covered without touching the rules. A name completed at
runtime (`--color--#{$hue}`, `` `--color--${hue}` ``, `"--color--" + hue`)
is rejected when a Brand group can complete it: only Brand defines tokens
under `color`. Declarations that redefine a Brand variable and at-rule
params (mixin arguments) are checked too.
Exempt: the docs pages that display the palette (foundations/design/colors,
design-tokens), and the llms export test of the palette table via an inline
disable. The codebase was already clean after the restructure (#3462).
Closes #3463
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntrast in CI The contrast audit now also rates content colors without a background sibling against the backgrounds they render on, in light and dark: `text`, `heading`, `link`, `icon`, `alert-text`, Alert and Notification headings, label, breadcrumb, IllustratedMessage, plain Kbd, loading spinner, placeholder, and — at 3:1 (WCAG 1.4.11) — focus ring, form-control borders, checkbox/radio and slider track. Surfaces come from the components' SCSS: page (neutral 100 / 200), LayoutCard, Modal and its footer, Popover, Table and striped rows, AccentBox colors, Message bubbles, ListItem, FileCard, Notification. Hover/pressed states of clickable containers carry their own content only, not form fields or the focus ring. Borders that only group content are not rated. `pnpm nx test:contrast tokens` runs the audit with `--strict` and is part of `pnpm test` and `affected:test`. It fails on a failure missing from `dev/contrast-audit-allowlist.js`, on an allowlisted pair below its recorded ratio, and on an allowlisted pair that passes now. The allowlist holds today's 162 known failures in five groups, each with its cause and the ticket that fixes it (#3454, #3455). Closes #3480 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Semantic danger, warning, success and info now reference the brand's system-danger/-warning/-success ramps (info: mittwald-navy) with steps chosen per role, not by nearest luminance: | Semantic | Role | light (success) | dark (success) | | --- | --- | --- | --- | | 100–300 | soft, plain hover/pressed | 100–300 | 1800–1600 | | 800 | solid, plain, border | 1300 (1600) | 700 (1000) | | 900 | text, solid hover | 1500 (1700) | 500 (600) | | 1000 | solid pressed | 1600 (1800) | 400 (500) | 400–700 and 1100 are unused outside Semantic and fill the ramp in order. The brand bases (1000) carry no white text in light (danger 3.67, warning 2.75, success 1.63), so light solids take darker steps; dark success solid is the base itself. Danger and warning share their steps; they stay apart at ΔE 9.2 (light solid) and 4.9 under tritanopia (dark solid). decorative-red/-orange/-green mirror danger/warning/success. The legacy amaranth-red, royal-orange and espelkamp-green ramps are removed from Brand. Contrast gate: 65 allowlisted failures pass now, among them every warning failure. New allowlist groups record the accepted ones: plain status buttons on their own pressed tint (4.24–4.26, warning 3.41/3.72), content on the darker brand tints in light (#3479), and the darker dark status text on AccentBox violet/green and gray pressed surfaces (#3455, #3456). Part of #3454 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3454 # Conflicts: # packages/codemods/src/migrations.generated.ts # packages/codemods/src/tests/guide.test.ts # packages/components/MIGRATION.md
UX decided in #3456 that AccentBox green takes the brand's green instead of keeping the success mint: the AccentBox is decorative. green is deprecated toward sage, now renders exactly like it, and the codemod color-props-brand-palette renames it. The accent-box green token and class are gone; their two contrast allowlist entries with them (237 -> 235). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dark primary quiet.100–300 moves from mittwald-blue 1800/1700/1600 to 1600/1500/1400. On 1800 a primary soft Button vanished into the card (1.01:1 against elevation.100); 1600 lifts it to 1.13. UX compared 1700, 1600 and 1500 side by side and picked 1600: 1500 (1.22, level with secondary soft) read too loud. Plain and outline hover/pressed, the DatePicker range and TimeField pressed share these tokens and lighten with it. Text stays AA: soft text (blue 300) 7.41 at rest, 6.24 pressed; the link (blue 400) on plain pressed 5.60. No allowlist change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Light decorative-navy quiet.100–300 moves from mittwald-navy 400/500/600 to 300/400/500. On 400, navy outweighed the other decorative surfaces (1.74:1 against the card; lilac 1.51, salmon 1.56, sage 1.62), which UX noticed in Avatar, where navy is the default, and in the sender Message. 300 sits at 1.56. UX compared 300 and 200 side by side and picked 300: 200 read washed out. This departs from the brand's Background/Brand-100 (navy 400, #3550). Dark stays on 1700, already the quietest decorative surface after blue. Badge navy and AccentBox navy share the token and lighten with it. Contrast improves: initials 8.07 → 8.99, link on the sender Message 4.80 → 5.34. Danger text and the invalid field border now pass on the navy tints; their 8 allowlist entries go, and the remaining light navy and sender-Message floors are raised to the new ratios. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Content containers looked boxy next to LayoutCard and Modal: they shared 8px with the controls inside them, while surfaces had 24px. The scale jumped from m (8) straight to l (24). The scale keeps its names and gains a step: l is 16px now, the new xl takes 24px. Radii follow the element's role, and a nested element sits at least one step below its container (documented in border.yml): - xl: LayoutCard, Modal (and Badge, ProgressBar, which keep their 24px) - l: Table, List items and tiles, AccentBox, Alert, Message, Notification, FileCard, Accordion, CodeBlock, Image, FileDropZone, ImageCropper - m, s, round: unchanged UX decided the 16px and to keep the s/m/l names. Direct users of --corner-radius--l get 16px as well: in the docs app the component card, the file preview frame and the wireframe bars (which stay pills). Alert and Notification draw their start edge as a masked layer in their own shape. As a wide border-inline-start (Alert) or a 3px element with the corner radius (Notification), the edge bent into the larger radius. Now its outer edge follows the corner and its inner edge stays straight; content keeps its position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Popover is a container like the content containers: ContextMenu, ContextualHelp and the DatePicker calendar group controls inside its padding (MenuItem 8px, 8px padding). By the radius rule it sits one step above them, at l (16px), instead of sharing m (8px) with its items. The ChartTooltip shares the token. Tooltip stays at m. A separate commit, so UX can review and revert it on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
corner-radius.l drops from 16px to 12px (UX). 12px sits between the size-px steps s (8) and m (16), so the token takes the literal value. Every l container follows: Table, List, AccentBox, Alert, Message, Notification, FileCard, Accordion, CodeBlock, Popover, … The start stripe of Alert (was 8px) and Notification (was 4px) now references the element's own corner radius. A stripe narrower than the radius ended inside the corner curve; as wide as the radius it ends where the curve ends, and it follows any later radius change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @@ -0,0 +1,78 @@ | |||
| --- | |||
| since: 1.5.0 | |||
There was a problem hiding this comment.
Both new catalogue entries say since: 1.5.0, but they ship with 1.6.0. upgrade selects every entry with since <= target, so a consumer on 1.5.x running upgrade patch (target 1.5.53) gets this codemod and ends up with color="danger"/"sage", which 1.5 doesn't know. Same in color-tokens-brand-theme-neutral/entry.md, then pnpm nx build codemods.
| since: 1.5.0 | |
| since: 1.6.0 |
|
|
||
| /* The start edge is masked out of a layer in the alert's shape: as a | ||
| border-inline-start it bent into the corner radius. */ | ||
| &::before { |
There was a problem hiding this comment.
With the border gone, the surface and the start stripe are both background-color, which forced-colors mode (Windows high contrast) replaces with Canvas. The Alert then has no visible boundary at all; before, the 1px border survived as CanvasText. A @media (forced-colors: active) { border: 1px solid CanvasText; } on .alert would keep it apart from the surrounding content.
| red: "danger", | ||
| }, | ||
| Avatar: decorative, | ||
| Icon: decorative, |
There was a problem hiding this comment.
The generated icons (IconCheck, …) take ComponentProps<typeof Icon>, so they accept the deprecated color values too, but only the name Icon is matched here. <IconCheck color="violet" /> stays as it is and keeps warning. Could imports matching /^Icon[A-Z]/ (and Flow.IconX) be treated like Icon?
| : undefined; | ||
|
|
||
| const backgroundColor = | ||
| useDeprecatedColor( |
There was a problem hiding this comment.
For the legacy color="gradient" / color="green" path, this warns "Use 'gradient-navy' instead" without saying it belongs on backgroundColor. Following it literally (color="gradient-navy") drops the gradient. Maybe a dedicated message for that path (Use backgroundColor="gradient-navy")? The codemod also only rewrites backgroundColor, so these stay untouched.
Lisa18289
left a comment
There was a problem hiding this comment.
apps/docs/src/content/foundations/design/colors/index.mdx:22
8 of the 12 DesignTokenTables on the Colors page point at groups this PR removes (color.gray, color.hosting-blue, color.espelkamp-green, …). They render empty without a build or test failure, so nothing stops the page from going live like that once next is released. #3465 is still on the list here, so this only matters if the PR is merged before that's done.
| args: { color: "blue" }, | ||
| }; | ||
|
|
||
| export const Colors: Story = { |
There was a problem hiding this comment.
die story brauchts nicht die farben sieht man alle in der ersten story (umschaltbar über controls)
|
|
||
| export const Default: Story = {}; | ||
|
|
||
| export const Colors: Story = { |
There was a problem hiding this comment.
die story brauchts nicht die farben sieht man alle in der ersten story (umschaltbar über controls)
# Conflicts: # packages/design-tokens/src/dataVisualisation/area.yml
Co-authored-by: Lisa18289 <84317589+Lisa18289@users.noreply.github.com>
| value: "{corner-radius.l}" | ||
| # No border: the soft surface carries the Alert, the start edge keeps it | ||
| # apart from the AccentBox (#3564). | ||
| border-width: |
There was a problem hiding this comment.
die border width kann weg wenn sie nicht mehr genutzt wird
| corner-radius: | ||
| value: "{corner-radius.m}" | ||
| value: "{corner-radius.l}" | ||
| # As wide as the corner radius: the stripe ends where the corner curve |
| 1900: | ||
| value: "#1d202a" | ||
|
|
||
| mittwald-green: |
There was a problem hiding this comment.
wollen wir hier auch auf sage wechseln?
| 1900: | ||
| value: "#25222d" | ||
|
|
||
| mittwald-orange: |
There was a problem hiding this comment.
wollen wir hier auch auf salmon wechseln
| value: "{size-px.xs}" | ||
| m: | ||
| value: "{size-px.s}" | ||
| # 12px sits between the size-px steps s (8) and m (16), set by UX. |
| @@ -0,0 +1,437 @@ | |||
| # Semantic tier (ADR 0007), dark theme: colors with a meaning. The only | |||
There was a problem hiding this comment.
hier und auch an anseren stellen sind sehr viele kommentare, evtl kann man da noch etwas kürzen, und nur drin lassen was in zukunft zum arbeiten mit den jeweiligen token etc nötig ist.
| @@ -0,0 +1,642 @@ | |||
| # Variant tier (ADR 0007): pairs of Semantic colors per variant. References | |||
There was a problem hiding this comment.
ADR 0007 würd ich nicht erwähnen wenn er nur während des entwickeln der neuen Tokens wichtig war
| import { fileHeader, formattedVariables } from "style-dictionary/utils"; | ||
| import { propertyFormatNames } from "style-dictionary/enums"; | ||
|
|
||
| /* |
There was a problem hiding this comment.
in der datei werden auch wieder viele ADRs, MRs und Issues erwähnt, würde ich auch weglassen
|
packages/remote-react-components/src/tests/visual/screenshots/AccentBox.browser.test.tsx/AccentBox-colors-firefox-linux.png -> screenshots sind zu hoch geworden, die Accentboxen werden nicht mehr ganz angezeigt, ectl auf zweispaltig (ColumnLayout) umstellen |
|
die separator, zb hier bei den accordions (packages/remote-react-components/src/tests/visual/screenshots/Accordion.browser.test.tsx/AccordionGroup-firefox-linux.png) sind im darkmode schon sehr schwer zu erkennen, evtl müssen die doch etwas kräftiger/heller sein? |
#3435 left four component color tokens as literals or on a role that misses dark. Each now references Semantic (ADR 0007), decided with UX: - area.dot-background-color: {elevation.color.100}. Matches the area border it sits on; dark was pure black, now the card surface. - password-creation-field.complexity-shimmer-color: {light-static.color.strong.300}. White 70% instead of 80%: Semantic has no 80% step, and 70/80/90% look the same on the bar. - skeleton.background-color: {neutral.color.quiet.100}. Light stays close to the old tint; dark is a quiet dark surface instead of a bright blue-gray. - skeleton.shimmer-color: {light-static.color.quiet.300}. Stays white in dark; the theme-aware `light` group turns black there and the shimmer vanishes on the dark skeleton. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
skeleton.shimmer-color moves from {light-static.color.quiet.300} to
{light.color.quiet.300}: white 30% in light, black 30% in dark. UX
prefers the barely visible dark shimmer over a white sweep on the dark
skeleton. a312c0e had picked the static value without that decision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
skeleton.shimmer-color moves from {light.color.quiet.300} to
{elevation.color.100}: white in light, #1d1e1f in dark. The same alpha
reads three times weaker in light: the light bar sits close to white,
so a white veil barely lifts it (shimmer peak to bar 1.07 light, 1.20
dark). Opaque elevation.color.100 is even in both themes (1.23 / 1.24).
No single alpha step balances them, since a component token cannot pick
a step per theme. Decided with UX against three alternatives.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…roved The brand team's approval counts as given (#3542, 2026-11-02): the neutral ramp, the AccentBox gradients (#3456) and sage/salmon (#3508) stand as proposed. No values change. - color-brand.yml: drop "approval pending" and "Provisional until the brand team reviews them". - ADR 0007: the brand gradients are in Brand; `gradient` is replaced, only `background-gradient` stays a Semantic literal. - Contrast allowlist: "#3456 decides the AccentBox colors" → "set". Closes #3542 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the new mittwald brand colors into Flow and restructures the color token tiers. Each resolved decision of #3448 lands here as a commit; the draft is the visual reference for the next one.
Part of #3448
Done
pnpm nx audit:contrast tokensreports WCAG 2.2 AA for every content/background pair in light and dark; pairs come from token names. Baseline: light 2 fail + 23 large-only, dark 10 fail + 3 large-only.color-brand.yml,color-semantic.{light,dark}.yml,color-variant.yml. The build emits Semantic per theme, all Brand bypasses are rewired (decorative colors provisionally todecorative-*ramps),build-tokens.jschecks references, and the migration entrycolor-tokens-brand-theme-neutralis in place. Every CSS variable and json-runtime token resolves to the same value per theme as before; visual suite green.color-brand.ymltakes the Figma Color Primitives 1:1 —mittwald-blue/-navy/-green/-lilac/-orange,mittwald-neutral,system-danger/-warning/-success(19-step hue ramps), 10-stepblack-alpha/white-alpha, no Info. Old palettes removed;amaranth-red/royal-orange/espelkamp-greenstay as legacy until Evaluate the brand's status colors for functional use #3454. Semantic points at the new ramps provisionally (nearest luminance per theme). Audit: light 2 fail + 30 large-only, dark 10 fail + 8 large-only (no new fails). Visual suite: 216 light / 16 dark diffs, baselines deliberately not updated. Migration entry covers the removed palettes.borderrole (Propose a revised neutral ramp to the brand team #3539), provisional until the brand team approves (Re-sync the neutral ramp with the brand team's approved revision #3542):mittwald-neutralas 19 steps (the 12 approved values on new numbers plus 7 in-between steps), calmer neutral hover/pressed, secondary text one half step stronger, and a Semanticborderrole with lighter control borders (light #777C84, dark #888D96). Gate 249 → 171 allowlisted. Reverted if the brand team rejects it.Open
colorsanddesign-tokens(Update the Colors and Design Tokens docs pages to the color token tiers #3465, last), visual baselinesShips as a Minor: token names and values are outside the semver contract (ADR 0005).
🤖 Generated with Claude Code