Skip to content

feat(design-tokens): adopt the new mittwald brand colors - #3459

Merged
Jan-Eimertenbrink merged 66 commits into
nextfrom
feat/brand-color-tokens
Nov 3, 2026
Merged

Jan-Eimertenbrink merged 66 commits into
nextfrom
feat/brand-color-tokens

Conversation

@Jan-Eimertenbrink

@Jan-Eimertenbrink Jan-Eimertenbrink commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Brings the new mittwald brand colors into Flow and restructures the color tokens into three tiers.

Closes #3448
Closes #3507

What changes

  • Tiers (ADR 0008): Brand → Semantic → Variant → component. Brand is theme-neutral and holds literals only. Semantic is the only tier that switches light/dark. Component tokens never reference Brand; build-tokens.js fails on a forbidden reference.
  • Brand (color-brand.yml): mittwald-blue/-navy/-green/-lilac/-orange, a 19-step mittwald-neutral, system-info/-danger/-warning/-success, black-alpha/white-alpha, and a light and a dark value per brand gradient. The old palettes are gone.
  • Semantic (color-semantic.{light,dark}.yml): named roles instead of mirrored ramps. Colored groups have quiet/strong 100–300; the neutral ramp has elevation, content and border; each brand color has a decorative-<hue> group.
  • Variant (color-variant.yml) and all component tokens reference Semantic.
  • Contrast: pnpm nx audit:contrast tokens reports WCAG 2.2 AA in both themes. pnpm nx test:contrast tokens gates CI; dev/contrast-audit-allowlist.js lists each accepted failure with its ratio.
  • Lint: flow/no-brand-color-variable (stylelint and ESLint) rejects Brand variables in components and apps/docs.
  • Components:
    • Badge, Avatar, Icon, Color and AccentBox offer one decorative color per brand color: blue, navy, lilac, sage, salmon. Badge names its status hues success, warning, danger. The old values are deprecated, keep working and warn.
    • AccentBox: brand gradients gradient-navy/-sage/-lilac/-salmon; content defaults to dark on colored backgrounds.
    • Alert: soft surface and a start stripe instead of a border; a 1px CanvasText border in forced-colors mode. Badge has no border. List, FileCard and Table take the lighter separator border.
    • Corner radius by role: xl 24px for surfaces (LayoutCard, Modal), l 12px for content containers.
  • Migration: color-props-brand-palette (codemod, generated icons included) and color-tokens-brand-theme-neutral (manual), both since: 1.6.0.
  • Docs: the Colors and Design Tokens pages document the tiers; token tables show values per theme.
  • Visual baselines updated to the final colors, diffs approved by UX.

Ships as a Minor: token names and values are outside the semver contract (ADR 0005), and deprecated prop values keep working.

🤖 Generated with Claude Code

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>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

Preview environments are ready:

Type URL
docs pr-3459.docs.review.flow-components.de
storybook pr-3459.storybook.review.flow-components.de

Images:

  • docs: ghcr.io/mittwald/flow/docs:pr-3459
  • storybook: ghcr.io/mittwald/flow/storybook:pr-3459

`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>
@Jan-Eimertenbrink Jan-Eimertenbrink added the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Oct 12, 2026
@github-actions github-actions Bot removed the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

❌ Visual Regression Tests Failed

At 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 update-screenshots label to the PR.

Run details

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>
@Jan-Eimertenbrink Jan-Eimertenbrink added the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Oct 13, 2026
@Jan-Eimertenbrink Jan-Eimertenbrink added run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch and removed run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch labels Oct 13, 2026
@github-actions github-actions Bot removed the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Oct 13, 2026
…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>
Jan-Eimertenbrink and others added 4 commits October 13, 2026 10:04
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>
…ration

amaranth-red, royal-orange and espelkamp-green left Brand (#3454). The
entry lists them with the other removed palettes and points them at
--danger--, --warning-- and --success--color--N.

Part of #3454

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jan-Eimertenbrink Jan-Eimertenbrink added the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Oct 13, 2026
…3454

# Conflicts:
#	packages/codemods/src/migrations.generated.ts
#	packages/codemods/src/tests/guide.test.ts
#	packages/components/MIGRATION.md
github-actions Bot and others added 13 commits November 3, 2026 08:05
Co-authored-by: Lisa18289 <84317589+Lisa18289@users.noreply.github.com>
The Brand palette is maintained in the brand library, so the Colors page
no longer repeats it; the Aufbau section still explains the Brand tier.
The page's Best Practices list goes too.

The neutral roles state what each step is for instead of listing the
components that use it. Navy and the Light and Dark colors lose their
component examples for the same reason. The tier table on the Design
Tokens page drops its Light und Dark column; the sections below it say
the same.

AccentBox, Badge, Icon and Color called blue/navy/lilac/sage/salmon
"Brand Colors", which now names the palette tier. They are Decorative
Colors and link to that section. The Color status example used the
misspelled value "unavialable". The Avatar color example now shows all
five decorative colors and one icon instead of two.

Refs #3465

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dark `border.color.200` moves from `mittwald-neutral.1500` to 1400
(#404448). At 1500 the separators were hard to see on dark surfaces
(review on #3459): 1.44 on cards, modals and popovers, 1.58 on the page.
Now 1.70 and 1.86. Light stays at 500 (1.45 on white). The same ratio
reads weaker on a dark background.

Affects every `border.200` consumer in dark: Separator, Section,
AccordionGroup, Calendar presets, ListItem, Table (also in Markdown),
FileCard and the CartesianChart grid. Card and overlay borders
(`border.100`, 1600) stay. The contrast gate does not rate these borders;
the allowlist is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0007 is already beta packages, on main and next. Links in the lint
rules, build-tokens.js, the token YAML headers, both AGENTS.md and the
migration entry follow; MIGRATION.md is regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`upgrade` selects every entry with `since <= target`, so `since: 1.5.0`
handed the codemod to a 1.5.x consumer, whose Badge does not know
`danger` or `sage`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments keep what holds: the tier rules, the role legend, which step a
role takes and why, and for the contrast allowlist which pair reaches
which ratio and that UX accepts it. Issue references, approvals, ΔE
values and Figma file names go; git blame leads to them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kens

Forced colors replace the surface and the start stripe, both
backgrounds, with Canvas, so the borderless Alert lost its edge in
Windows high contrast. A 1px CanvasText border brings it back there.

`--alert--border-width` was 0 and `--alert--border-style` had nothing
to style, so both tokens and the border declarations go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The color control of the other stories shows every color already.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`IconCheck` & co. take `Icon`'s props, deprecated colors included, but
`color-props-brand-palette` matched only the name `Icon`. Imports
matching `/^Icon[A-Z]/` from a Flow package, and `Flow.IconX`, now get
`Icon`'s renames.

AccentBox's legacy `color="gradient"`/`"green"` needs no case:
`accent-box-color-to-background-color` moves it to `backgroundColor`
first in `upgrade`, where this transform renames it. The entry's
`apply` names the step for a manual migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`color="gradient"`/`"green"` warned "Use 'gradient-navy' instead".
Followed literally, `color="gradient-navy"` drops the gradient. The
legacy path now says `Use backgroundColor="gradient-navy" instead`;
`useDeprecatedColor` takes the prop the replacement belongs on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… roles

Ten AccentBoxes in one column outgrew the screenshot; three columns fit
all of them. Two columns still cut off the last row.

The AlphaColor backdrops were the old hex #3A434E and #E5EFF8, beside
the theme's own `neutral` AccentBox. They now take two Semantic roles,
so each theme shows two backdrops: the `neutral` AccentBox
(elevation.300) and the strong neutral surface (neutral.strong.100).

Baselines follow in #3507.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- AccentBox.tsx: kept next's FlowComponentProps<HTMLElement> and ref
  forwarding (#3593) together with the PR's backgroundColor JSDoc
  (@deprecatedValues violet, teal, green, gradient).
- SkeletonModeFormFields SkeletonMode-input-fields-{firefox,webkit}-linux.png:
  took the PR side. All baselines get regenerated at the end anyway (#3507).

No semantic conflicts: next brought no color token references; #3435 was
already part of this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jan-Eimertenbrink

Copy link
Copy Markdown
Member Author

Review summary

  1. ADR: renamed to 0008-color-token-tiers.md in 91803e0, with the heading and every link; MIGRATION.md regenerated. The PR body links 0008 now.
  2. References: removed in 1cb6a14. The allowlist reasons keep the pair, the ratio and that UX accepts it. The ADR keeps its origin.
  3. Process notes: removed from the token YAML headers in 1cb6a14. The role legend stays.
  4. PR body: updated.

AccentBox visual test too tall

Changed in 055e21f. Two columns still cut off the last row (five rows of boxes are taller than the 720px screenshot), so it uses three: four rows, all ten boxes visible. The baseline follows in #3507.

Test backgrounds

Changed in 055e21f. The AlphaColor backdrops were still the old hex #3A434E and #E5EFF8. They now take two Semantic roles, so each theme shows two backdrops: the neutral AccentBox (elevation.color.300) and the strong neutral surface (neutral.color.strong.100). light sits on the strong surface; the static colors take whichever is opposite their name. Baselines follow in #3507.

Empty Colors tables

Resolved by cf567a5 and a030ca7 (#3465): every DesignTokenTable on the Colors page points at a group that exists, and the tables show the tokens per theme.

@Lisa18289 Lisa18289 added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Nov 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Nov 3, 2026
github-actions Bot and others added 2 commits November 3, 2026 09:44
Co-authored-by: Lisa18289 <84317589+Lisa18289@users.noreply.github.com>
These 10 firefox-linux baselines still render the dark primary as
#85afff; the branch renders #9f9eef. The difference stays under the
comparator threshold, so update-screenshots never rewrote them. Without
a reference, the next update-screenshots run writes them from the
current code.

Part of #3507

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jan-Eimertenbrink Jan-Eimertenbrink added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Nov 3, 2026
Both sides changed the linux baselines of DateRangePicker-states and
SkeletonMode-input-fields: next for the DateRangePicker fix (#3625), the
branch for the brand colors. Neither image fits the merged code, so the
merge drops them and update-screenshots writes them fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jan-Eimertenbrink Jan-Eimertenbrink added update-screenshots Label a PR to update the screenshots used for visual regression testing and removed update-screenshots Label a PR to update the screenshots used for visual regression testing labels Nov 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Nov 3, 2026
Co-authored-by: Jan-Eimertenbrink <166510726+Jan-Eimertenbrink@users.noreply.github.com>
@Jan-Eimertenbrink
Jan-Eimertenbrink marked this pull request as ready for review November 3, 2026 12:46
@Jan-Eimertenbrink
Jan-Eimertenbrink requested a review from a team November 3, 2026 12:46
@Jan-Eimertenbrink
Jan-Eimertenbrink merged commit cc8e057 into next Nov 3, 2026
27 checks passed
@Jan-Eimertenbrink
Jan-Eimertenbrink deleted the feat/brand-color-tokens branch November 3, 2026 12:47
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.

2 participants