`full` takes the 40rem bound off `[data-md-field]` from `medium`, and `<x-input>`, `<x-password>`, `<x-textarea>`, `<x-select>` and `<x-file>` pass it to the field they draw. `<x-choices searchable>`, `<x-datepicker>` and `<x-timepicker>` draw the same field but did not declare the prop, and their roots only forward `class` and `style`, so `full` reached no element and the field stopped at 40rem: ReStride's time zone choice stood narrower than the rows filling its card. The three now declare `full` and pass it to their field (the date picker's own field, not the dialog's entry fields). `<x-choices>`' chips are not a field and have no bound, so they take `full` and render nothing for it, and a call site can switch `searchable` on and off without it. The docblocks and the skill list every component that takes it. The browser test that bounds a field at 600px now measures a bounded and a `full` searchable choice, date picker and time picker too; it fails without the change in Chrome, Firefox and Safari. A feature test renders `full` on every field-shaped component and none on the chips. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
23 KiB
Upgrading
From 2.1.0 to 2.1.1
$store.rail.toggle()flips what the first rail on the page draws, as its menu button does. With nothing stored,<x-scaffold>'s rail is drawn collapsed from 840 to 1199px whateverrail.defaultsays, andtoggle()collapsed it again, so the first press of an application's shortcut changed nothing; it now expands the rail. Below 840px, where there is no room to expand it in the layout,toggle()opens and closes the modal rail (show(),hide()) instead of changing a choice nothing draws there. A shortcut that pressed the rail's menu button to get this can call$store.rail.toggle()again.DesignGuardno longer reads a Markdown mail component's classes as Tailwind: a view undermail.markdown.paths(resources/views/vendor/mailby default) is drawn by the mail theme, whosetable,buttonandpanelshare Tailwind's names, so only its icon names, the directives in its component tags and the application's own bans are checked there. A mail theme stylesheet under that path is no longer checked for literal values, and its classes no longer exempt the same names in the application's other views. A filter an application wrote around such a finding can go.DesignGuardreports a colour utility on a colour the application's own Tailwind theme named (bg-brand,text-sport-run,from-brand,border-l-zone-4,bg-brand/8) in a class list, withvar(--…)of the application's own as its replacement, unless the application's CSS declares the class. None of them has compiled since 2.0.0, and they were the leftovers its count never showed. Tailwind's other utilities on the same prefixes are named by their own family now too:border-collapse,border-spacing-*,from-10%,via-none,fill-none,stroke-*,text-shadow-*,text-inherit, and Tailwind 4.2'smauve,olive,mistandtaupepalettes. A test that expects no findings may fail on them.- A selected row written by hand —
data-md-list-rowwithdata-md-selectedon an<li>, a<div>or a<tr>, not<x-list-item>or<x-card>— is drawn insecondary-containerwithon-secondary-containerink again, as 1.x'sdata-list-rowwithdata-selectedwas; 2.0.0 dropped the fill without saying so. Hover, focus and press now tint the fill instead of hiding it. In<x-table>a plain row takesdata-md-selectedtoo, besidearia-selected="true". A card still takes no selected fill. - 67 Material Symbols in each cut (
auto_awesome,insights,tips_and_updates,battery_50…) are drawn whole at every size. Google publishes them in 24 units (20 in the 20 cut) with a width and height but no viewBox, and the package dropped the size, so they fitted only at exactly 24px and were cut off or misplaced at any other — the 20 cut every small button draws from included. Nothing to change. - A focused text field keeps its focus edge under the pointer. The hover rule outweighed focus,
so a focused field the pointer rested on drew a 2px
on-surfaceedge instead ofprimary, and a field in erroron-error-containerinstead oferror— in the filled variant too, and on a customizable<x-select>while its menu is open. An open select in error now drawserrorrather thanprimary. A test that read the hover colour off a focused field readsprimarynow. - Escape closes one layer. A dialog, menu, customizable select or searchable choice opened
over or inside a modal
<x-drawer>,<x-bottom-sheet>or modal navigation rail — or a sheet opened from a sheet, or inside<x-modal>— closed the layer under it on the same Escape, since each sheet closed on any Escape the window heard. Now only the topmost layer closes; a sheet withclose-on-escapeoff still keeps the Escape from closing what is under it. A dialog or sheet opened from a modal sheet but rendered elsewhere on the page was also hidden from screen readers by the sheet'saria-hidden, and Tab could not move inside the dialog; both work now. An application's own guard around a sheet's Escape can go. - A standard
<x-drawer>sticks under the top safe area (--material-safe-top, else the device's inset) instead of at the window's edge, so its head and close button no longer slide under an installed app's status bar as the page scrolls; inside<x-scaffold>it also sticks under a sticky<x-app-bar>in thetopslot, which covered it. It is as tall as the window below that, and every side sheet keeps the bottom safe area inside its bottom padding. - A standard
<x-drawer>takes its place from the first paint. Until Alpine started, a closed standard sheet was a zero-wide item that still cost its row one gap, and a sheet bound to an open Livewire property had no width yet, so the content beside them changed width when the script ran. A standard sheet is now out of the row until then, and one whosewire:modelproperty is open is rendered open and stands at its width. One opened from an Alpine scope alone still appears when Alpine starts, which only the script can know. - A long value in
<x-select>ends before the arrow, with an ellipsis. Where the browser has the customizable select (Chrome, Safari), the closed select drew its value in a box of its own that took neithertext-overflownor a width from the select, so a long option in a narrow field, or atsize="sm"andxs, ran on under the arrow; the native select (Firefox) cut it at the arrow with no ellipsis. The select's first child is now a<button type="button" wire:ignore>holding<selectedcontent>, the browser's copy of the chosen option, which select.css cuts; a browser without the customizable select never renders it. A test that matched a select's markup up to its first<option>finds the button there. - A switch beside long text in a row keeps its track.
<x-toggle>'s root could shrink to nothing as a flex item, so beside a paragraph in<x-row justify="between">, or with a long label of its own, the 52px track ran out of the row and past a card's edge on a phone.<x-checkbox>did the same with its box. Both roots now keep their automatic minimum — the control, and beside a label its longest word — so the text wraps instead. Aflex: nonean application put on the switch to hold it can go. - A disabled
<x-button fab>is not drawn on a compact window. Belowmediumit was still the extended FAB, greyed out and fixed over the content; M3 never shows a disabled FAB and removes one whose action is unavailable, so afabgivendisabled(on a link too) now rendersdata-md-unavailableand isdisplay: nonethere, out of the accessibility tree with it. Frommediumit is the disabled filled button, as before. Aspinnerthat disables the button while its action runs leaves the FAB on screen. An application's ownhide-belowor condition around a disabled fab button can go. <x-choices searchable>,<x-datepicker>and<x-timepicker>takefull, as<x-input>,<x-textarea>,<x-select>and<x-file>do, to take the 40rem bound off a field frommedium; the prop did nothing there, so the field stopped at 40rem beside rows that filled their card.<x-choices>' chips have no bound and takefullwithout a change, so a call site can switchsearchableon and off. A width rule an application wrote around one of them for this can go.
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,collapsedorexpanded(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 fromnavigation-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-sm … rounded-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-sm … shadow-2xl, shadow-elevation-* (1.x) |
var(--md-sys-elevation-1) … var(--md-sys-elevation-5) |
text-xs … text-9xl, leading-*, tracking-*, font-medium … font-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,warningandinfoare now built on the 2025 colour spec with the contrast level, likeerror; their values change.--harmonizeblends them toward the seed (off by default).- The stylesheet gains medium and high contrast blocks, and
material-scheme.jsongains acontrastkey;light/darkat the top level are still the standard scheme, so the mail theme needs nothing.--contrastmust now be below 0.5; medium (0.5) and high (1.0) are generated alongside. - Config:
theme.contrast(defaultsystem|standard|medium|high,storage_key) andmotion.scheme(expressive|standard).$store.themegainscontrast,resolvedContrastandsetContrast();<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;typeonly picks the announcement role (alertfor error and warning,statusotherwise). An actioned snackbar stays until acted on; Escape dismisses a focused one.<x-fab>has nodisabledprop: M3 says to remove a FAB whose action is unavailable, so hide it instead (a form-submit FAB useswire:loading.attr="disabled").<x-alert>isrole="status"for every colour; passassertivewhere 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; connectedxs/smsegments have a 48px minimum width.<x-menu-item>rows are 48px with 16px sides; a selected item draws a trailing check unless it hasicon-right; a long menu scrolls.<x-drawer>renders a close button by default (:with-close-button="false"to drop it, ignored on astandardsheet or when Escape and the scrim are off) and left-aligns itsactionsin 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 at50dvh(was90dvh); anyheightis capped atcalc(100dvh - 72px).<x-carousel>paddingdefaults to16;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>orselection="single|multi"makes it alistboxofoptions witharia-selected.<x-card>changes elevation on hover instead of its corner;data-md-cardcarries 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; abox-classthat setoverflowno longer applies.<x-table>rows are 52px and the automatic fine-pointer density is gone: passdensefor 36px rows (size="xs" denseis 24px).- A field in error draws a trailing
erroricon 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 setsaria-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: thedata-theme-optionhooks are gone; targetinput[name="material-theme"][value="…"].<x-tab>panels renderid,aria-labelledbyanddisplay: nonefrom 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 areprimary. An application that set--material-bottom-barto 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
searchvariant bounds its own width, so drop hand-writtenmax-w-*wrappers. <x-app-shell>is renamed<x-scaffold>, with no alias; it is a column with a nested row and gainsbannerandfabslots.<x-navigation-rail>'sheaderslot takes one<x-fab label icon>that morphs (replace the two-FABrail-collapsed:swap); newdividerandfillprops. Therail-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-actionsanddata-app-shell-banneraredata-md-scaffold,data-md-scaffold-bar,data-md-scaffold-actionsanddata-md-scaffold-banner; the skip link isdata-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
fullremoves it. - Every
<x-modal>draws a rule under its header and over its actions while its body scrolls, and neither when the body fits.separatornow 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
descriptionis 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 whenheightsorsnapis set; without stops it behaves as before.- A chip set with
scrollshows 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()andforbidOpacityInk(). Every Tailwind utility now compiles to nothing and is reported with its replacement, sobg-whiteandtext-on-surface/60still 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 inlinestyle, anmd-ink-*class, a component'scolor/toneprop. - New:
missingStylesheets($cssEntry)names each@importthe views need, andunusedStylesheets($cssEntry)each one they no longer do; a.cssfile passed toscan()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.