frontend-a11ylisted
Install: claude install-skill pekral/cursor-rules
## Constraints
- Apply `@rules/laravel/livewire.mdc` — keep Blade presentation-only; use `wire:model` for bindings; components stay slim.
- Apply `@rules/laravel/filament.mdc` — reuse Filament's built-in accessible form/action components rather than rebuilding them.
- Apply `@rules/security/frontend.md` — sanitize any dynamic content before DOM insertion; prefer `textContent`/`setAttribute` over `innerHTML`.
- Stack is Blade + Livewire + Alpine.js + Filament + Tailwind. No React/Vue/Next — never output `useState`/`useEffect`/`useRef`/JSX.
## Use when
- Building or reviewing forms (`<input>`, `<select>`, `<textarea>`).
- Creating interactive elements (modals, dropdowns, tabs, tooltips) with Alpine.
- Adding `<div>`/`<span>` with `wire:click` or `x-on:click` instead of a button.
- Adding any `aria-*` attribute.
- Managing focus or announcing async updates after a Livewire round-trip.
- Acting on accessibility feedback from a review tool.
---
## Form accessibility
Missing `for`/`id` pairing and disconnected error messages are the most commonly flagged issues. Livewire's `@error` directive pairs naturally with `aria-describedby`.
### Label connection
```blade
{{-- BAD: no association --}}
<label>Email</label>
<input type="email" wire:model="email">
{{-- GOOD: for matches id --}}
<label for="email">Email</label>
<input id="email" type="email" wire:model="email">
```
### Required fields
```blade
{{-- visual asterisk hidden from SR; required + aria-required convey state --}