Files
livewire-material/UPGRADE.md
T
Andreas Reinhold / reiniandClaude Opus 5 f2a3c4d056 Size the loading indicator and publish the rail's value
Two gaps found rewriting the Boost skill for 2.0's plain CSS, each
closed the way M3 describes it.

`<x-loading size="96">`: M3 gives the loading indicator a responsive
size, 48dp by default and flexible from 24 to 240dp ("never exceed that
range"), with the container and the active shape in a fixed ratio. The
size had no prop, so an application wrote a width and height of its own;
`size` now takes a whole number of px in that range, written as
`--md-loading-size`, and the SVG keeps the 48:38 ratio as it scales. A
value outside the range is ignored, as `<x-icon>` and `<x-shape>` ignore
theirs, and an application's own width and height still win.

`--md-navigation-rail-value`: M3 Expressive's rail has two values,
collapsed and expanded (Compose's WideNavigationRailValue), and content
in a rail follows it. Without a hook an application copied the rail's
seven conditions — mode, `data-rail`, `data-rail-auto`, open, and the
window band — out of navigation-rail.css. The rail now publishes the
answer from the same branches that narrow it: `expanded` by default,
`collapsed` wherever it is drawn collapsed, so a style query in the
application's CSS switches at the first paint and at the same moment
as the rail's own items. A rail open over a scrim reads `expanded`, and
outside a rail the property is unset. A JS attribute would have missed
the first paint; a width container query would have lagged the collapse
animation.

Style queries on a custom property need Firefox 151, so the browser
floor moves from Firefox 147 to 151 (README, the CI note, UPGRADE's new
2.1.0 section); Chrome 125 and Safari 18.4 are unchanged.

Browser tests pin both in Chrome, Firefox and Safari: the indicator's
drawn box at 96 and 32px, and the rail's value — with a style query
acting on it — at the first paint for fixed modes, across the window
classes and the menu button for an adaptive rail, and open and closed
for a modal one. NavigationRailTest pins the value in each of the five
collapsed branches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:06:16 +02:00

15 KiB

Upgrading

From 2.0.0 to 2.1.0

  • Browsers: Firefox 151 or later (was 147), for container style queries on a custom property; Chrome 125 and Safari 18.4 are unchanged.
  • <x-navigation-rail> publishes its value as --md-navigation-rail-value, collapsed or expanded (M3's two rail values). Content an application puts in a rail reads it with @container style(--md-navigation-rail-value: collapsed) instead of copying the rail's conditions from navigation-rail.css.
  • <x-loading size="96"> sizes the loading indicator in px, 24 to 240 (M3's responsive range), with the container and the shape in proportion. A width and height of the application's own still work.

From 1.x to 2.0.0

2.0.0 aligns the library with Material Design 3 Expressive as Google documents it (m3.material.io, checked page by page; the audits are in docs/audits/m3-alignment/). Most of the change is inside the components. What reaches an application is below, in the order to do it.

1. Breakpoints are M3's window size classes

Tailwind's sm:, md:, lg:, xl: and 2xl: no longer compile, and there are no responsive variants in their place: the breakpoints are M3's window size classes — compact below 600px, then medium (600px), expanded (840px), large (1200px) and extra-large (1600px) — and only those. A layout component takes the class as a prop (hide-below, hide-from, stack-below, <x-grid>'s columns map); the application's own CSS writes the width as a range media query:

Was Becomes
sm: / max-sm: (640) medium · @media (width >= 600px) / (width < 600px)
md: / max-md: (768) medium or expanded — choose by what the layout means
lg: / max-lg: (1024) expanded · @media (width >= 840px) / (width < 840px)
xl: / max-xl: (1280) large · @media (width >= 1200px) / (width < 1200px)
2xl: (1536) extra-large · @media (width >= 1600px)

<div class="hidden lg:block"> is <x-stack hide-below="expanded">; flex flex-col sm:flex-row is <x-row stack-below="medium">; grid-cols-1 lg:grid-cols-2 is <x-grid :columns="['compact' => 1, 'expanded' => 2]">. Scripts read the same numbers from resources/js/breakpoints.js (from('expanded'), upTo('medium')). DesignGuard reports every leftover prefix with its replacement.

2. Only M3's scales, as tokens

Tailwind's radius, shadow, text-size, weight, leading, tracking and easing utilities compile to nothing, and so do the 1.x utilities that stood for M3's scales. A text style is a class; the rest is a token in the application's own CSS:

Was Becomes
rounded-smrounded-4xl, rounded-corner-* (1.x) var(--md-sys-shape-corner-xs)var(--md-sys-shape-corner-xxl) (-full, -none), or <x-surface corner="xs">
shadow-smshadow-2xl, shadow-elevation-* (1.x) var(--md-sys-elevation-1)var(--md-sys-elevation-5)
text-xstext-9xl, leading-*, tracking-*, font-mediumfont-black, type-* (1.x) one md-type-* class (md-type-body-md, md-type-emphasized-title-md …), or font: var(--md-sys-typescale-body-md) with its -tracking
ease-in, ease-out, ease-in-out, duration-300, ease-spatial-* (1.x) var(--md-sys-motion-spatial-*) / var(--md-sys-motion-effects-*) with its -duration, in a transition
gap-4, p-4, space-y-2 gap="space200", <x-surface padding="space200">, <x-stack gap="space100">, or var(--md-sys-measurement-space200)
state-layer, focus-ring, touch-target, link (1.x) md-state-layer, md-focus-ring, md-touch-target, md-link

The spacing tokens are the 4px grid Tailwind's scale was (space200 is 16px). DesignGuard names each one with its replacement.

3. Regenerate the colour scheme

php artisan material:scheme "#4f46e5" --variant=tonal-spot   # the command in the file's header
  • success, warning and info are now built on the 2025 colour spec with the contrast level, like error; their values change. --harmonize blends them toward the seed (off by default).
  • The stylesheet gains medium and high contrast blocks, and material-scheme.json gains a contrast key; light/dark at the top level are still the standard scheme, so the mail theme needs nothing. --contrast must now be below 0.5; medium (0.5) and high (1.0) are generated alongside.
  • Config: theme.contrast (default system | standard | medium | high, storage_key) and motion.scheme (expressive | standard). $store.theme gains contrast, resolvedContrast and setContrast(); <x-theme-toggle mode="contrast"> is a row of three.

4. Inks are roles, not opacities

1.x's text-meta, text-body, text-quiet, border-chrome, border-structure and border-divider are gone with the rest of the utilities. Text takes the role's md-ink-* class — md-ink-variant (on-surface-variant) for text-meta and text-body, md-ink-quiet (outline) for text-quiet — and a line is <x-divider>, <x-surface outlined> or var(--md-sys-color-outline-variant) in the application's CSS. Where the old translucent grey was intended, the role is the same colour; where a template relied on the opacity to blend over a colour, use the role directly.

5. Changed defaults and props

  • <x-toast> no longer draws a state icon; type only picks the announcement role (alert for error and warning, status otherwise). An actioned snackbar stays until acted on; Escape dismisses a focused one.
  • <x-fab> has no disabled prop: M3 says to remove a FAB whose action is unavailable, so hide it instead (a form-submit FAB uses wire:loading.attr="disabled").
  • <x-alert> is role="status" for every colour; pass assertive where the notice answers something the person just did.
  • <x-button size="xs"> and <x-group size="xs"> are 8px wider; a <x-button-group> no longer wraps; connected xs/sm segments have a 48px minimum width.
  • <x-menu-item> rows are 48px with 16px sides; a selected item draws a trailing check unless it has icon-right; a long menu scrolls.
  • <x-drawer> renders a close button by default (:with-close-button="false" to drop it, ignored on a standard sheet or when Escape and the scrim are off) and left-aligns its actions in a 72px row. <x-drawer pane> is removed; use <x-list-detail> for the second pane of a list-detail layout.
  • <x-bottom-sheet> opens at 50dvh (was 90dvh); any height is capped at calc(100dvh - 72px).
  • <x-carousel> padding defaults to 16; layout="full-screen" scrolls vertically with edge-to-edge items; items, not the row, are the tab stops.
  • <x-list dividers> draws a 16px-inset rule; <x-list selectable> or selection="single|multi" makes it a listbox of options with aria-selected.
  • <x-card> changes elevation on hover instead of its corner; data-md-card carries the variant.
  • A button directly in <x-stack> (stretched, the default) or <x-form> keeps its label's width at the start edge instead of filling the width, as M3 asks; a full-width submit is your own CSS.
  • <x-modal> pins its header and actions and scrolls only the body; a box-class that set overflow no longer applies.
  • <x-table> rows are 52px and the automatic fine-pointer density is gone: pass dense for 36px rows (size="xs" dense is 24px).
  • A field in error draws a trailing error icon unless it already trails something; a read-only field no longer draws a dashed outline.
  • The time picker's AM/PM buttons are radios (aria-checked); the password reveal no longer sets aria-pressed.
  • <x-slider size="md"> grows from 44 to 52px and every value indicator from 32 to 44px tall.
  • <x-theme-toggle mode="picker"|"contrast"> is a connected group over native radios: the data-theme-option hooks are gone; target input[name="material-theme"][value="…"].
  • <x-tab> panels render id, aria-labelledby and display: none from the server.
  • <x-section-nav> with five or more sections is a scrollable tab bar, not a grid. The tab bar is 10px taller; the focus ring sits outside.
  • The navigation bar's horizontal label is label-md; place="bottom" toolbars sit above --material-bottom-bar; a standard toolbar's icon buttons are primary. An application that set --material-bottom-bar to lift the snackbar over its own bottom toolbar removes it: the toolbar now reads it to place itself, and publishes --material-bottom-toolbar, which the snackbar clears and the page pads its end with.
  • The centred app bar headline is a grid column; the search variant bounds its own width, so drop hand-written max-w-* wrappers.
  • <x-app-shell> is renamed <x-scaffold>, with no alias; it is a column with a nested row and gains banner and fab slots. <x-navigation-rail>'s header slot takes one <x-fab label icon> that morphs (replace the two-FAB rail-collapsed: swap); new divider and fill props. The rail-collapsed: variant is gone: style what the application puts in a rail by the rail's value, @container style(--md-navigation-rail-value: collapsed) (2.1.0). data-app-shell, data-app-shell-bar, data-app-shell-actions and data-app-shell-banner are data-md-scaffold, data-md-scaffold-bar, data-md-scaffold-actions and data-md-scaffold-banner; the skip link is data-md-skip-link.
  • <x-icon optical="20"> selects the optical-size-20 cut; the components pass it for their own small icons, applications pass it for icons drawn at 20px or less.
  • A text field stops at 40rem wide from medium (600px), as M3 bounds fields on wider windows; a width rule of the application's own on the field beats it, and full removes it.
  • Every <x-modal> draws a rule under its header and over its actions while its body scrolls, and neither when the body fits. separator now means "always draw both rules" and no longer renders two <x-divider> elements; the spacing between header, body and actions moved to M3's split gaps, so a dialog that fits is a few pixels shorter.
  • A snackbar with a description is 68px tall, and below medium (600px) a two-line snackbar with an action puts the action under the text. Alt+G moves focus to an actioned snackbar.
  • <x-bottom-sheet>'s drag now follows the pointer and settles on the nearest preset height when heights or snap is set; without stops it behaves as before.
  • A chip set with scroll shows a scroll button over each overflowing edge on fine pointers.

6. material.css is gone, and so is Tailwind

The 1.x single-import shortcut no longer exists, and Tailwind has left the whole stack — the package, its showcase, error pages and Workbench build carry none, and an application drops it too. The package's CSS is plain, no build step of its own, in one entry: resources/css/all.css for everything, or foundation.css first and then the stylesheet of each component the views render, opening with the layer statement every package stylesheet does (see Installation in README.md). An application's views write no utility layer of their own either: layout components (<x-scaffold>, <x-pane>, <x-stack>, <x-row>, <x-grid>, the canonical layouts) take M3's spacing tokens and breakpoints as props, a small set of md-type-* and md-ink-* classes covers text on plain elements, and --md-sys-* custom properties serve the rest of an application's own stylesheet. The foundation smooths text in grayscale, as Tailwind's antialiased class did: drop the class.

7. New in 2.0.0

Nothing to migrate, but worth knowing: submenus (<x-menu-item submenu>), a filtering menu (<x-menu filter>), a menu that opens as a bottom sheet on compact windows (<x-menu sheet-at-compact>), gap-grouped menu items (<x-menu-group gap>), square button groups (shape="square"), managed selection on connected groups (selection="single|multi" required), FAB collapse on scroll (<x-fab collapse-on-scroll>), the tall navigation bar and hide-on-scroll (tall, hide-on-scroll; tall-bar, hide-bar-on-scroll on the shell), the narrow, centred and hide-when-collapsed rail (width="narrow", align="center", hide-when-collapsed), a docked toolbar with a FAB and a rounded large-screen form (rounded), app bar actions that overflow into a menu (:actions="[…]"), the list item's video slot, the card's dragged state, the standard side sheet (<x-drawer standard>), bottom sheet preset heights, the multi-aspect carousel (layout="multi-aspect"), the divider with a subheader (<x-divider text>), the character counter (counter), the vertical slider (orientation="vertical"), the full-screen range date picker on compact windows, search's icon entry point and suggestions (trigger="icon", suggestions slot), the contrast toggle (<x-theme-toggle mode="contrast">) and the Standard motion scheme.

8. Tests and guards

DesignGuard changes with the stack:

  • Removed: the maryUI tag and daisyUI class checks, forbidAbsolutes() and forbidOpacityInk(). Every Tailwind utility now compiles to nothing and is reported with its replacement, so bg-white and text-on-surface/60 still are.
  • Retargeted: forbidColours([...]) keeps its signature and reports a left-out role where 2.0.0 writes one: var(--md-sys-color-…) in CSS or an inline style, an md-ink-* class, a component's color/tone prop.
  • New: missingStylesheets($cssEntry) names each @import the views need, and unusedStylesheets($cssEntry) each one they no longer do; a .css file passed to scan() is checked for literal values and off-scale media queries.

Browser tests that assert widths switch at 640/1024/1280 now switch at 600/840/1200; tests that read role="alert" on an alert, aria-pressed on the time picker's period buttons or data-theme-option need the new hooks above.

Every component hook is prefixed data-md-: data-toolbar-place is data-md-toolbar-place, data-account-menu is data-md-account-menu, data-field-copy is data-md-field-copy. The attributes on <html> keep their names (data-theme, data-contrast, data-scheme, data-motion, data-rail). A component's inner parts also carry its name:

1.x 2.0.0
data-app-shell, -bar, -actions, -banner data-md-scaffold, -bar, -actions, -banner
data-theme-option="dark" input[name="material-theme"][value="dark"]
data-scheme-option="teal" data-md-scheme-picker-option="teal"
data-account-theme data-md-account-menu-theme
data-section-picker data-md-section-nav-picker
data-material-carousel, -item, -content, -label, -surface data-md-carousel, -item, -content, -label, -surface
data-sheet (drawer) data-md-drawer-sheet
data-drag-handle (bottom sheet) data-md-bottom-sheet-handle
data-check, data-mixed (checkbox) data-md-checkbox-check, data-md-checkbox-mixed
data-on, data-off, data-handle (toggle) data-md-switch-on, data-md-switch-off, data-md-switch-handle
data-handle, data-thumb, data-tick, data-stop, data-segment, data-track-icon (slider) data-md-slider-handle, -thumb, -tick, -stop, -segment, -icon

Rename the negative assertions too: ->not->toContain('data-app-bar') passes against 2.0.0 whatever the page renders.

9. For AI agents

php artisan boost:update --discover picks up the new material-3 guideline and the material-3-design skill, which state M3's rules and tables beside the library's classes, props and tokens.