Every foundations, styles and components page of m3.material.io (238, from the sitemap) extracted into docs/reference/m3, five audit reports with 142 findings in docs/audits/m3-alignment, and the 2.0.0 plan in docs/plans/material-3-alignment.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
293 lines
32 KiB
Markdown
293 lines
32 KiB
Markdown
# Audit: core (tokens, scheme, theme, icons, shapes, guard, showcase foundations, AI docs)
|
||
|
||
Ground truth: `docs/reference/m3/styles.md`, `docs/reference/m3/foundations.md`, raw pages under `m3/raw/`.
|
||
Library paths are relative to `/Users/surtic86/Privat/Development/livewire-material`.
|
||
|
||
## Summary
|
||
|
||
The core is in good shape: every 2025-spec colour role (the `*-dim` roles and the fixed roles included) is generated and mapped, the legacy `surface-tint`/`surface-variant` are correctly left out, the corner scale is M3 Expressive's ten steps, the five shadow levels are Google's web geometry, the six springs carry the exact Compose damping/stiffness constants, the state layer uses M3's 8/10/10 % and hover is pointer-only, the typescale sizes/line heights/weights match, and theme switching honours both the OS and the visitor's choice. The 35 shapes and the full Symbols set are ported from Google's sources.
|
||
|
||
The gaps are around the edges of the system rather than in it. (1) The three custom state colours (`success`/`warning`/`info`) are built with the 2021 `customColor` recipe, so they ignore the `--contrast` level and the 2025 spec's tone rules that `error` follows. (2) Only the standard contrast level exists; M3 (May 2025) makes standard/medium/high part of the colour system. (3) The semantic ink utilities (`text-meta` 60 %, `text-quiet` 38 %, `border-divider` 40 %) are M2-style opacity emphasis, which M3 replaced with `on-surface-variant`/`outline-variant`, and 38 % collides with M3's disabled opacity. (4) Tailwind's own radius, shadow, type-size, weight, easing and duration utilities still compile, so an application (or an AI agent) can silently leave the M3 scale; only colours were cleared. (5) There are no tokens or variants for M3's five breakpoints, no `dragged` state, and no shared touch-target utility. (6) Type tracking follows the older material-web numbers rather than Compose's, and the emphasized styles reuse baseline tracking. (7) The AI docs list the utilities but explain none of M3's rules for choosing between them; that is the "core concept" document the user asked for.
|
||
|
||
Counts: must-fix 1 · should-fix 12 · nice-to-have 11.
|
||
|
||
## Findings
|
||
|
||
### C1 · scheme command · custom colours ignore the contrast level and the 2025 spec
|
||
- Severity: must-fix
|
||
- M3 says: "Custom components support contrast levels simply by using Material color roles… the role's resolved value changes per contrast level automatically" (reference-styles §Color/Contrast levels; site `styles/color/roles`). Aug 2024: on-container roles became "more colourful while remaining accessible"; the 2025 spec that the library asks for (`--spec=2025`) applies those curves to `error`. `define-new-colors`: custom colours should behave like the built-in roles.
|
||
- Library does: `resources/node/scheme.mjs` (tail) builds `success`/`warning`/`info` with material-color-utilities' `customColor()` (`pe(...)`, `blend:false`): fixed tones 40/100/90/10 in light and 80/20/30/90 in dark, no contrast curve, no spec awareness. So `php artisan material:scheme --contrast=1` raises every M3 role but leaves the three state colours at standard contrast, and their `on-*-container` pairs are tone 10/90 while `on-error-container` (2025) is `#6e0523`-style tone ~25. Visible in `resources/css/tokens/scheme.css:66-77` vs `:62-65`.
|
||
- Fix: in `bin/scheme.mjs` (source of the bundle), define the three colours as `DynamicColor`s on their own `TonalPalette` exactly as `color_spec_2025.js` defines `error`/`onError`/`errorContainer`/`onErrorContainer` (same `ContrastCurve`s and `ToneDeltaPair`s), evaluated against the same `DynamicScheme` (so `contrastLevel`, `isDark`, `specVersion`, platform all apply). Rebuild `resources/node/scheme.mjs`; regenerate `tokens/scheme.css`, `scheme.json`, the workbench scheme. Add a `SchemeCommandTest` case: contrast 1 changes `success`.
|
||
- Effort: M
|
||
- Breaks API? no (role names unchanged; values change)
|
||
|
||
### C2 · scheme · only the standard contrast level is generated
|
||
- Severity: should-fix
|
||
- M3 says: "Three levels of contrast: standard, medium (3:1 minimum), high (7:1)… contrast settings apply automatically in both light and dark theme" (reference-styles §Color; `styles/color/roles`, "What's new May 2025"). Accessibility principle "Honor individuals — build in customizable features" (reference-foundations §Accessibility).
|
||
- Library does: `SchemeCommand` (`src/Console/SchemeCommand.php:24`) takes `--contrast` once and writes a single light and dark block; nothing at runtime can raise contrast; `theme.js` has no contrast state; no `@media (prefers-contrast: more)`.
|
||
- Fix: have `material:scheme` also emit `[data-contrast="medium"]` (contrast 0.5) and `[data-contrast="high"]` (1.0) blocks for light and dark (and per profile), plus `@media (prefers-contrast: more) { :root:not([data-contrast]) {…} }` as the OS default; add `contrast` to `$store.theme` (`resources/js/theme.js`) and to the head script's attribute set (`theme-script.blade.php`, kept across `wire:navigate` like `data-theme`); `<x-theme-toggle mode="picker">` or a new `<x-contrast-picker>` exposes it. Document in SKILL.md §Theme.
|
||
- Effort: M
|
||
- Breaks API? no (opt-in)
|
||
|
||
### C3 · theme.css · M2-style opacity inks and lines instead of M3 roles
|
||
- Severity: should-fix
|
||
- M3 says: "Default typography color is on surface (or on surface variant as a strong alternative)"; dividers use **outline variant**, not a lighter mix; "Always apply color roles, never static hex values"; 0.38 is the **disabled** opacity (reference-foundations §States values; reference-styles §Color rules, §Typography accessibility).
|
||
- Library does: `resources/css/tokens/theme.css:99-106` defines `--color-meta` = on-surface 60 %, `--color-quiet` = on-surface 38 %, `--color-chrome` = outline-variant 60 %, `--color-divider` = outline-variant 40 % via `color-mix()`. `text-quiet` therefore looks exactly like disabled text (`text-on-surface/38`, used 21× in components), and `border-divider` is lighter than M3's divider. None of these utilities is used by a package component (grep: only the showcase swatches and one `border-divider`/`divide-divider`), so they exist for ReStride's templates.
|
||
- Fix: keep the names, repoint them to roles: `--color-body` → on-surface-variant (already), `--color-meta` → on-surface-variant, `--color-quiet` → outline, `--color-structure` → outline-variant (already), `--color-chrome` → outline-variant, `--color-divider` → outline-variant. Say in SKILL.md that emphasis is a role, not an opacity, and that 38 % means disabled. If ReStride needs the lighter divider, it overrides the variable in its own `@theme`.
|
||
- Effort: S
|
||
- Breaks API? no (names stay; rendered colour changes in ReStride)
|
||
|
||
### C4 · theme.css · Tailwind's default radius, shadow, type-size, weight, easing and duration utilities still compile
|
||
- Severity: should-fix
|
||
- M3 says: "Do use design tokens instead of hardcoded values" (reference-foundations §Design tokens); the corner scale, elevation levels, typescale and motion tokens are the only values (reference-styles §Shape, §Elevation, §Typography rule "avoid changing type size", §Motion).
|
||
- Library does: `theme.css:19-25` clears `--color-*` only. `rounded-lg` (Tailwind: 8 px, M3 "large" is 16 px), `rounded-xl` (12 px vs 28 px), `shadow-md`, `text-sm`/`text-lg`, `font-bold`, `leading-*`, `tracking-*`, `ease-in-out`, `duration-300` all still compile with Tailwind's values. The package's own views are clean (only `rounded-full` ×4 and `rounded-none` ×1, both fine), but consuming apps and AI agents are not stopped. `DesignGuard` does not look for them.
|
||
- Fix: two steps. (a) Now, non-breaking: `DesignGuard` gains a "value outside the M3 scale" check for `rounded-(xs|sm|md|lg|xl|2xl|3xl|4xl)`, `shadow-(2xs|xs|sm|md|lg|xl|2xl)`, `text-(xs|sm|base|lg|xl|\dxl)`, `font-(thin|…|black)`, `leading-*`, `tracking-*`, `ease-(in|out|in-out)`, `duration-\d+`, `blur-*`? (no), each with the M3 utility to use instead. (b) In the next major: `@theme { --radius-*: initial; --shadow-*: initial; --ease-*: initial; --text-*: initial; --font-weight-*: initial; --leading-*: initial; --tracking-*: initial; }` before re-declaring the M3 ones, mirroring what was done for colour (keep `--radius-full`/`rounded-none` behaviour by declaring `--radius-full: 9999px`).
|
||
- Effort: S (guard) + S (theme, but a major-version change)
|
||
- Breaks API? (a) no · (b) yes for applications using Tailwind defaults
|
||
|
||
### C5 · type.css · tracking follows material-web, not Compose; emphasized styles reuse baseline tracking
|
||
- Severity: should-fix
|
||
- M3 says (reference-styles §Typography, `TypeScaleTokens.kt`): Display Large −0.2 sp; Title Medium 0.2 sp; Body Medium 0.2 sp; emphasized: Display Large 0, Title Medium 0.15, Body Large 0.15, Body Medium 0.25 (others unchanged).
|
||
- Library does: `resources/css/tokens/type.css:21` display-lg tracking −0.015625 rem (−0.25 px, material-web); `:39` title-md 0.009375 rem (0.15 px, M3: 0.2 sp = 0.0125 rem); `:48` body-md 0.015625 rem (0.25 px, M3: 0.2 sp); every `type-emphasized-*` utility (`:101` onward) uses the baseline `-tracking` variable, so emphasized display-lg keeps −0.25 px instead of 0, emphasized body-lg keeps 0.5 px instead of 0.15.
|
||
- Fix: add `--md-sys-typescale-emphasized-*-tracking` variables with Compose's values and use them in the emphasized utilities; correct the three baseline values to Compose's (sp/16 rem). Sub-pixel, but it is what the Expressive token file says and the header comment cites material-web, which is pre-Expressive.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C6 · type.css · regular styles do not reset `font-variation-settings`
|
||
- Severity: nice-to-have
|
||
- M3 says: emphasized styles are applied deliberately, per element (reference-styles §Typography rules).
|
||
- Library does: `type-emphasized-*` sets `font-variation-settings: "ROND" 100` (`type.css:107`), an inherited property; `type-*` utilities set none, so `type-body-md` text inside an element carrying `type-emphasized-title-md` (a card slot under an emphasized wrapper) renders fully rounded.
|
||
- Fix: every regular utility sets `font-variation-settings: normal` (or `"ROND" 0`).
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C7 · font · variable axes of the subset are unverified here
|
||
- Severity: nice-to-have (verification)
|
||
- M3 says: brand/plain typefaces are the product's choice; Roboto is the default (reference-styles §Typography fonts). Google Sans Flex is Google's Expressive typeface (the site's shape page: "M3 shapes and Google Sans Flex share roundness attributes"), so the choice is aligned.
|
||
- Library does: `font.css` claims weight 400–700 and `ROND` 0–100 are kept. I could not open the woff2 (no fontTools on this machine).
|
||
- Fix: add a test (or a `bin/` check) that reads the `fvar` table and asserts the `wght` and `ROND` axes exist, so a future re-subset cannot silently drop the axis the emphasized styles depend on.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C8 · motion.css · spring durations differ from the web equivalents Google publishes
|
||
- Severity: should-fix (with a counter-argument)
|
||
- M3 says (`styles/motion/overview/specs`, reference-styles §Motion "Web curve equivalents"): expressive spatial fast/default/slow = 350/500/650 ms, effects = 150/200/300 ms, each with a cubic-bezier; "springs aren't natively supported on the web, so Web should use these approximated curves with matching duration".
|
||
- Library does: `motion.css:33-52` samples the real springs into `linear()` (more faithful than Google's cubic-bezier) but stops at the settle time: 360/440/600 ms and 150/240/330 ms. Default spatial is 12 % quicker than Google's web number, slow effects 10 % slower.
|
||
- Fix: either (a) sample each spring over Google's published web duration (the curve reaches 1 earlier and holds, so the bounce timing is unchanged) so the `*-duration` tokens read 350/500/650/150/200/300, or (b) keep the settle times and record why in the header. Also add the six published cubic-bezier curves as `--md-sys-motion-spring-*-bezier` fallbacks for `@supports not (animation-timing-function: linear(0,1))` (Safari < 17.2 is below the floor, so this is documentation more than need). (a) is "what Google says".
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C9 · motion.css · only the Expressive motion scheme exists
|
||
- Severity: nice-to-have
|
||
- M3 says: two preset schemes, Expressive (default) and Standard ("minimal bounce, for utilitarian products"), swappable product-wide (reference-styles §Motion principles; `StandardMotionTokens.kt`: fast spatial 0.9/1400, default 0.9/700, slow 0.9/300; effects identical).
|
||
- Library does: six springs, Expressive only.
|
||
- Fix: sample the three Standard spatial springs too and let `[data-motion="standard"]` on `<html>` (or a config flag rendered by the theme script) swap the `--md-sys-motion-spatial-*` variables. Effects are the same in both schemes.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C10 · state.css · no `dragged` state, no disabled tokens, no shared touch target
|
||
- Severity: should-fix
|
||
- M3 says: state layer opacities hover 0.08, focus 0.10, pressed 0.10, **dragged 0.16**; disabled content 0.38; "the size of state layers is 40dp while the interactive target size is 48dp"; target spacing 8 dp (reference-foundations §States, §Accessibility values).
|
||
- Library does: `state.css:26-40` covers hover/focus/active; nothing for dragged (cards, chips, list items and the slider are the components M3 gives a dragged state); no `--md-sys-state-*` custom properties, so components hard-code `/38`, `/12`, `/10` (21, 10 and 4 occurrences); the 48 px target is built ad hoc in `button.blade.php:167` with an `after:` pseudo-element and nowhere shared.
|
||
- Fix: in `state.css` declare `--md-sys-state-hover-opacity: 0.08`, `focus 0.10`, `pressed 0.10`, `dragged 0.16`, `disabled-content 0.38`, `disabled-container 0.12` and use them in `state-layer` (add `&[data-dragged]::before { opacity: var(--md-sys-state-dragged-opacity) }`); add a `touch-target` utility (the `after:` pseudo-element from the button, `min 48×48`) so small icon buttons, chips, checkboxes, radios and switch share one implementation.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C11 · state.css · focus ring spec has no source on the M3 site
|
||
- Severity: nice-to-have (documentation)
|
||
- M3 says: only "a ring-like keyboard focus indicator" (reference-foundations §States; the site defines no thickness/offset/colour — see its "Cross-cutting gaps" 1).
|
||
- Library does: `focus-ring` = 3 px `secondary`, offset 2 px (`state.css:47-53`), which is material-web's `md-focus-ring` default (3 px, outward offset 2 px, secondary).
|
||
- Fix: none to the code; cite material-web in the header so nobody "corrects" it to the site's silence.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C12 · core · no tokens or variants for M3's breakpoints
|
||
- Severity: should-fix
|
||
- M3 says: five breakpoints — compact < 600, medium 600–839, expanded 840–1199, large 1200–1599, extra-large ≥ 1600 dp — with the navigation component, pane count and dialog/menu choice per breakpoint (reference-foundations §Layout breakpoints tables).
|
||
- Library does: nothing in the tokens; components switch on Tailwind's `sm` 640 / `md` 768 / `lg` 1024 / `xl` 1280 (the navigation audit maps each use). An application cannot write "at the medium breakpoint" at all.
|
||
- Fix: `theme.css` adds `--breakpoint-medium: 600px; --breakpoint-expanded: 840px; --breakpoint-large: 1200px; --breakpoint-extra-large: 1600px` (Tailwind 4 turns these into `medium:`, `expanded:`, `large:`, `extra-large:` variants; `max-medium:` etc. come for free), keeps `sm/md/lg/xl` for now, and the components migrate to the M3 variants in their own findings. Add pane tokens as custom properties: `--md-sys-layout-pane-fixed: 360px` / `412px`, `--md-sys-layout-side-sheet-max: 400px`, margins 16 px compact / 24 px otherwise (older M3 layout page values; the current site gives no numeric margin table — mark as such).
|
||
- Effort: S (tokens) — the component migration is counted in the other audits
|
||
- Breaks API? no
|
||
|
||
### C13 · DesignGuard · no check for values outside the M3 scale, hex colours, or `white`/`black`
|
||
- Severity: should-fix
|
||
- M3 says: "Always apply color roles, never static hex values or raw tonal-palette values" (reference-styles §Color rules).
|
||
- Library does: `src/Testing/DesignGuard.php:26-33` flags the Tailwind palette and daisyUI colours; it does not flag arbitrary values (`bg-[#1d7afc]`, `text-[rgb(…)]`), `bg-white`/`text-black` (re-added in `theme.css:21-22` and not M3 roles; the M3 white is `surface-container-lowest` in light), or any of the non-token utilities in C4.
|
||
- Fix: add patterns for `(bg|text|border|…)-\[#`, `-\[rgb`, `-\[hsl`, `-\[oklch`, and an opt-in `forbidAbsolutes()` for `white`/`black`; add the C4 list with a hint per match ("`rounded-lg` → `rounded-corner-lg`").
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C14 · theme-toggle · the picker is drawn as M3's deprecated segmented button
|
||
- Severity: should-fix
|
||
- M3 says: segmented buttons are "M3 only", replaced in M3 Expressive by the connected button group (reference-components-a §Segmented buttons / §Button groups; the plan doc lists segmented button among the six deprecated components).
|
||
- Library does: `resources/views/components/theme-toggle.blade.php:27-49` (`mode="picker"`) draws an outlined 40 px group with `border-outline` and `aria-checked` fills — the segmented-button anatomy — while `<x-group>` (the connected button group) already exists.
|
||
- Fix: render the picker with `<x-livewire-material::group>` (three `wire:model`-less radio options bound to `$store.theme`), or reuse its classes; keep `role="radiogroup"` and the arrow-key behaviour.
|
||
- Effort: S
|
||
- Breaks API? no (`data-theme-option` hooks can stay)
|
||
|
||
### C15 · scheme · harmonisation of the custom colours is not offered
|
||
- Severity: nice-to-have
|
||
- M3 says: "you can choose to harmonize your static colors to the scheme's primary color… while retaining the semantic meaning"; "colors can stay completely static and forgo harmonization if their values are tied to literal sources such as brand colors" (`styles/color/advanced/define-new-colors`).
|
||
- Library does: `customColor(..., blend:false)` always (scheme.mjs tail); no option.
|
||
- Fix: `--harmonize` flag on `material:scheme` (and `harmonize` per profile) passed as `blend:true`; default off, as today. Do together with C1.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C16 · icons · one weight/grade/optical size for every context
|
||
- Severity: nice-to-have
|
||
- M3 says: grade −25 "for a light icon on dark background"; optical size 20 for 20 dp icons in dense/desktop layouts; 40/48 for display pairing (reference-styles §Icons axes table).
|
||
- Library does: Material Symbols Rounded 400/0/24 only (`bin/fetch-symbols`, `icon.blade.php` header); buttons `xs`/`sm` draw 20 px icons from the 24-opsz outlines (`button.blade.php:147-149`), so strokes are ~17 % thinner than a true opsz-20 glyph; dark theme keeps grade 0.
|
||
- Fix: none that is cheap — each extra cut is another 4,135 SVGs (~2.6 MB). Record the trade-off in the icon header and SKILL.md; if ever needed, ship `opsz20` for the outlined set only and let `<x-icon size="20">` pick it.
|
||
- Effort: L
|
||
- Breaks API? no
|
||
|
||
### C17 · scheme · `background`/`on-background` are emitted, `surface-tint`/`surface-variant` are not
|
||
- Severity: nice-to-have (informational, aligned)
|
||
- M3 says: background/on-background are legacy roles with the same values as surface/on-surface; surface-variant was superseded by the surface-container family (Feb 2023); surface tint is deprecated (reference-styles §Color roles table).
|
||
- Library does: emits and maps `background`/`on-background` (`scheme.css:16-17`, `theme.css:29-30`), omits the two deprecated ones. Correct. Consider marking `bg-background` as "same as `bg-surface`; prefer surface" in SKILL.md so agents do not treat them as two surfaces.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C18 · scheme · the package default is the 2025 rendering of the baseline seed, not the site's baseline table
|
||
- Severity: nice-to-have (informational)
|
||
- M3 says: the baseline palette table (`PaletteTokens.kt`, site `styles/color/static/baseline`) is the 2021 spec: primary 40 = `#6750A4`, on-primary = white.
|
||
- Library does: `tokens/scheme.css` is `#6750a4` tonal-spot through the 2025 spec (`primary #655789`, `on-primary #fdf7ff`). Both are Google's; the header says so. Nothing to fix; the showcase colour page could say "spec 2025" so a reader comparing with the site's swatches is not puzzled.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C19 · elevation · no expression of "which component rests at which level"
|
||
- Severity: nice-to-have (documentation)
|
||
- M3 says: level 3 = dialogs, FAB, pickers, search; level 2 = menus, scrolled app bar, navigation bar, rich tooltip, toolbar; level 1 = elevated button/card/chip, modal sheets; level 0 = everything else; +1 level on hover (reference-styles §Elevation tokens table); scrim 32 %.
|
||
- Library does: shadow tokens only (`elevation.css`); the showcase shows five boxes with no mapping; components pick levels individually (correctly where I looked: menu 2, FAB 3, elevated button 1→2 on hover, `bg-scrim/32` ×4).
|
||
- Fix: put the table in the new guideline/skill and in the showcase elevation section, with M3's rule "tonal separation first, shadows for floating things and interaction".
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C20 · showcase · the foundation pages show tokens but not M3's rules for them
|
||
- Severity: nice-to-have
|
||
- M3 says: (as in C19, plus) surface container hierarchy by emphasis; outline vs outline-variant; type roles and when emphasized is used; spatial vs effects; corner scale per component class; optical roundness (outer − padding = inner).
|
||
- Library does: `showcase/sections/{colour,type,shape,elevation,motion,icons}.blade.php` list every utility with one sentence each; the colour page groups "Ink and lines" as if they were roles (see C3).
|
||
- Fix: one short "when to use" paragraph per section, drawn from the same guideline text (C24), so the showcase and the AI docs cannot drift.
|
||
- Effort: S
|
||
- Breaks API? no
|
||
|
||
### C21 · theme script · `data-theme` only, no `prefers-color-scheme` in CSS — aligned
|
||
- Severity: nice-to-have (no change)
|
||
- M3 says: light/dark is a user preference; honour the OS and the person's choice (reference-foundations §Accessibility principles; §Color "built-in dark theme").
|
||
- Library does: resolves `system` before first paint, follows OS changes while `system`, keeps the attributes across `wire:navigate`, declares `color-scheme` per block. Correct.
|
||
- Fix: none. (C2 adds contrast to the same mechanism.)
|
||
|
||
### C22 · reduced motion · stricter than the site, aligned with its intent
|
||
- Severity: nice-to-have (no change)
|
||
- M3 says: no global reduced-motion rule on `styles/motion`; component pages ask for parallax/expansion to be removed under reduced motion (reference-styles §Motion accessibility).
|
||
- Library does: zeroes every duration token (`motion.css:66-77`); carousel, progress, tabs and figure check the media query themselves. Correct; the header's "anything that animates without them is a bug" is a good rule to repeat in the guideline.
|
||
|
||
### C23 · icons · accessibility of `<x-icon>` — aligned
|
||
- Severity: nice-to-have (no change)
|
||
- M3 says: decorative icons hidden; meaningful icons labelled; don't put the role in the label (reference-foundations §Accessibility labelling).
|
||
- Library does: `aria-hidden` by default, `role="img"` + `aria-label` when `label` is given, `focusable="false"` (`icon.blade.php:27-33`). Correct.
|
||
|
||
### C24 · AI docs · the M3 core concepts are not written down anywhere an agent reads
|
||
- Severity: should-fix (this is the user's explicit request)
|
||
- M3 says: see the list under "Concepts missing from the AI docs" below.
|
||
- Library does: `resources/boost/guidelines/core.blade.php` (6 bullets: what the package is, no maryUI, theme script, showcase, error pages/mail) and SKILL.md §Tokens/§Theme/§Conventions list the utilities and three rules (elevation is for floating things; pair easing with duration; `dark:` follows `data-theme`). Nothing says what a role, a container level, a corner size, a type role or a spring is *for*, nor any layout/accessibility rule.
|
||
- Fix: a second always-on guideline `resources/boost/guidelines/material-3.blade.php` (short: the rules that change what an agent writes) and a fuller `resources/boost/skills/material-3-design/SKILL.md` (the concept reference with the tables), both generated from one source or tested against each other; Boost 2.8 loads every file under `resources/boost/guidelines` and every skill directory (verified in `laravel/boost/src/Install/GuidelineComposer.php:320-351`, `SkillComposer.php:127-141`). Add the drift test.
|
||
- Effort: M
|
||
- Breaks API? no
|
||
|
||
## Deliberate deviations
|
||
|
||
| Where | Deviation | Reason given | Holds up? |
|
||
| --- | --- | --- | --- |
|
||
| `shape.css` | `corner-full` = 9999px instead of material-web's `50cqmin` | container units need a declared container | Yes. Compose's `CircleShape` is 50 % of the smaller dimension = a stadium; 9999px gives the same on rectangles, `50%` would not. |
|
||
| `elevation.css` | `color-mix()` instead of `hsl(from …)` | browser floor | Yes; same result. |
|
||
| `motion.css` | springs sampled to `linear()` rather than Google's cubic-bezier approximations | closer to the physics | Yes for the curve; the durations differ from Google's published web numbers (C8). |
|
||
| `motion.css` | reduced motion zeroes durations | one place instead of per component | Stricter than the site; consistent with "Honor individuals". |
|
||
| `theme.css` | colour blocks are `@theme inline` | nested `data-theme` sections | Sound; verified by a browser test per the plan doc. |
|
||
| `theme.css` | white/black re-added | absolutes | M3 has no such roles; white is `surface-container-lowest` (light). Harmless, but the guard should at least be able to flag them (C13). |
|
||
| `theme.css` | `--color-meta/quiet/divider/chrome` opacity mixes | ReStride's templates | Does **not** hold against M3's text (C3): M3 dropped opacity-based emphasis with M3; 38 % is disabled. |
|
||
| `type.css` | Google Sans Flex for brand and plain, `ROND` 100 on emphasized | Expressive's typeface | Holds: the site names Google Sans Flex on the shape page and leaves the typeface to the product; `ROND` is that font's own axis, not an M3 token. |
|
||
| scheme | `surface-tint`/`surface-variant` omitted | superseded/deprecated | Holds (reference-styles §Color roles). |
|
||
| scheme | success/warning/info via `customColor`, unharmonised | "harmonisation off" | Off is allowed; the 2021 recipe and contrast-blindness are not (C1, C15). |
|
||
| `icon` | one Symbols cut, 400/0/24, SVG not font | any name works, no runtime download | Holds for weight; grade/optical size are lost (C16). |
|
||
| `theme-script` | OS never read by CSS | the script resolves `system` | Holds. |
|
||
|
||
## Aligned (keep as is)
|
||
|
||
- All 2025-spec colour roles generated and mapped, `*-dim` and fixed roles included; `scheme.json` and `Scheme.php` fill missing roles from the default; every pair (`x` / `on-x`) comes from Google's algorithm.
|
||
- Corner scale: none 0, xs 4, sm 8, md 12, lg 16, lg-increased 20, xl 28, xl-increased 32, xxl 48, full — exactly M3 Expressive's ten steps, same names.
|
||
- Elevation: five levels with material-web's shadow geometry; tonal separation preferred; scrim at 32 % where used.
|
||
- Motion: spatial 0.6/800, 0.8/380, 0.8/200; effects 1.0/3800, 1.0/1600, 1.0/800 — the Compose Expressive constants; legacy easing tokens present; reduced motion handled centrally and in the four scripted components.
|
||
- Typescale: all 15 sizes, line heights and weights match `TypeScaleTokens.kt`, emphasized set present (weights right).
|
||
- State layer: 8/10/10 %, content colour, hover only on `(hover: hover)`, disabled removes it; focus ring only on `:focus-visible`.
|
||
- Theme: `data-theme` resolved before paint, OS followed while `system`, choice persisted, legacy keys adopted, attributes survive `wire:navigate`, `color-scheme` declared, `dark:` variant on `data-theme`.
|
||
- Shapes: the 35 Expressive shapes ported from androidx, all fill the same box, decorative and hidden from AT.
|
||
- Symbols: Google's own 24 px Rounded files (not the 48-opsz npm cut), outlined and filled, `currentColor`, hidden unless labelled.
|
||
- Safe areas: `--material-safe-*` = M3's safety region.
|
||
- DesignGuard already enforces "roles, not palette" and "no maryUI/daisyUI".
|
||
|
||
## Missing (M3 things the core does not offer at all)
|
||
|
||
- Contrast levels medium/high (C2).
|
||
- Breakpoint tokens/variants and pane/margin tokens (C12).
|
||
- Dragged state, disabled tokens, a shared touch target (C10).
|
||
- Standard motion scheme (C9).
|
||
- Harmonisation option for custom colours (C15).
|
||
- An M3 concept guideline for agents (C24).
|
||
|
||
## Breakpoint map (core only)
|
||
|
||
| Item | Library | M3 breakpoint | Gap |
|
||
| --- | --- | --- | --- |
|
||
| tokens | none; Tailwind `sm` 640 / `md` 768 / `lg` 1024 / `xl` 1280 / `2xl` 1536 | compact <600 / medium 600 / expanded 840 / large 1200 / extra-large 1600 | no M3 breakpoint is expressible; nearest Tailwind values are 40–88 px off (see navigation audit for each component) |
|
||
|
||
## Concepts missing from the AI docs (input for the "Material 3 core concept" guideline)
|
||
|
||
Each line: what M3 says → whether the package docs say it today.
|
||
|
||
Colour
|
||
- Roles are "paint-by-number" slots; a colour is always a role, never a hex or palette tone → docs say "every colour class names an M3 role" (yes) but not why or the hex rule.
|
||
- Pair `x` with `on-x` only; a container is a fill, "on" is for text/icons on it; don't mix pairs (primary + secondary-container + on-surface) → no.
|
||
- Primary = high-emphasis actions; secondary = less prominent, tonal fills; tertiary = complementary accent; error = static semantic → no.
|
||
- Surface = page; surface-container-lowest…highest = emphasis hierarchy, not elevation; navigation on surface-container; same mapping for a region across breakpoints → no.
|
||
- `outline` for boundaries that must read (text fields), `outline-variant` for dividers and cards; never `outline` on dividers → no.
|
||
- Fixed/dim roles: "if you aren't sure, you probably shouldn't"; never where contrast matters → no.
|
||
- Inverse roles are for the snackbar/inverse surfaces only → no.
|
||
- success/warning/info are the package's custom roles, built like error; semantic colours are never dynamic-coloured away → no.
|
||
- Low emphasis is `on-surface-variant`, 38 % means disabled; links are primary **and** underlined → no (the opposite: `text-quiet` is documented as decoration).
|
||
- Contrast targets 4.5:1 small text, 3:1 large text/graphics, 3:1 clustered elements; disabled exempt → no.
|
||
- Three contrast levels exist → no.
|
||
|
||
Elevation
|
||
- Tonal separation first; shadows for floating elements and interaction only; the level table per component; +1 on hover; scrim 32 % → partly ("for what floats over content").
|
||
|
||
Shape
|
||
- Which corner for what: full for buttons/chips/FAB-menu, xs 4 for text fields/snackbar, sm 8 for chips, md 12 for cards, lg 16 for FAB, xl 28 for dialogs/sheets/menus in Expressive, xxl 48 for large containers → no.
|
||
- Optical roundness: inner radius = outer radius − padding → no.
|
||
- Shapes are decoration, never semantic; use sparingly; press morph is the interaction cue → no.
|
||
|
||
Typography
|
||
- Role purposes: display (big, short), headline (short, high emphasis), title (medium, short), body (paragraphs), label (inside components; buttons use label-lg) → no.
|
||
- Emphasized styles are opt-in, for selection, primary actions, headlines, badges — not decoration → no.
|
||
- 40–60 characters per line; tabular numbers where values change; don't change sizes when customising → no (only "never assemble text-* by hand").
|
||
|
||
Motion
|
||
- Spatial springs for position/size/shape (they overshoot), effects for colour/opacity (never overshoot); fast for small elements, default for most, slow for large → partly (the pairing rule, one sentence).
|
||
- Enter = decelerate, permanent exit = accelerate, temporary exit = emphasized; exits shorter than enters → no.
|
||
- Reduced motion: everything through the tokens goes instant; parallax/expansion removed → no.
|
||
|
||
States
|
||
- Six states; two visual indicators per state; hover 8 % / focus 10 % / press 10 % / drag 16 %; disabled 38 % content, 12 % container, no hover; state layer takes the content colour → no (utilities only).
|
||
- 48 × 48 target, 8 dp between targets, never below 48 by default, density is opt-in → no.
|
||
|
||
Layout
|
||
- Five breakpoints and what changes at each (navigation bar → collapsed rail → expanded rail; 1 → 2 → 3 panes; bottom sheet → menu; full-screen dialog → basic dialog) → no.
|
||
- Scaffold: bars, rails, panes; safety regions; pane widths 360/412, side sheet ≤ 400 → partly (safe areas documented).
|
||
- RTL: leading/trailing, mirror directional icons, use logical utilities (`ps-`, `ms-`, `start-`) → no.
|
||
|
||
Accessibility
|
||
- Landmarks (one main/banner/contentinfo; label repeated navs; no role in the label); headings in order, one H1; dialog focus in/out; labels for icon-only controls; decorative images hidden; keyboard shortcuts need a modifier → no.
|
||
|
||
Icons
|
||
- Filled = active/selected; keep one weight per group; 24 default, 20 in dense UI; label complex icons below 20; 48 target → partly (`filled` documented).
|