--- name: livewire-material-development description: Build Laravel and Livewire views with Livewire Material's Material 3 Expressive Blade components — props, slots, colour roles, type, shape, motion, theming, toasts, the design guard, and the Livewire traps each component handles. --- # Livewire Material Development ## When to use this skill Use this skill when writing or changing any Blade view, Livewire component view or layout in an application that requires `nonameweb/livewire-material`, and when styling, theming or testing such views. ## Setup Composer packages must be installed before the Vite build (in Dockerfiles and CI alike), because the application's build imports from `vendor/`: ```css /* resources/css/app.css */ @import 'tailwindcss'; @import '../../vendor/nonameweb/livewire-material/resources/css/material.css'; @import './material-scheme.css'; @source '../../vendor/nonameweb/livewire-material/resources/views'; @source '../../vendor/nonameweb/livewire-material/src'; ``` ```js // resources/js/app.js import '../../vendor/nonameweb/livewire-material/resources/js/material.js' ``` Every layout puts the theme script in ``, before `@vite`: ```blade @vite(['resources/css/app.css', 'resources/js/app.js']) ``` ## Colour scheme The scheme is generated, never hand-edited. Regenerate it with the seed and variant recorded at the top of `resources/css/material-scheme.css`: ```bash php artisan material:scheme "#4f46e5" --variant=tonal-spot ``` Variants: `tonal-spot` (M3's default), `vibrant`, `expressive`, `neutral`, `fidelity`, `content`, `monochrome`, `rainbow`, `fruit-salad`. `--spec` is the colour spec: `2025` (default, M3 Expressive) or `2021` (M3's original colour — keep it for a palette generated before Expressive; the library itself uses 2021 for variants 2025 does not define, and the header records the spec actually used). `--success`, `--warning` and `--info` set the source of the state colours; `--harmonize` pulls those three towards the seed (off by default: a state has to stay recognisable). The header of the stylesheet records the whole command, every option that differs from its default included. The command also writes `material-scheme.json` beside the stylesheet. `success`, `warning` and `info` are built exactly as M3 builds `error` — dynamic colours on their own tonal palette — so they follow the scheme's spec, its dark tones and its contrast level like every other role. Use them as roles (`bg-success`, `text-on-warning-container`), never as a hex. ### Contrast levels Every scheme is generated at M3's three levels: standard, medium (3:1) and high (7:1), for light and dark and for every profile. `--contrast` (and a profile's `contrast`) moves the **standard** level only and must stay below 0.5; medium and high are Google's fixed levels and are always written, under `[data-contrast='medium']` and `[data-contrast='high']`. - `` is the level on screen, written by `` before the first paint; standard writes no attribute, because the plain blocks are already standard. No stylesheet ever asks `prefers-contrast` — the head script does, once. - `theme.contrast.default` is `system` (follow the operating system), `standard`, `medium` or `high`, kept in localStorage under `theme.contrast.storage_key`. - In Alpine: `$store.theme.contrast` (the choice), `$store.theme.resolvedContrast` (the level showing) and `$store.theme.setContrast('high')`. `` is the ready-made row of three. - Nothing in a template names a level: a role's value changes underneath it. Never set `data-contrast` on an element to make a corner of the page higher-contrast — the level is the visitor's, page-wide. ### Colour profiles An installation that switches between several schemes lists them in `config/livewire-material.php` and runs the command without a seed, which generates every profile into the same stylesheet, keyed by ``: ```php 'profiles' => [ 'indigo' => ['label' => 'Indigo', 'seed' => '#4f46e5', 'variant' => 'vibrant'], 'teal' => ['label' => 'Teal', 'seed' => '#00897b', 'variant' => 'vibrant'], ], 'profile' => 'indigo', // the default; else the first ``` ```bash php artisan material:scheme ``` - Each profile: `seed`, and optionally `label` (default: the name as a headline), `variant` (default `tonal-spot`), `contrast` (default 0, below 0.5), `harmonize`, `spec`, `success`, `warning`, `info` (for these five, without the key the command's `--harmonize`, `--spec`, `--success`, `--warning`, `--info` or their defaults apply). Every profile is generated at all three contrast levels, keyed on `[data-scheme='x'][data-contrast='high']` and so on. - Names are lowercase letters, digits and dashes. Regenerate after changing the list; only generated profiles exist for the picker, the resolver and the stylesheet. - The application says which profile is active, once, in a service provider. The closure runs every time a colour is drawn (head script, mail, error page), so it may read the database; a name that is not a generated profile, or a closure that throws, falls back to the default: ```php use NoNameWeb\LivewireMaterial\Support\Scheme; Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile')); ``` - `` writes the active profile to `` before the first paint; mails and error pages draw it too. `Scheme::profiles()` lists the generated profiles (name ⇒ label, light and dark roles) and `Scheme::profile()` names the active one — validate a stored choice with `Rule::in(array_keys(Scheme::profiles()))`. Both take a contrast level as their last argument (`Scheme::load($path, $profile, 'high')`, `Scheme::profiles(contrast: 'medium')`); without one they answer with the standard level, which is what a mail wears. - Choose with `` (see Components). Never set `data-scheme` on an element inside the page expecting a different profile there: profiles key on ``. ## Tokens Tailwind's default palette is cleared: every colour class names an M3 role. `text-red-600`, `bg-base-200` or `text-gray-500` compile to nothing. - Colour roles (`bg-*`, `text-*`, `border-*`, …): `primary`, `on-primary`, `primary-container`, `on-primary-container`, `inverse-primary`, `primary-fixed`, `primary-fixed-dim`, `on-primary-fixed`, `on-primary-fixed-variant`; the same for `secondary` and `tertiary`; `error`, `on-error`, `error-container`, `on-error-container`; `success`, `warning` and `info` with their `on-`, `-container` and `on-…-container`; `inverse-error|success|warning|info`; `surface`, `surface-dim`, `surface-bright`, `surface-container-lowest|low||high|highest`, `on-surface`, `on-surface-variant`, `inverse-surface`, `inverse-on-surface`, `outline`, `outline-variant`, `scrim`, `shadow`; plus `white` and `black`. - Ink and lines by meaning: `text-body` (body copy), `text-meta` (metadata), `text-quiet` (decoration only), `border-structure`, `border-chrome`, `border-divider` / `divide-divider`. - Type: `type-{display|headline|title|body|label}-{lg|md|sm}` and `type-emphasized-…` — M3's 15 styles and their emphasized twins, one weight step heavier, rounder, with their own tracking. Never assemble `text-*`, `leading-*` and `tracking-*` by hand; a utility carries size, line height and tracking together. Emphasis is deliberate and one element at a time (M3 asks for it on badges, primary buttons, selected rows, headlines), so a regular utility inside an emphasized one goes back to plain. The font is Google Sans Flex (`font-sans`). - Shape: `rounded-corner-{none|xs|sm|md|lg|lg-increased|xl|xl-increased|xxl|full}`. - Elevation: `shadow-elevation-{1…5}` — for what floats over content, not for panels (a panel separates by its container tone). - Motion: `ease-spatial-{fast|default|slow}` (position, size, shape; springs that overshoot) and `ease-effects-{fast|default|slow}` (colour, opacity, which never overshoot). Always pair an easing with its duration: `duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast` — M3's published web durations, spatial 350/500/650 ms and effects 150/200/300 ms. `motion.scheme` in the config picks `expressive` (the default, with the bounce) or `standard` (minimal bounce), which the head script writes to `` and which swaps the three spatial springs; a component names a spring, never a scheme. Reduced motion zeroes every duration in both schemes. - States: `state-layer` (M3's hover/focus/press overlay; makes the element `relative` and `isolate`), `focus-ring` (keyboard focus indicator), `link` (a link in running text). - Breakpoints are M3's window size classes, and only those: `medium:` 600px, `expanded:` 840px, `large:` 1200px, `extra-large:` 1600px, with `max-medium:` … for "below" (compact is below `medium`). Tailwind's `sm:`…`2xl:` are cleared — a `sm:` compiles to nothing — because 640px means nothing in M3. Scripts ask `resources/js/breakpoints.js` (`from('expanded')`, `upTo('medium')`) so a stylesheet and a script never disagree at the boundary pixel; a component's *own* width is a container query (`@md:`), which is a different thing. - `dark:` follows the page's theme (`data-theme`), not the operating system. - `x-figure` on an element holding one number counts it up on first appearance and on change. ## Theme `config/livewire-material.php` → `theme.default` (`light`, `dark` or `system`), `theme.storage_key`, `theme.legacy_keys`, `theme.meta`, `theme.contrast` (`default` and `storage_key`, see Contrast levels) and `motion.scheme` (`expressive`, the default, or `standard` — M3's restrained springs, written to ``). In Alpine, `$store.theme` holds `choice` (what the visitor picked), `resolved` (`light` or `dark`, what shows), `set('light'|'dark'|'system')` and `toggle()`; `x-model="$store.theme.value"` binds a control. It also holds `contrast`, `resolvedContrast` and `setContrast()` for the contrast level. With colour profiles it holds `scheme` (the profile on screen) and `previewScheme(name)`, which shows another profile on this page without storing anything. `theme.meta` (default `false`) keeps the browser's bar in the page's colour, for an installed web app: the head script sets the `content` of every `` without a `media` attribute to the resolved theme's `surface` — of the profile in ``, at the level in `` — before the first paint, adding one to `` when there is none. It follows every later change of `data-theme`, `data-scheme` or `data-contrast` (`$store.theme.set()`/`toggle()`, `setContrast()`, an OS change while `system`, `previewScheme()`), and paints the next page's meta after `wire:navigate`. A theme-color meta the layout renders itself goes before `` (after it, the script has already added one, and the page ends up with two), or is left out. A `media="(prefers-color-scheme: …)"` pair follows the OS instead of the visitor's choice: drop it when turning this on. ## Safe areas Every component that meets the edge of the screen (app bar, navigation bar and rail, docked and placed toolbars, full-screen search, dialog and side sheet, bottom sheet, the skip link) keeps clear of a notch or home indicator through `var(--material-safe-top|bottom|left|right, env(safe-area-inset-…))`. The layout needs `viewport-fit=cover` in its viewport meta for the insets to be non-zero. Set a variable to replace the device's inset, on `` or any ancestor: a browser test fakes a notch with `document.documentElement.style.setProperty('--material-safe-top', '47px')`, and an app that draws its own status strip adds its height. `--material-snackbar-height` is the height of the snackbar on screen, written on `` by `` while one shows and removed when it goes. `` reads it, so the FAB sits above the snackbar rather than under it, as M3 requires; a placed `` does the same by wrapping it in `
`. `--material-bottom-extra` (default `0px`) is the height of anything the application docks on top of the phone's navigation bar in `` (an offline banner): the shell adds it to `--material-bottom-bar` (64px + the bottom inset), so the snackbar, a `fab` button and the page's bottom padding clear it too. Set it while the docked element shows, and remove it when it goes; place the docked element itself directly above the bar, at `bottom: calc(4rem + var(--material-safe-bottom, env(safe-area-inset-bottom)))`, on a compact window (below `medium`) only. ## Toasts ```php use NoNameWeb\LivewireMaterial\Concerns\Toasts; class Settings extends Component { use Toasts; public function save(): void { // … $this->success('Settings saved'); // also warning(), error(), info() $this->info('Link copied', timeout: 6000); $this->success('Share created', redirectTo: route('shares.show', $share)); } } ``` The methods are protected. They dispatch a `toast` browser event (`assertDispatched('toast', type: 'success', title: 'Settings saved')` in tests). ## Error pages Laravel's HTTP error pages — 403, 404, 419, 429, 500, 503, and the framework's own 401 and 402 — render in M3 without setup. The provider appends the package's error views to `view.paths` after the application's, so a file in `resources/views/errors/` always wins. - The pages load `config('livewire-material.showcase.vite')` and ``, so they use the app's scheme, font and theme. While the build is missing (a deploy in progress) they fall back to an inline stylesheet coloured from `resources/css/material-scheme.json`. - `abort(403, 'Only the owner can open this share.')` and `abort(503, '…')` show the message as the sentence. Every other string goes through `__()`; translate them in `lang/{locale}.json`. - To change wording or design, run `php artisan vendor:publish --tag=livewire-material-errors`, which copies the layout and pages to `resources/views/errors`. A page extends `errors::minimal` and sets `title`, `code`, `headline`, `message`, `shape` (an `` name) and optionally `actions`: ```blade @extends('errors::minimal') @section('title', __('Payment Required')) @section('code', '402') @section('shape', 'cookie-4') @section('headline', __('Your plan has ended')) @section('message', __('Choose a plan to keep using the app.')) @section('actions') @endsection ``` - Maintenance mode: `php artisan down --render="errors::503"`. - The showcase previews each page at `/material/errors/{code}`. ## Mail Markdown mail (notifications and `markdown:` mailables) wears M3 once the application selects the theme: ```dotenv MAIL_MARKDOWN_THEME=livewire-material::mail.theme ``` or per mail: `(new MailMessage)->theme('livewire-material::mail.theme')`, or `public $theme = 'livewire-material::mail.theme';` on a mailable. - Colours are the light scheme from `resources/css/material-scheme.json` (`livewire-material.scheme`), inlined as hexes; regenerate the scheme and mail follows. Without the file, the package's default scheme applies. - Write the body as Markdown; the theme styles the bare tags (`#` headings, prose, lists, tables) with M3's typescale. `` is a filled pill in `primary`; `color` also takes `secondary`, `tertiary`, `error`, `success`, `warning` and `info`. `` is a tinted container. - There is no dark mail. Never put `@media` rules, CSS variables or `color-mix()` in mail CSS: the inliner strips media queries and mail clients resolve no variables. - The package's mail header (the app name, or a logo) and message (with a replaceable footer) are opt-in: set `MATERIAL_MAIL_COMPONENTS=true`, or `php artisan vendor:publish --tag=livewire-material-mail` to copy them into `resources/views/vendor/mail`. For a logo set `livewire-material.mail.logo` to `['src' => 'https://example.com/logo.png', 'width' => 160, 'height' => 40]`: an absolute URL, with the image at twice those dimensions. - With those components, a mail can replace the footer: ```blade Your export is ready. © {{ date('Y') }} {{ config('app.name') }} · [Unsubscribe]({{ $unsubscribeUrl }}) ``` - The showcase renders a sample mail at `/material/mail`. ## Components ### `` A Material Symbol (Rounded, weight 400, grade 0), inline. Every symbol on fonts.google.com/icons exists, by Google's name with underscores. An unknown name throws. | Prop | Default | | |---|---|---| | `name` | required | `calendar_month`, `cloud_upload`, `content_copy` | | `filled` | `false` | the filled symbol — M3 uses it for active or selected | | `optical` | `24` | the cut the glyph is drawn from, `24` or `20`; anything else falls back to 24 | | `label` | `null` | names the icon for screen readers when it carries the meaning alone; otherwise it is `aria-hidden` | 24px (`size-6`) unless a `size-*`, `w-*` or `h-*` class is passed. Colour follows the text: ``. M3's optical size axis redraws a symbol so its strokes look equally heavy at every size, so **an icon drawn at 20px or smaller (`size-5` and below) takes `optical="20"`**; scaling the 24 cut down thins its strokes by about a sixth. The cut is not a size — pass both, and where a component sizes an icon for you, pass the cut alongside the size class. ### `` One of M3 Expressive's 35 shapes, filled in the text colour, `aria-hidden`, sized by its caller: ``. Names: `circle`, `square`, `slanted`, `arch`, `fan`, `arrow`, `semi-circle`, `oval`, `pill`, `triangle`, `diamond`, `clam-shell`, `pentagon`, `gem`, `very-sunny`, `sunny`, `cookie-4`, `cookie-6`, `cookie-7`, `cookie-9`, `cookie-12`, `ghostish`, `clover-4`, `clover-8`, `burst`, `soft-burst`, `boom`, `soft-boom`, `flower`, `puffy`, `puffy-diamond`, `pixel-circle`, `pixel-triangle`, `bun`, `heart`. ### `` The theme decided before the first paint. Exactly once per layout, in ``, before `@vite`. No props; configured in `config/livewire-material.php`. With `theme.meta` on it also paints `` (see Theme); a layout's own theme-color meta goes before it. ### `` Label button, icon button, toggle and responsive FAB in one component. | Prop | Default | | |---|---|---| | `label` / slot | | the words; without them and with an `icon` it is an icon button | | `variant` | `text` | `filled`, `tonal`, `outlined`, `elevated`, `text` | | `color` (alias `tone`) | `primary` | `primary`, `secondary`, `tertiary`, `error`, `success`, `warning`, `info` | | `primary`, `danger`, `caution` | | shorthands: filled primary, filled error, filled warning | | `size` | `sm` | `xs` 32px, `sm` 40px, `md` 56px, `lg` 96px, `xl` 136px | | `shape` | `round` | or `square`; both square off further while pressed | | `icon`, `icon-right` | | Material Symbol names; drawn from M3's 20px cut where the glyph is 20px, and filled on a default icon button | | `width` | `default` | icon buttons only: `narrow`, `default`, `wide` | | `selected` | `null` | `true`/`false` makes it a toggle (`aria-pressed`, selected colours and shape) | | `link`, `external`, `no-wire-navigate` | | renders ``, with `wire:navigate` unless external | | `spinner` | | `true` shows the loading indicator while its `wire:click` runs; a string names the action | | `tooltip`, `tooltip-left`, `tooltip-right`, `tooltip-bottom` | | plain tooltip; also the icon button's accessible name | | `disabled`, `type`, `responsive`, `fab` | | `responsive` hides the label below `expanded`; `fab` is an extended FAB on a compact window (below `medium`), a filled button from there | ```blade ``` ### `` M3's plain tooltip, standalone around any trigger: ``. `side`: `top` (default), `bottom`, `left`, `right`. Shows on hover (fine pointers) and keyboard focus, and goes 1.5s after the pointer or the focus leaves it (M3's transient tooltip); only one is on screen at a time. It is `aria-hidden`, so the trigger has to carry the same words itself — as an icon button's `aria-label` does. Where the tip says something the trigger does not, use ``, which points the trigger at its text. Buttons and FABs take a `tooltip` prop instead. ### ``, ``, ``, `` ```blade ``` ``: `trigger` slot (its first button or link becomes the menu button, and the menu hangs on that button — a `position: fixed` trigger such as `` carries it along, and a menu with no room flips to the other side, end or both), `label`, `position` (`bottom-start` default, `bottom-end`, `top-start`, `top-end`), `vibrant`. ``: `label`, `icon`, `icon-class` (classes for the leading icon; a colour there paints it, a selected item's too, but not a disabled one's — `icon-class="text-sport-run"`), `icon-right`, `description`, `shortcut`, `link`, `external`, `selected` (makes it a `menuitemcheckbox`, ticked at its end unless it has an `icon-right`), `current` (for a menu of places: marks the page you are on with `aria-current="page"` in secondary-container, never a checked choice), `badge` (`true` for a dot, or a count, at the end of the row), `disabled`, `keep-open`. Choosing an item closes the menu unless `keep-open`; a second press on the menu button closes it too. An open menu stays open while the Livewire component around it renders, a `keep-open` item's own `wire:click` included. Keyboard: arrows, Home, End, a letter, Escape (focus returns to the trigger), Tab; a `disabled` item keeps its place in that order, as M3 asks, but cannot be activated. A menu longer than the window scrolls. ### `` A row of ``s: ``. `connected` sets them 2px apart with small inner corners (a selected toggle rounds fully). Pass the `size` of the buttons inside. ### `` A choice between a few options as a connected button group of native radios (checkboxes with `multiple`): ```blade ``` Props: `label`, `hint`, `hint-class` (classes for the hint, as on the fields; a colour there paints it), `name` (required with `x-model`), `options`, `option-value` (`id`), `option-label` (`name`), `option-icon` (`icon`), `size`, `variant` (`tonal`, `filled`, `outlined`), `multiple`, `inline` (intrinsic width instead of sharing the row). A validation error for the bound property replaces the hint. ### `` ```blade ``` Attributes go to the leading button; the slot is the menu. `variant` (`filled` default, `tonal`, `outlined`, `elevated`), `color`, `size`, `disabled`, `spinner`, `menu-label`, `position`. ### `` `` — `size` `sm` 56px (default), `md` 80px, `lg` 96px; with `label` it is an extended FAB. The glyph is filled, as M3 requires of a FAB. `color` `primary`/`secondary`/`tertiary`, drawn in the container, or `variant="filled"`. It does not position itself; wrap it (`
` — M3's 16dp margin, 24dp from `large`). `link`, `external`, `type`. There is no `disabled`: M3 says to remove a FAB whose action is unavailable, so hide it instead. `data-fab` on the root lets a place restyle a nested FAB (a rail flattens it to elevation 0). ### ``, `` ```blade
``` Two to six items open above the FAB, which turns into a close button, rising into place as it opens and sinking back as it closes; a window too short for them scrolls the list while the FAB stays put. ``: `icon` (`add`), `label` (defaults to "Toggle menu" — the trigger has no other accessible name), `color`, `position` (`top-end` default). Give items the same `color`. Keyboard, and staying open through a Livewire render, as ``. The wrapper keeps M3's margin from the window edge: 16dp, 24dp from `large`. ### `` M3 Expressive's loading indicator — a shape morphing through seven Expressive shapes as it turns — for a wait of unknown length. 48px and `primary` unless sized or coloured by class; `contained` sets it on a primary-container circle. A `progressbar` named by `label` ("Loading"); `:label="false"` makes it decorative. It rests under reduced motion. ```blade
``` `` shows a decorative one in place of its icon. ### `` The snackbar host. Once per layout, near the end of ``: `` (`position="bottom-start"` to leave the centre free). It is `@persist`ed across `wire:navigate` and shows, one at a time, every toast from the `Toasts` concern (see Toasts above) or from JavaScript: ```js materialToast('Share deleted', { type: 'success', description: null, timeout: 4000, action: { label: 'Undo', handler: () => $wire.restore() } }) ``` `type` (`success`, `error`, `warning`, `info`) picks the announcement role and draws no icon (M3 tells you to avoid one in a snackbar); `timeout: 0` keeps it until dismissed; a toast with an action or no timeout gets a close button. Hover or focus pauses the timer. A toast with an `action` never auto-dismisses (M3's rule) unless you write a `timeout` out. - `action`: `label`, plus `handler` (a function) and/or `event` (a name). Pressing it closes the snackbar, calls `handler`, then dispatches `new CustomEvent(event)` on `window`; give both and both run. Use `event` where a function cannot travel, such as a toast built from JSON. - `sticky: true` keeps a toast until it is dismissed or its action is pressed (any `timeout` is ignored), without holding the queue up: a toast dispatched meanwhile shows in its place, and the sticky one comes back once the queue is empty. One sticky toast is kept at a time; a newer one replaces it. Use it for a question that must be answered, not for news: ```js window.dispatchEvent(new CustomEvent('toast', { detail: { type: 'info', title: 'A new version is ready', sticky: true, action: { label: 'Reload', event: 'app:update' } } })) window.addEventListener('app:update', () => location.reload()) ``` - Escape dismisses a snackbar that holds the focus. - Hooks: `data-toast` on the snackbar on screen, `data-toast-action` on its action button (`[data-toast]` is absent while nothing shows). Target these in tests, not classes. ### `` M3 Expressive's progress indicator: linear (as wide as its container) or `circular` (40px, 48px wavy, unless a `size-*` class is passed), flat or `wavy`, determinate with a `value` or indeterminate without one. | Prop | Default | | |---|---|---| | `value` | `null` | 0 to `max`, clamped; `null` is indeterminate | | `max` | `100` | | | `bind` | `null` | an Alpine expression it follows in the browser; `null`/`undefined` is indeterminate | | `circular` | `false` | circular instead of linear | | `wavy` | `false` | Expressive's wave (flat below 10% and from 95%) | | `thick` | `false` | 8px track and indicator instead of 4px | | `color` | `primary` | `primary`, `secondary`, `tertiary`, `error`, `success`, `warning`, `info`; the track is the colour's container (secondary-container for primary) | | `label` | `"Progress"` | names the `progressbar`; `:label="false"` makes it decorative | ```blade
``` A value the server changes animates after a morph (the SVG is `wire:ignore`; only the root's attributes change). Under reduced motion values jump, the wave stands still and an indeterminate indicator holds one frame. A `w-*` class narrows a linear one; never pass a display or position class. ### `` - `` — M3's small badge, a dot. `` — M3's large badge, a count. Both `error` by default. `floating` pins it to the top-end corner of a `relative` parent: ``. A dot or count is `aria-hidden` unless it has a `label`; name the control instead ("Messages, 4 unread"). - ``, ``, ``, `` — a status label (not an M3 badge) in the colour's container, in the colour itself (`solid`, for a label that has to stand out), or a neutral `outline` edge (the role that has to be seen, not the decorative `outline-variant` dividers use). `color` (alias `tone`): `error` default, `primary`, `secondary`, `tertiary`, `success`, `warning`, `info`, `neutral`, `plain`; an unknown colour is `error`. - `color="neutral"` — neutral ink on every variant: a dot or count in on-surface-variant with surface text, `tonal` in surface-container-high with on-surface-variant text, `outline` in the outline edge with on-surface-variant text. - `color="plain"` — no background, text or border colour in any variant (shape, size and type stay), so the classes you pass paint it: ``. Pass both a background and a text class; an `outline` badge's edge takes the text colour unless you pass a `border-*` colour. - The value is `value` or the slot; the slot renders as HTML: ` Pro`. `value` is escaped. A slot that holds only whitespace or comments is still a dot. ### `` A notice in the page, in the state's container colour with its icon: ```blade The upload failed. ``` `color` (alias `tone`): `info` (default), `success`, `warning`, `error`, `primary`, `secondary`, `tertiary`, `neutral`. `icon` overrides the state icon; `:icon="false"` removes it. Every alert is `role="status"`, whatever its colour: it is usually on the page as it renders, and an assertive region talks over the page title on load. Pass `assertive` for one put on screen in answer to something the person just did. ### `` A few lines of context around a trigger, with an optional `title` and `actions` slot: ```blade ``` Shows on hover and keyboard focus; `persistent` opens it on press and keeps it until a press elsewhere or Escape (use it when there are actions). The trigger is pointed at the bubble with `aria-describedby`, so its words are read out with the control. An open bubble stays open while the Livewire component around it renders, its actions' `wire:click` included. `side`: `bottom` (default), `top`, `left`, `right`. ### `` `` — a figure on a surface-container panel; the value counts up on first appearance and when it changes. The slot goes under the description (e.g. a quota's ``). Do not pass a `bg-*` class; wrap it. ### `` "Nothing here yet": `icon` on an Expressive `shape` (`cookie-9` by default), `title`, `description` or slot, and an `actions` slot. Use it for an empty collection, not for a filter that matched nothing. The `illustration` slot draws the application's own artwork in place of the shape and icon (`icon` and `shape` are then unused). Size the artwork yourself; the slot's attributes go on the element around it, so its `class` sets the colour `currentColor` takes. Mark decorative SVG `aria-hidden="true"`. A slot holding only whitespace or comments leaves the shape and icon. ```blade ``` ### `` `variant`: `filled` (default, surface-container-highest), `elevated`, `outlined`; medium corner. Props `title`, `subtitle`, `separator`; slots `figure` (full-bleed media), `menu` (top-end), `actions` (end-aligned). Do not pass `bg-*`; use `variant`. A card or list item that opens something is a **row**: `data-list-row` on it and `data-list-open` on its one opener (the title link or a button). A press anywhere else on the row reaches the opener; its other controls keep their own presses. Never wrap a card in `
` or use a stretched link. A row answers with the state layer and one step of elevation; its corner does not move. ```blade {{ $share->name }} ``` `data-dragged` on the card draws M3's dragged state — elevation 8dp elevated / 6dp filled and outlined, under the 16% dragged state layer. The application sets it when its drag starts and removes it on drop; pair any drag with a single-pointer alternative (a menu with the same actions), as M3 requires. That is M3's *non-actionable card with actionable elements*: Tab walks the controls inside. For M3's *directly actionable card*, where Tab lands on the card and then moves to the next card, add `actionable` (and `role="link"` where it goes somewhere) and put `tabindex="-1"` on the opener — the card is then the tab stop, named by its `title`, and Enter or Space on it reaches the opener while the card's other actions follow it. ### ``, `` ``: `label`, `dividers`, `segmented` (M3 Expressive: separate tiles 2px apart). ``: `title` (or slot), `overline`, `description`, leading `icon` / `avatar` (image URL or initials) / `image` / `video` / `leading` slot, trailing `trailing` text / `icon-right` / `end` slot, `link` (the whole item becomes a row that opens it), `selected`, `disabled`. One-, two- and three-line heights follow from the content, and a three-line item top-aligns as M3 asks. Its icons are 24px, 20px in a `segmented` list. `video` is M3's leading media in landscape: a poster URL, or `` for a `