--- name: material-3-design description: Material 3 Expressive's design system as Livewire Material implements it — colour roles and surface containers, elevation, shape, type, motion, states and targets, window size classes, spacing, icons, accessibility — each M3 name beside the utility that draws it and Google's source page, for deciding how a screen should look and behave before writing it. --- # Material 3 design ## When to use this skill Use this skill when deciding how a screen, panel or control should look or behave — which colour, container, corner, type style, motion, breakpoint or spacing — in an application that requires `nonameweb/livewire-material`, and when reviewing a view against Material 3. The props and slots of each component are in the `livewire-material-development` skill; this one is the design language they implement. The rules an agent must always follow are in the `material-3` guideline; the tables here are what those rules compress. Every table pairs the M3 name with the library's utility or attribute. The tokens are CSS variables (`--md-sys-color-*`, `--md-sys-typescale-*`, `--md-sys-shape-*`, `--md-sys-elevation-*`, `--md-sys-motion-*`, `--md-sys-state-*`, `--md-sys-measurement-*`, `--md-ref-typeface-*`) and the utilities read them, so a template names a utility and never a variable or a value. ## Colour A colour scheme is generated from one seed by Google's colour science (`php artisan material:scheme`); every role below is a slot in that scheme, light and dark, at three contrast levels. A template names a role and nothing else — never a hex, a palette tone, an arbitrary value or an opacity — because only a role follows the theme, the contrast level and a colour profile. ### Roles | Role | Purpose | Its `on-` pair | Utility | | --- | --- | --- | --- | | primary | High-emphasis fills, text and icons: the key action on a screen | on-primary | `bg-primary text-on-primary`, `text-primary` | | primary-container | A standout fill for key components (FAB, an emphasised panel) | on-primary-container | `bg-primary-container text-on-primary-container` | | primary-dim | A darker primary for a pressed or contrasting fill (2025 spec) | on-primary | `bg-primary-dim` | | secondary | Less prominent fills, text and icons | on-secondary | `bg-secondary text-on-secondary` | | secondary-container | The recessive fill: tonal buttons, selected navigation, selected chips | on-secondary-container | `bg-secondary-container text-on-secondary-container` | | tertiary | A complementary accent, used sparingly for contrast | on-tertiary | `bg-tertiary text-on-tertiary` | | tertiary-container | The complementary fill | on-tertiary-container | `bg-tertiary-container text-on-tertiary-container` | | error | Urgency and errors; static, does not follow dynamic colour | on-error | `text-error`, `bg-error text-on-error` | | error-container | An error panel | on-error-container | `bg-error-container text-on-error-container` | | success, warning, info | This library's custom state colours, built like error on the 2025 spec, with `-container` and `on-` pairs | on-success … | `text-success`, `bg-warning-container text-on-warning-container` | | surface | The page background | on-surface | `bg-surface text-on-surface` | | on-surface-variant | Lower-emphasis text and icons on any surface | — | `text-on-surface-variant`, `text-body`, `text-meta` | | outline | A boundary that must be read: a text field, a target's edge (3:1 against surface) | — | `border-outline`, `text-outline`, `text-quiet` | | outline-variant | Decorative lines: dividers, card edges | — | `border-outline-variant`, `border-divider`, `border-structure`, `border-chrome`, `divide-divider` | | inverse-surface | A surface that contrasts with its surroundings (the snackbar) | inverse-on-surface | `bg-inverse-surface text-inverse-on-surface` | | inverse-primary | An action on an inverse surface (the snackbar's action) | — | `text-inverse-primary` | | scrim | Behind a modal, at 32% | — | `bg-scrim/32` | | shadow | The shadow colour, inside `shadow-elevation-*` | — | — | | surface-dim, surface-bright | Add-on surfaces that keep their relative brightness in both themes | on-surface | `bg-surface-dim`, `bg-surface-bright` | | primary-fixed, primary-fixed-dim, on-primary-fixed, on-primary-fixed-variant (and secondary, tertiary) | Add-on roles with the same tone in light and dark; for a colour that must not change with the theme; never where contrast matters | — | `bg-primary-fixed text-on-primary-fixed` | Pairing: a role's `on-` pair is the only combination whose contrast is guaranteed at every contrast level. `bg-primary text-on-primary` and `bg-secondary-container text-on-secondary-container` are right; `bg-primary-container text-on-surface` or `bg-secondary-container text-primary` are not, and break as the contrast level rises. Google: "Pair and layer color roles only as intended … Don't mix roles improperly." ### Surface containers A hierarchy of emphasis, not of height: the tone separates panels before any shadow does, and a region keeps its role at every breakpoint (body always `surface`, navigation always `surface-container`). | Role | Use | Utility | | --- | --- | --- | | surface | The page | `bg-surface` | | surface-container-lowest | The most recessed panel; an elevated card's body in dark themes | `bg-surface-container-lowest` | | surface-container-low | An elevated card, a modal bottom or side sheet, the full-screen search view | `bg-surface-container-low` | | surface-container | Navigation bar and rail, docked and floating toolbars, menus, the segmented list | `bg-surface-container` | | surface-container-high | Dialogs, the search bar, date and time pickers, a rich tooltip | `bg-surface-container-high` | | surface-container-highest | A filled card, a filled text field, a filled chip's selected state | `bg-surface-container-highest` | ### Emphasis and lines - Default ink is `on-surface`; lower emphasis is `on-surface-variant`; decoration is `outline`. Emphasis is never an opacity: M3 reserves 38% (`/38`) for disabled content and 12% (`/12`) for a disabled container. - `outline` for a boundary that has to be perceived (a text field's edge, a target's edge — 3:1 against the surface); `outline-variant` for dividers and the edge of a card or any component holding several elements. Google: "Don't use the outline color for dividers … use outline variant instead." `outline-variant` may edge a chip or a button only because the content inside already carries the contrast. - A hyperlink in running text is `primary` (or `tertiary` for a quieter link) **and** underlined: the `link` utility. ### Contrast | Level | Target | How | | --- | --- | --- | | Standard | Hierarchy from high- and low-contrast elements together; text 4.5:1, large text and icons 3:1, grouped non-text controls 3:1 | the default scheme | | Medium | 3:1 minimum everywhere, without halation | `` | | High | 7:1 | ``, or the visitor's OS setting (`theme.contrast.default` = `system`) | Every role changes with the level automatically; a component built from roles needs nothing else. Disabled states are exempt from contrast. A colour outside the roles (a hex, `white`, `black`) does not change and is the one thing that breaks a contrast level. Sources: https://m3.material.io/styles/color/roles · https://m3.material.io/styles/color/system/how-the-system-works · https://m3.material.io/styles/color/advanced/apply-colors · https://m3.material.io/foundations/designing/color-contrast ## Surfaces and elevation M3 separates surfaces by tone first; a shadow says that something floats over the content or is being interacted with. "When it comes to applying shadows, less is more." | Level | Shadow | Rests here | Utility | | --- | --- | --- | --- | | 0 | none | The page, cards (filled, outlined), buttons (filled, tonal, outlined), button groups, icon buttons, lists, chips, tabs, sliders, the rail, a docked side sheet, a carousel, a full-screen dialog, a FAB inside the rail, an app bar at rest | — | | 1 | 1dp | Elevated cards, elevated buttons and chips, modal bottom and side sheets, a banner | `shadow-elevation-1` | | 2 | 3dp | Menus, the navigation bar, a scrolled app bar, toolbars, rich tooltips | `shadow-elevation-2` | | 3 | 6dp | FAB and extended FAB, the FAB menu's close button, dialogs, date and time pickers, the search bar | `shadow-elevation-3` | | 4 | 8dp | Interaction only: a level-3 element on hover or while dragged | `shadow-elevation-4` | | 5 | 12dp | Interaction only | `shadow-elevation-5` | - Hover lifts an element one level (a FAB 3 → 4, an elevated card 1 → 2); focus and selection may too; a raised element lowers when something higher appears. - Overlapping panels take different surface-container roles to show separation; the roles are not tied to the levels. - A scrim (`bg-scrim/32`) brings focus to a modal over a large surface; it is never a substitute for a shadow on a small floating element. - On a dark surface a shadow is nearly invisible, so the tone does the work there. Sources: https://m3.material.io/styles/elevation/overview · https://m3.material.io/styles/elevation/applying-elevation · https://m3.material.io/styles/elevation/tokens ## Shape ### The corner scale | Style | Value | Utility | | --- | --- | --- | | None | 0 | `rounded-corner-none` | | Extra small | 4px | `rounded-corner-xs` | | Small | 8px | `rounded-corner-sm` | | Medium | 12px | `rounded-corner-md` | | Large | 16px | `rounded-corner-lg` | | Large increased | 20px | `rounded-corner-lg-increased` | | Extra large | 28px | `rounded-corner-xl` | | Extra large increased | 32px | `rounded-corner-xl-increased` | | Extra extra large | 48px | `rounded-corner-xxl` | | Full | a stadium or circle | `rounded-corner-full` | Tailwind's `rounded-sm`, `rounded-lg`, `rounded-full`, … do not compile. The `-t`, `-s`, `-e` sides work (`rounded-t-corner-xl` for a bottom sheet). ### Corner by component | Component | Corner | Note | | --- | --- | --- | | Buttons, icon buttons, split button (outer), FAB menu items | full | a press morphs to `md` (xs/sm sizes), `lg` (md), `xl` (lg/xl); a selected toggle swaps round ↔ square | | Connected button group | full outside, `sm` between segments | segments press to `xs` | | FAB | `lg` 16 (baseline 56px), `lg-increased` 20 (medium 80px), `xl` 28 (large 96px) | extended FAB `lg` | | Chips | `sm` 8 | an avatar in a chip `md` 12 | | Cards | `md` 12 | no change on hover | | Text fields | `xs` 4 (outlined: all corners; filled: top corners only) | | | Menus, snackbar, plain tooltip | `xs` 4 | the Expressive vertical menu rounds the focused item | | Rich tooltip | `md` 12 | | | Dialogs | `xl` 28 | full-screen dialog `none` | | Bottom sheet | `xl` 28 on top | | | Side sheet | `lg` 16 on the inner side | | | Search bar | full | search view `xl` 28 when docked, `none` full-screen | | Date and time pickers | `xl` 28 | date cells full | | Carousel items | `xl` 28 | | | Navigation indicator, badges, switch, slider handle, checkbox state layer | full | checkbox box 2px, tab indicator 3px on top | | Navigation bar, app bar, docked toolbar, tabs | none | floating toolbar full | | Segmented list rows | `xs` inner, `lg` outer; a selected row `lg` | | ### Rules - Optical roundness: a shape nested in a rounded container takes inner radius = outer radius − padding (48 − 14 = 34), never the container's own radius. - Large and full corners do not belong on information-dense containers (cards, tables, text fields). - A press squares a round shape and rounds a square one (the components carry the morph on the fast spatial spring); nothing morphs on hover. - The 35 Expressive shapes (``, also the loading indicator and the standard button group's press shape) are decoration for emphasis and delight — never a carrier of meaning, never behind text-heavy content, and used sparingly. Sources: https://m3.material.io/styles/shape/corner-radius-scale · https://m3.material.io/styles/shape/shape-morph · https://m3.material.io/styles/shape/overview-principles ## Type The typeface is Google Sans Flex for brand and plain styles (`font-sans`, `--md-ref-typeface-brand`); an application may replace it after importing the stylesheet. Each style is one utility that sets size, line height, weight, family and tracking together; `text-sm`, `font-medium`, `leading-*` and `tracking-*` do not compile. | Role | Style | Size / line | Weight | Utility | Use for | | --- | --- | --- | --- | --- | --- | | Display | large / medium / small | 57/64 · 45/52 · 36/44 | 400 | `type-display-lg` … | hero figures, one short marketing line; never running text | | Headline | large / medium / small | 32/40 · 28/36 · 24/32 | 400 | `type-headline-lg` … | page titles, section titles, a dialog's headline (`headline-sm`) | | Title | large / medium / small | 22/28 · 16/24 · 14/20 | 400 / 500 / 500 | `type-title-lg` … | app bar title (`lg`), card and list-section titles (`md`), dense headers (`sm`) | | Body | large / medium / small | 16/24 · 14/20 · 12/16 | 400 | `type-body-lg` … | paragraphs (`lg` for reading, `md` in components), supporting text (`sm`) | | Label | large / medium / small | 14/20 · 12/16 · 11/16 | 500 | `type-label-lg` … | buttons and tabs (`lg`), chips and navigation (`md`), captions and badges (`sm`) | - `type-emphasized-*` is the same size and line height one weight step heavier (400 → 500, 500 → 700), fully rounded in Google Sans Flex, with its own tracking. M3 uses it deliberately, never by default: a selected list or menu item, a button's label on a primary action, an extended FAB, a badge, a headline given editorial weight. - Tracking follows Compose's `TypeScaleTokens`: display-large −0.2, title-medium 0.2, title-small 0.1, body-large 0.5, body-medium 0.2, body-small 0.4, label-large 0.1, label-medium and small 0.5 (sp; rem = sp/16); the emphasized set tightens a few (display-large 0, title-medium 0.15, body-large 0.15, body-medium 0.25). - Line length 40–60 characters (`max-w-prose` is close; `max-w-[60ch]` is exact). Figures that change take `tabular-nums`. - Text must scale to 200%: containers grow, side-by-side controls stack, padding stays; components without text (progress, checkboxes) do not scale. Truncate to an ellipsis only when the full text is one tooltip or link away. - When customising, change the typeface or tracking, never the sizes: component layout depends on them. Sources: https://m3.material.io/styles/typography/type-scale-tokens · https://m3.material.io/styles/typography/applying-type · https://m3.material.io/styles/typography/fonts · https://m3.material.io/foundations/writing/text-resizing · https://m3.material.io/foundations/writing/text-truncation ## Motion M3 Expressive moves on physics: every transition is a spring, and the library samples each spring into a CSS `linear()` easing paired with a duration. Use the pair together, or the curve is stretched over the wrong time. | Spring | Damping / stiffness | Duration | Utility | For | | --- | --- | --- | --- | --- | | Spatial fast | 0.6 / 800 | 350ms | `ease-spatial-fast duration-(--md-sys-motion-spatial-fast-duration)` | small elements: a button's press morph, a switch, a chip | | Spatial default | 0.8 / 380 | 500ms | `ease-spatial-default duration-(--md-sys-motion-spatial-default-duration)` | most position, size and shape changes | | Spatial slow | 0.8 / 200 | 650ms | `ease-spatial-slow duration-(--md-sys-motion-spatial-slow-duration)` | large surfaces: a sheet, a pane, a full-screen transition | | Effects fast | 1.0 / 3800 | 150ms | `ease-effects-fast duration-(--md-sys-motion-effects-fast-duration)` | state layers, small fades | | Effects default | 1.0 / 1600 | 200ms | `ease-effects-default duration-(--md-sys-motion-effects-default-duration)` | most colour and opacity changes | | Effects slow | 1.0 / 800 | 300ms | `ease-effects-slow duration-(--md-sys-motion-effects-slow-duration)` | large fades, a scrim | - Spatial springs are underdamped and overshoot — that bounce is what reads as Expressive — so they carry only position, size and shape. Effects springs are critically damped and carry colour and opacity, which must never overshoot. A `transition-all` mixes the two and is wrong. - The Standard motion scheme (``, config `motion.scheme`) swaps the spatial springs for stiffer ones with almost no bounce (0.9 / 1400, 700, 300; 350, 500, 750ms) for utilitarian products; effects are shared. - Direction: something entering decelerates (`ease-emphasized-decelerate`, or a spatial spring from off-screen), a permanent exit accelerates (`ease-emphasized-accelerate`), a temporary exit that can be recalled (a drawer, a sheet) takes `ease-emphasized`; exits are shorter than entrances, and larger areas move longer. - The cubic-bezier set (`ease-standard`, `ease-emphasized`, `ease-emphasized-decelerate`, `ease-emphasized-accelerate`, `--md-sys-motion-duration-short|medium|long`) is for the few transitions whose duration is fixed from outside: a view transition, an animated scroll. - Reduced motion zeroes every duration token, so anything animated through them turns instant; a literal `duration-300` or a keyframe with its own timing ignores the visitor's setting and is a bug. Container transforms, parallax and expansions are removed, not slowed. Sources: https://m3.material.io/styles/motion/overview · https://m3.material.io/styles/motion/overview/specs · https://m3.material.io/styles/motion/easing-and-duration/tokens-specs · https://m3.material.io/styles/motion/transitions/transition-patterns ## States and targets | State | Layer | Utility or hook | Also | | --- | --- | --- | --- | | Enabled | none | — | | | Hover | 8% of the content colour | `state-layer` (pointer devices only) | one level of elevation on floating elements | | Focused | 10% | `state-layer focus-ring` (keyboard focus: a 3px `secondary` ring, 2px out) | only one focused element at a time | | Pressed | 10% | `state-layer` (`:active`) | the shape morph on buttons | | Dragged | 16% | `state-layer` with `data-dragged` | one level of elevation | | Disabled | content 38%, container 12%, no state layer, not focusable | `disabled:text-on-surface/38 disabled:bg-on-surface/12` | exempt from contrast; a FAB is hidden rather than disabled | | Selected | the `secondary-container` pair, a filled icon, the emphasized style | component props (`selected`, `aria-selected`, `aria-pressed`) | combines with hover, focus and press | - The state layer takes the content's `on-` colour (on `secondary-container` it is `on-secondary-container`), is 40px on a 48px target, and only one shows at a time. - Every state shows two indicators, so a colour change alone is never a state: add a shape, an outline, an icon, a weight or a word (`aria-selected` plus the container, an error colour plus an icon and a message). - Targets: 48×48px minimum, 8px between targets, on every device; `touch-target` extends a smaller drawing to 48px. Density is an opt-in prop (`dense`) that steps padding by 4px and never applies to menus, snackbars, dialogs or settings controls, and never takes a target below 48px. - Keyboard: Tab and Shift+Tab between components in DOM order, arrows within a component (menu, tabs, grid, radio group), Enter and Space activate, Escape dismisses; a dialog moves focus in on open and back to its opener on close. Sources: https://m3.material.io/foundations/interaction/states/state-layers · https://m3.material.io/foundations/interaction/states/applying-states · https://m3.material.io/foundations/designing/structure · https://m3.material.io/foundations/layout/grids-spacing/density ## Layout and breakpoints Layout keys on the width of the window, in M3's five window size classes; they are the only responsive variants that compile, and `resources/js/breakpoints.js` (`from('expanded')`, `upTo('medium')`) gives scripts the same numbers. | Class | Width | Variant | Navigation | Panes | Dialogs and choices | Margins | | --- | --- | --- | --- | --- | --- | --- | | Compact | below 600px | the default; `max-medium:` for "only here" | navigation bar; the rail opens as a modal | 1 | full-screen or basic dialog; a bottom sheet for choices | 16px | | Medium | 600–839px | `medium:` | collapsed rail (96px) | 1, or 2 for low-density content at 50% each | basic dialog; a menu for choices | 24px | | Expanded | 840–1199px | `expanded:` | rail, collapsed or expanded, collapsible | 2 recommended; a fixed pane 360px | basic dialog; menu | 24px | | Large | 1200–1599px | `large:` | rail expanded | 2; a fixed pane 412px | basic dialog; menu | 24px | | Extra-large | 1600px and up | `extra-large:` | rail expanded | 2, or 3 with a standard side sheet (at most 400px) | basic dialog; menu | 24px | - `` implements the navigation column; `` is the second pane of a list-detail layout from `expanded:`, `` puts a supporting pane (360px, beside the focus pane) from `expanded:` and below it before that. Moving up a class, ask what to reveal, divide into panes, resize, reposition or swap — never swap a component for one that does not do the same job. - Scaffold: bars (app bar at the top, navigation bar at the bottom: 3–5 destinations), rails (the navigation rail, toolbars, the FAB, on the leading edge), panes (all content), around a safety region that stays clear of the device's own chrome (`--material-safe-top|bottom|left|right`). - Canonical layouts: feed (a grid of cards that gains columns per class), list-detail (one pane on compact, two from expanded; a back button only in single-pane mode, a selected row only in two-pane mode), supporting pane (two thirds focus, one third support). - Bidirectionality: write logical properties (`ps-4`, `me-2`, `start-0`, `text-start`, `border-s`); leading and trailing icons swap, directional icons (back, send) mirror, the rail moves to the right; charts, media controls, clocks and Hebrew progress bars stay left-to-right. Sources: https://m3.material.io/foundations/layout/breakpoints/overview · https://m3.material.io/foundations/layout/breakpoints/compact (medium, expanded, large-extra-large) · https://m3.material.io/foundations/layout/scaffold/overview · https://m3.material.io/foundations/layout/canonical-examples/overview · https://m3.material.io/foundations/layout/bidirectionality-rtl ## Spacing M3's spacing tokens are multiples of an 8px base on a 4px grid; Tailwind's spacing scale is that grid, so `p-4` is `space200`. | Token | Value | Utility | | --- | --- | --- | | space25 | 2px | `p-0.5`, `gap-0.5` | | space50 | 4px | `p-1` | | space75 | 6px | `p-1.5` | | space100 | 8px (the base) | `p-2`, `gap-2` | | space125 | 10px | `p-2.5` | | space200 | 16px | `p-4` — a component's padding, compact margins | | space300 | 24px | `p-6` — a dialog's padding, margins from `medium:` | | space400 | 32px | `p-8` | | space500 | 40px | `p-10` | | space600 | 48px | `p-12` — a target | | space700 | 56px | `p-14` | | space800 | 64px | `p-16` | | space900 | 72px | `p-18` | - Padding and gaps live on the parent (`p-4 gap-2` on the container), never as margins on children; a margin is for space beyond a container's padding or between layout regions. - Spacing does not scale with text: at 200% text size the same padding and gaps stay. - Name a gap by what it separates when a component has several (icon–label 8px, label–supporting text 4px). Sources: https://m3.material.io/styles/spacing/overview · https://m3.material.io/styles/spacing/tokens · https://m3.material.io/styles/spacing/applying-spacing ## Icons `` draws a Material Symbol Rounded (weight 400, grade 0), outlined or `filled`, at optical size 24 or `optical="20"`. | Axis | Values | In this library | | --- | --- | --- | | Fill | 0 outlined, 1 filled | `filled` — active, selected or on state (a selected navigation item, a FAB's icon, a checked filter chip) | | Weight | 100–700; never below 200 at 24px | 400 for every icon; one weight per group | | Grade | −25 on dark backgrounds, 0 otherwise, positive for emphasis | 0 | | Optical size | 20 dense, 24 standard, 40–48 with display type | `optical="20"` when drawn at 20px or less (small buttons, chips, dense lists), 24 otherwise | - An icon beside text takes the text's size and colour (`size-5` beside `type-label-lg`, `size-6` beside body) and the same optical weight; its baseline sits about 11.5% of the text size below the text's. - Icons stay flat and forward-facing, on the pixel grid, inside their 20px live area of the 24px canvas. - An icon-only control has an accessible name (`aria-label`, or a tooltip that names it); a decorative icon is `aria-hidden`; a complex icon drawn below 20px needs a label beside it. Sources: https://m3.material.io/styles/icons/overview · https://m3.material.io/styles/icons/designing-icons · https://m3.material.io/styles/icons/applying-icons ## Accessibility - Native elements before ARIA: `