DS2 — shared MUI theme baseline, acceptance & verification sheet (Musterbogen)

The candidate baseline from round 1 is now decided. This sheet is no longer a chooser between variants — it is the acceptance record for the locked createAppTheme(overrides) baseline, and the object later theme changes get re-checked against. The MUI 7.3.11 raw-default comparison stays throughout so the delta is still visible. Every contrast ratio shown is computed by an inline script at render time, not hand-typed. Static HTML/CSS, light mode only, no app code.

In one screen

Declared coverage

Covers: the token table; ink/border/surface with the page-background strip; the divider/controlBorder split; status split into text and fill roles; the settled type scale, control height, radius and shadow rule; the data-series ramp and its greyscale check; a render-time contrast script; and the 14 agreed component specimens, each beside its MUI default.

Does not cover: dark mode (out of scope for v1); components outside the agreed list; the createAppTheme() implementation and its override API; an audit of the 14 apps against this baseline; and the series-4/5 hue proximity itself, which is unchanged.

Explicit: the MUI column is a CSS transcription of MUI v7's documented defaults, not a live mount, and it renders in a system-sans stand-in rather than Roboto. Judge size, weight, radius, colour and shadow from it — not glyph shapes.

Font disclosure

Baseline columns use DM Sans 400/500/600, base64-embedded from @fontsource/dm-sans@5.2.8 in this estate's node_modules — no network fetch. MUI columns use a system-sans stand-in, not Roboto.

Ink, border, surface

MUI's default text is translucent (rgba(0,0,0,0.87)) and shifts with whatever sits behind it. The candidate uses three fixed opaque tiers. Muted was darkened twice: #6A7178 → #6A7178 → #6A7178.

MUI raw default MUI
text.primary
rgba(0,0,0,.87)
text.secondary
rgba(0,0,0,.6)
text.disabled
rgba(0,0,0,.38)
disabled UI is WCAG-exempt from AA text contrast — shown for reference only
background.default
#FAFAFA
divider
rgba(0,0,0,.12)
Baseline candidate DS2
ink.primary
#212529
ink.secondary
#5B6670
ink.muted
#6A7178 #6A7178
on page bg (B):on palest candidate (C):
bg.page
#FAF9F7 #F7F6F3
divider
rgba(33,37,41,.10)
decorative — 1.4.11-exempt, see below

Border, split in two: divider vs. controlBorder

WCAG treats the two differently, so one token cannot serve both. divider is decorative and exempt from 1.4.11; it stays pale. controlBorder is a control's boundary and needs ≥3:1 against its surface — the inherited input.border never cleared it. Derived from the same ink hue at higher opacity.

controlBorder (rest)
rgba(33,37,41,.50)
on C
controlBorder (hover)
rgba(33,37,41,.65)
on C
controlBorder (focus)
#3D5A99 (primary)
on C
controlBorder (error)
#BF3227 (critical)
on C

TextField / Select — rest, hover, focus, error

Same four states, MUI default beside the candidate, every boundary computed live. MUI's rest default also fails 3:1; its hover/focus/error states clear it. A rest-state gap in the library, not a deviation from it.

MUI raw default MUI
Rest
Hover
Focus
Error
Baseline candidate — controlBorder DS2
Rest
Hover
Focus
Error

Checkbox boxes and outlined Button/Chip edges use controlBorder too — the same rest-state fix wherever the sub-3:1 input.border was the outline.

Status, split by role, and the freshness semantic

One hex cannot be both text and fill: #1E8E3E measures 4.20:1 as text on white and — the formula being symmetric — the same 4.20:1 as a fill under white text. Each status now carries a text tone and a fill tone with its own contrastText. The text tones are checked against their own Alert tint, not against white — a tint lowers contrast, and checking the wrong surface hid a real failure: warning measured 4.10:1 on #FBF0DC, now #976100 at 4.62:1.

MUI raw default MUI
success.main — deploy healthy
warning.main — CI flaky
info.main — no "stale" concept; nearest is info (blue), semantically wrong for freshness
error.main — server unreachable

MUI's own palette.contrastThreshold defaults to 3, not WCAG's 4.5 — its auto-computed contrastText can legally ship combinations that fail AA normal text. No fill+contrastText row is rendered for this column: the point being made is the token design, not a second MUI demo.

Baseline candidate — text/icon role DS2
success — deploy healthy
warning — CI flaky
stale — last refreshed 6h ago
critical — server unreachable

Fill role — solid chip/badge, contrastText pair

Success
Warning
Stale
Critical

No estate precedent. Three prototypes invented a stale concept, all in amber: cockpit #9a7b33, fitness-monitor reused --status-warn. #5B6B7D is new — included on the operator's decision, because reusing amber would collide with warning, which is the reason the token exists. Unvalidated in a running screen; the first consuming app validates it.

Data-series ramp (categorical, owned by the baseline)

Six colours chosen to avoid every status hue (no green, amber, red or blue-grey), so a series is never mistaken for a status. Muted and cool-to-neutral, unchanged on the operator's verdict. No second channel: dash patterns, marker shapes and pattern fills were all removed on review — how a chart separates series is DS-4's question.

series 1
#3D5A99 — circle
series 2
#3E80B8 — square
series 3
#2E8F8A — triangle
series 4
#7A5FA8 — diamond
series 5
#9C4F86 — cross
series 6
#8A7355 — plus

Greyscale check — same swatches, desaturated to luminance only

Colour removed. Series 4 and 5 remain near-identical greys (109 vs 106), and nothing else distinguishes them since the marker glyphs were dropped.

series 1
series 2
series 3
series 4
series 5
series 6

Desaturated greys (live, sRGB 0–255): 92 · 123 · 129 · 109 · 106 · 119. Series 4 and 5 differ by ~3/255, below reliable perception; 2/3/6 sit inside 10 points. With nothing but hue carrying identity, a six-series chart does not satisfy WCAG 1.4.1.

Second channel in practice — multi-series line chart

Same six series, solid strokes, no per-series pattern or marker — removed on operator review (2026-08-09). Series 4 and 5 cross around March/May, which is where the close-luminance pair matters most; in the desaturated pane they are not reliably separable.

Colour
Jan Feb Mar Apr May Jun
Desaturated — colour removed, shape kept
Jan Feb Mar Apr May Jun
Series 1 / circle
Series 2 — dashed / square
Series 3 — dotted / triangle
Series 4 — dash-dot / diamond
Series 5 — long dash / cross
Series 6 — dash-dot-dot / plus

Second channel in practice — grouped bar chart

The same six series as bar fills — flat colour, no pattern. In the desaturated pane the close pairs are not separable.

Colour
hram jg-ferien spesix
Desaturated — colour removed, pattern kept
hram jg-ferien spesix
Series 1
Series 2
Series 3
Series 4
Series 5
Series 6

With the second channel gone, the desaturated panes are not reliably readable — series 4 and 5 in particular. Surface contrast is unaffected (desaturation preserves relative luminance); this section is about series-to-series distinguishability under 1.4.1, not text contrast.

Shadow: rest vs. transient — now a binary rule

MUI's shadows[1] is a visible three-layer drop shadow — right for 2014 Material, wrong for a flat dashboard. Round 1's near-invisible shadow only moved the judgment to how invisible is enough. The rule: Paper and Card at rest have zero shadow (elevation: 0, variant: 'outlined'); weight exists only on transient surfaces — Dialog, Drawer, Menu, Tooltip. Binary and checkable in review.

MUI raw default MUI
shadows[1]
rest (Paper/Card)
shadows[24]
overlay
Baseline candidate DS2
rest (Paper/Card)
no shadow, ever
overlay
Dialog/Drawer/Menu/Tooltip
Paper — no shadow Card — no shadow Button (contained) — no shadow Dialog — shadow Drawer — shadow Menu — shadow Tooltip — shadow

Typography — settled scale

MUI's h6 is 20px/500; the corpus runs 15–16px card titles and 13px tile titles. In the decided scale h4 (20px) is the screen title — a visual variant only. Semantically it stays <h1>: in MUI, component="h1" variant="h4". Treating the variant as the element produces a broken heading hierarchy and h6 (16px) the card title; h1–h3 stay usable rather than decorative.

MUI raw default MUI
H1 heading96/300
H2 heading60/300
H3 heading48/400
H4 heading34/400
H5 heading24/400
H6 heading20/500
Subtitle 116/400
Subtitle 214/500
Body 1 — the quick brown fox jumps over the lazy dog16/400
Body 2 — the quick brown fox jumps over the lazy dog14/400
BUTTON LABEL14/500 uppercase
Caption text12/400
OVERLINE TEXT12/500
Baseline candidate — decided scale DS2
H1 heading32/600
H2 heading28/600
H3 heading24/600
H4 heading — screen title20/600
H5 heading18/600
H6 heading — card title16/600
Subtitle 115/600
Subtitle 213/500
Body 1 — the quick brown fox jumps over the lazy dog14/400
Body 2 — the quick brown fox jumps over the lazy dog13/400
Button label14/500, no uppercase
Caption text12/400
OVERLINE TEXT11/600

Control height, touch target & radius — settled

MUI: medium OutlinedInput ~56px, small ~40px. Decided: 40px (36px in round 1), consistently across Button/Select/TextField/Chip. 34–36px is a recorded defect against the 44px touch guideline in an approved envelope; 40px sits on MUI's small, and @media (pointer: coarse) bumps to 44px — simulated here. Radius settled at 3px control / 8px card.

MUI raw default MUI
control 56px · cell pad 16px · radius 4/4
Chip
Card
radius 4
Baseline candidate — desktop DS2
control 40px · cell pad 10/16 · radius 3/8
Chip
Card
radius 8
Baseline candidate — touch (simulated) DS2
44px hit area via @media (any-pointer: coarse)
Chip
Card
radius 8

Mobile frame — 375px

A literal phone frame, not a resized window — at the decided control height, with the table→card behaviour below the usual breakpoint.

Controls at 375px (40px control height)

hram
jg-ferien
+3

Table → card collapse

hram
StagingSuccess · 4h
CI2 failing
jg-ferien
StagingSuccess · 5d
CIOK

Below the corpus breakpoint, each row becomes a card — same fields, stacked instead of columned. Cards are border-only at rest, per the shadow rule above.

What this sheet asserts, and what remains unverified

Release candidate, not yet a locked baseline. The motion tokens are declared but not demonstrated — a static specimen cannot show timing, and this document renders without JS in the environments it has been opened in so far. Reviewed 2026-08-09 by a second reader; the warning/tint contrast failure, the chart-token contradiction, the touch and focus rules and this document's own legibility were corrected in response. This document is what a later theme change gets re-checked against. Below: what is verified here at render time, and what still has to be confirmed elsewhere.

Verified
Every ink tone clears 4.5:1 on white and every status text tone clears 4.6:1 on its own Alert tint, computed live. Muted ink was darkened twice — now to a 4.6:1 target that also holds on a near-white page.
Verified
Every status fill↔contrastText pair clears 4.5:1. Success and warning needed a fill hue distinct from their text tone; critical and stale reuse one hex, which clears in both directions.
Verified
Muted ink is verified against the page background, not only a white surface — the round-2 gap is closed.
Verified
Control-boundary contrast is fixed. The old input.border (1.59 rest / 2.72 hover) never cleared 3:1. Split into divider (exempt) and controlBorder (all states ≥3:1), applied to TextField, Select, Checkbox and outlined Button/Chip.
Decided
The second non-colour channel was removed on operator review (2026-08-09) — hatch fills, dash patterns and marker shapes all read as noise. Series identity now rests on colour alone, so the series-4/5 greyscale proximity is a live limitation and a six-series chart does not satisfy WCAG 1.4.1. Resolving it belongs to DS-4, not to the theme.
Decided
#FAFAFA is decided (operator, 2026-08-09) after three warm candidates were rejected. The four-value strip is kept as provenance.
Known limitation
stale has no estate precedent and has not been seen in a running screen — a decision, not a validation. The first consuming app tests it.
Open
Out of scope here: dark mode, components outside the agreed list, the createAppTheme() implementation, and per-app conformance.

Appendix — provenance

Page background — candidate strip

Four values side by side: pure white, the chosen neutral, the original cool value, and the smallest perceptible warm shift. Each carries a real white card, so the question is whether the card still reads as a card. Path: #F5F7F8 → #F7F6F3 → #FCFBF9 → #FAFAFA.

#FFFFFF
pure white — the reference

Card on #FFFFFF

body text inside a white surface

Body text directly on the page

Muted caption directly on the page

Δ from white (R,G,B): 0, 0, 0 · cast (R−B): 0
reference: hram, fitness-monitor and innoservice ship this today
#FAFAFA
neutral, one step off white

Card on #FAFAFA

body text inside a white surface

Body text directly on the page

Muted caption directly on the page

Δ from white (R,G,B): 5, 5, 5 · cast (R−B): 0
CHOSEN 2026-08-09 — MUI grey[50], no colour cast
#F5F7F8
slightly cool — round 1

Card on #F5F7F8

body text inside a white surface

Body text directly on the page

Muted caption directly on the page

Δ from white (R,G,B): 10, 8, 7 · cast (R−B): −3
the original blue-cast value; note it is also ~5 points darker, so its difference is not only the cast
#FAFAF7
one nuance toward yellow

Card on #FAFAF7

body text inside a white surface

Body text directly on the page

Muted caption directly on the page

Δ from white (R,G,B): 5, 5, 8 · cast (R−B): +3
same lightness as the chosen value, blue lowered by 3 — the minimum perceptible warm shift

#FAFAFA is the chosen bg.page (operator, 2026-08-09). All three warm candidates read as too warm: against a blue-cast value, warmer meant neutral, and the need was a lightness step off white, not a hue. #FAFAFA is MUI's grey[50]. hram, fitness-monitor and innoservice ship pure white today and change visibly on adoption; jg-ferien and spesix override for identity.

#6A7178 cleared 4.5:1 only on pure white (4.549) and failed all three page candidates (4.399 / 4.324 / 4.247). Darkened along the same hue to #6A7178: 4.946 on white, 4.616 on the darkest candidate — against a 4.6 target, not a nudge over 4.5. Used everywhere ink.muted appears.

Canonical baseline tokens

Every decided value, once. A work order references this table by token name rather than retyping values; if a number here and a number in a WO disagree, this table is the one to fix. Contrast badges are computed live.

TokenValueRoleComputed contrastNote
Ink & surface
ink.primary#212529body text, headings unchanged
ink.secondary#5B6670subtitle2, secondary text unchanged
ink.muted#6A7178captions, meta text was #6A7178 (4.549 on white, 4.247 on the darkest page candidate). Darkened to clear 4.6:1 on all of them.
dividerrgba(33,37,41,.10)decorative separators, card outline decorative — exempt from WCAG 1.4.11. Split off from the single border token this round.
controlBorder (rest)rgba(33,37,41,.50)TextField/Select rest, Checkbox box, outlined Button/Chip new — supersedes input.border (0.23 alpha), which never cleared 3:1. Same ink hue, higher opacity.
controlBorder-hoverrgba(33,37,41,.65)control outline on hover supersedes input.border-hover (rgba(33,37,41,.45), 2.72:1, also sub-3:1)
control.autofillsurface inset shadow + -webkit-text-fill-color = ink.primaryautofilled input/select background and text not a contrast pair — a suppressionMUI has no autofill styling, and the browsers disagree: the same field rendered yellow in Chromium, dark grey in Opera, dark blue in Firefox (operator, 2026-08-09). :-webkit-autofill alone misses Firefox — the standard :autofill is needed too. Not demonstrated here: this sheet's form controls are inert, so no native chrome pollutes the comparisons. The gap is real for the apps.
controlBorder-focusderived from the app's primary, auto-darkened until it clears 3:1 against the surface (MUI getContrastRatio/darken). No fixed hex — the baseline defines no primary. Ratios for the four real estate accents →control outline on focus hram #468AB2
spesix #1B8FD8
fm #0F62FE
survey #432CA1
new — reuses the existing primary hue rather than introducing another colour
controlBorder-error#BF3227control outline in error state new — reuses the existing critical hue
bg.page#FAFAFAapp canvas / page background— neutral, no cast — MUI grey[50]. Fourth in the chain #F5F7F8 → #F7F6F3 → #FCFBF9 → #FAFAFA.
bg.surface#FFFFFFPaper/Card/Table/Dialog—unchanged
Status — success
success.text#35794AAlert text/icon, table cell text was #1E8E3E (4.20:1, FAIL). Replaced with jg-ferien's validated tone.
success.fill / .fillText#1B8038 / #FFFFFFsolid chip/badge fill new — #1E8E3E fails as a fill too (4.20:1 either direction, by symmetry). Darkened until fill+white clears 4.5:1.
success.bg#E5F4E9Alert background tint—unchanged; pairs with success.text, contrast only improves vs. pure white
Status — warning
warning.text#976100Alert text/icon, table cell text was #A66A00 (4.484:1, short by 0.016). Darkened minimally along the same muted brown-amber hue — never yellow.
warning.fill / .fillText#C08A2C / #212529solid chip/badge fill jg-ferien's validated precedent, dark ink as contrastText — a mid-tone amber fails under white text.
warning.bg#FBF0DCAlert background tint—unchanged
Status — critical
critical.text#BF3227Alert text/icon, table cell text kept — already passes
critical.fill / .fillText#BF3227 / #FFFFFFsolid chip/badge fill one hex, both roles: it clears 4.5:1 on white, so by symmetry white-on-it clears too. No second hue needed.
critical.bg#FBEAE8Alert background tint—unchanged
Status — stale
stale.text#5B6B7DAlert text/icon, table cell text kept — already passes
stale.fill / .fillText#5B6B7D / #FFFFFFsolid chip/badge fill same hex, same symmetry argument as critical
stale.bg#EAEDF1Alert background tint—unchanged. No precedent elsewhere in the estate — see the provenance note below.
Typography
h132px / 600rare, top-level marketing-adjacent heading—was 38px
h228px / 600section heading—was 30px
h324px / 600subsection heading—was 25px
h420px / 600screen title (visual variant; the element stays <h1>) — matches the estate's real 19-22px screen titles—was 21px
h518px / 600panel / dialog heading—unchanged
h616px / 600card title—unchanged
subtitle115px / 600emphasised secondary line—was 14px
subtitle213px / 500secondary tile title—unchanged
body114px / 400primary body copy—unchanged
body213px / 400secondary body copy, table cells—unchanged
button14px / 500, no uppercaseButton label—unchanged
caption12px / 400captions—unchanged
overline11px / 600, uppercaseoverline labels—unchanged
Density & control height
control.height40px
44px @ (pointer: coarse)
Button/Select/TextField height— was 36px. 34–36px is a recorded defect against the 44px touch guideline; 40px sits on MUI's small, 44px activates under a coarse pointer.
table.cellPadding10px 16pxTableCell—was 8px 12px, scaled proportionally with control height
chip.height / .radius / .fontSize32px / 16px / 13pxChip—was 28px / 14px / 12.5px, same proportional scaling
Radius
radius.control3pxButton/Select/TextField/Chip corner—settled — no A/B variant left
radius.card8pxPaper/Card/Dialog corner—settled — no A/B variant left
Shadow
shadow.restnonePaper/Card at rest — elevation:0, variant:'outlined' is the default—binary rule now: rest state never has a shadow, full stop
shadow.overlay0 8px 24px rgba(20,26,31,.16), 0 2px 8px rgba(20,26,31,.08)Dialog, Drawer, Menu, Tooltip only—unchanged value; scope narrowed to transient surfaces only
Data-series ramp (categorical)
series.1…series.6#3D5A99 · #3E80B8 · #2E8F8A · #7A5FA8 · #9C4F86 · #8A7355charts, categorical fillssee the ramp section below hues unchanged per the operator's verdict. The series-4/5 greyscale collision is not fixed — the second channel that covered it was removed; see DS-4.
series.markercircle · square · triangle · diamond · cross · plusline chart point markers, series 1→6 in order—opt-in, off by default — DS-13. Not applied in the charts above; a multi-series chart satisfies WCAG 1.4.1 only with the overlay switched on.
series.dasharraynone · 8,4 · 2,3 · 10,3,2,3 · 14,4 · 8,3,2,3,2,3line stroke pattern, series 1→6 in order—opt-in, off by default — DS-13. Not applied in the charts above; a multi-series chart satisfies WCAG 1.4.1 only with the overlay switched on.
series.patternsolid · diagonal hatch · dots · cross-hatch · h-lines · v-linesbar/area fill pattern, series 1→6 in order—opt-in, off by default — DS-13. Not applied in the charts above; a multi-series chart satisfies WCAG 1.4.1 only with the overlay switched on.
motion.fast120mshover, focus, border and colour state on controls not a contrast pairbelow ~100ms no transition is perceived; above ~150ms a control feels sluggish
motion.base180msexpand/collapse, reveal, list-item enter ——
motion.overlay220ms in / 180ms outDialog, Drawer, Menu, Tooltip —asymmetric on purpose, following MUI's own 225/195: an entrance may be followed, an exit should get out of the way
motion.chart300mschart entry only —MUI's standard: 300 is kept for exactly this case, where a line needs time to draw. Charts that poll or re-render on a timer get skipAnimation (cockpit's status board, hram's CampaignMonitorPanel) — X-Charts 8.28.2 offers no separate enter/update control, so those two lose entry animation too. A library limit, not a design choice.
motion.easingenter easeOut · exit easeIn · state easeInOutwhich of MUI's curves applies where —curves are MUI's own, unchanged — only the assignment is decided here
motion.reducedall durations → 0.01msprefers-reduced-motion: reduce —0.01ms, not 0 — at exactly 0 some browsers never fire transitionend, and MUI's Grow/Fade wait for it, so the component hangs. Kept as courtesy, not compliance: reduced-motion is not itself a WCAG criterion (2.2.2 is A for auto-playing motion over 5s, 2.3.3 is AAA).
Spacing, gutters, buttons (already settled, unchanged)
spacing.unit8pxlayout rhythm multiplier—unchanged
container.gutter24px (32px ≥1200px)Container—unchanged
button.textTransformnoneButton label casing—unchanged
button.elevationnone (contained)Button contained variant—unchanged, and now consistent with the shadow.rest rule above