`. A row that opens something is `data-md-list-row` with one `data-md-list-open` control; a selected row is `aria-selected="true"`.
`
Size` sorts through the Livewire property `sortBy` (`['column' => …, 'direction' => 'asc'|'desc']`; `model` names another), with `aria-sort`.
```blade
{{ $shares->links() }}
```
Pagination: `$paginator->links()` (Laravel and Livewire, full and simple/cursor) is drawn in M3 — current page in secondary-container, "Page 2 of 7" on a phone. Turn off with `config('livewire-material.pagination')` = `false`; published `vendor/pagination` or `vendor/livewire` views still win.
## 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
->forbidAbsolutes() // opt-in: `bg-white`, `text-black`
->forbidOpacityInk() // opt-in: `text-on-surface/60`
->violations())->toBe([]);
});
```
It fails on maryUI tags, daisyUI classes, colours the theme does not declare, unknown symbol names and Blade directives written inside a component tag (where they do not compile), with `path:line` for each.
It also fails on every value the theme cleared, and names the replacement on the same line:
| Written | Use |
| --- | --- |
| `sm:`, `md:` (and `max-sm:`, `max-md:`) | `medium:` (`max-medium:`) |
| `lg:`, `xl:`, `2xl:` | `expanded:`, `large:`, `extra-large:` |
| `rounded-lg`, `rounded-t-2xl`, `rounded-full` | `rounded-corner-lg`, `rounded-t-corner-xxl`, `rounded-corner-full` |
| `shadow-sm`, `shadow-md` … `shadow-2xl` | `shadow-elevation-1` … `shadow-elevation-5` |
| `text-sm`, `leading-6`, `tracking-wide` | a `type-*` style, which sets the three together |
| `font-medium`, `font-bold` | a `type-emphasized-*` style |
| `ease-in-out`, `ease-linear` | `ease-standard`, or an `ease-spatial-*`/`ease-effects-*` |
| `duration-300` | `duration-(--md-sys-motion-…-duration)`, paired with its easing |
| `bg-[#1d7afc]`, `text-[rgb(…)]`, `border-[color-mix(…)]` | an M3 role |
`forbidAbsolutes()` adds `white` and `black` (M3's white is `surface-container-lowest`, its ink an `on-` role) and `forbidOpacityInk()` adds opacity as emphasis (`text-on-surface/60` → `text-on-surface-variant` or `text-outline`). Both are off by default: M3 reserves 38 % on content and 12 % on a container for the disabled state, and the package's own components are written with those two opacities.
## Conventions
- Components are anonymous Blade components: `
` without a prefix, or `` when `config('livewire-material.prefix')` is set; `` always works.
- 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-medium: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.