--- 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`. `--success`, `--warning` and `--info` set the source of the state colours; `--contrast` goes from -1 to 1. The command also writes `material-scheme.json` beside the stylesheet. ## 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-…`. Never assemble `text-*`, `leading-*` and `tracking-*` by hand. 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). Always pair an easing with its duration: `duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast`. Reduced motion zeroes the durations. - 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). - `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`. 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. ## 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). ## Components ### `` A Material Symbol (Rounded, weight 400, grade 0, 24px), 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 | | `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: ``. ### `` 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`. ## Testing the design ```php use NoNameWeb\LivewireMaterial\Testing\DesignGuard; it('uses only what compiles', function () { expect(DesignGuard::scan([resource_path('views'), resource_path('js'), app_path()]) ->forbidColours(['tertiary']) // roles this application's rules leave out ->violations())->toBe([]); }); ``` It fails on maryUI tags, daisyUI classes, colours the theme does not declare and unknown symbol names, with `path:line` for each. ## Conventions - Components are anonymous Blade components: `` without a prefix, or `` when `config('livewire-material.prefix')` is set. - Write class names out whole. Tailwind cannot compile `'text-'.$tone` or `type-{{ $size }}`, and the design guard cannot read them. - The showcase at `/material` (local only, `MATERIAL_SHOWCASE=true` to force it) renders every token and component. ## Livewire traps - Blade directives do not compile inside a component tag's attributes: `` reaches the browser as literal text. On a component tag use `{{ }}` and `:prop` bindings, or put the Alpine on a plain element inside the slot. - Never pass `hidden`, a display utility or a position (`absolute`, `relative`) to a component: it is merged beside the component's own and whichever Tailwind emits last wins. Wrap the component in an element that carries it. A variant that only hides (`max-sm:hidden`) is safe. - `$attributes->wire('model')->value()` is `false`, not `null`, when there is no `wire:model`, and `filled(false)` is true. Normalise with `?: null`. - End every statement in a multi-line Alpine attribute with `;`: an inline `@if … @endif` inside it swallows the newline after it.