# Audit: inputs Scope: text fields and their chrome (`form`, `field`, `input`, `password`, `textarea`, `select`, `file`), selection controls (`checkbox`, `radio`, `toggle`), `slider`, `chip` / `chip-set` / `choices`, `datepicker`, `timepicker`, `search`, and the data pieces (`table`, `sort-header`, pagination views). Sources cited below: `docs/reference/m3/reference-components-b.md` (RB) and `docs/reference/m3/reference-foundations.md` (RF) — both built from m3.material.io; raw page dumps `docs/reference/m3/raw/components_*_specs.md`; androidx token files in `docs/reference/m3/tokens/*.kt`. `reference-styles.md` did not exist while this audit ran, so shape/type/elevation numbers come from the raw `styles_*` dumps and the token files. ## Summary This is an unusually faithful M3 implementation. The text-field chrome, the three selection controls, the chip family and both pickers reproduce the androidx token numbers almost exactly — the switch matches `SwitchTokens` in every dimension, the date picker matches `DatePickerModalTokens` (360 × 120 header, 40 px day in a 48 px cell, today's 1 px primary outline), and the time picker matches the site's own spec table for the dial, the selector handle and the time-selector boxes. The biggest problems are accessibility rather than geometry: `` announces nothing when results appear and puts `aria-expanded` on a plain `` with orphan `role="listitem"` results; a disabled text field still lights its outline on hover because the hover declaration sits on a descendant of the element carrying the disabled value; and a label-less checkbox, radio or switch has an 18–52 px hit target instead of 48 px. Measurable deviations cluster in the slider (the M-size handle is 44 px where M3 says 52 px, the value indicator is a 32 × 36 pill where M3 says 44 × 48, and the stop/tick/icon colours are inverted from the site's roles) and in the search view (it draws the deprecated *divided* style's divider and uses surface-container-high where the full-screen layout should be surface-container-low). Missing entirely: the slider's Expressive vertical orientation, the full-screen range date picker, and any width bound on the search bar or a text field at medium/expanded widths. Several of the library's own androidx-token citations in header comments name numbers that the M3 site's spec tables contradict — all listed in **Token-citation disagreements** below. ## Findings ### IN-01 · search · results appear with no announcement, and the input's ARIA is invalid - Severity: must-fix - M3 says: "Screen reader must announce when suggestions/results appear (autosuggest change)" and "Suggestions/results use the list component and are announced as a list by screen readers (follow list accessibility guidelines for item labels)" — RB §Search/Accessibility (m3.material.io/components/search/accessibility). - Library does: `resources/views/components/search.blade.php:47-64` puts `aria-controls` and `aria-expanded` on a bare `` with no `role="combobox"`. ARIA 1.2 does not list `aria-expanded` as supported on `textbox`, so the state is dropped by conforming AT. The view (`search.blade.php:75-90`) has no role and no live region, and `[data-search-results]` (`search.blade.php:86`) is a plain `
` whose children are ``s rendered as `role="listitem"` (`resources/views/components/list-item.blade.php:41`) — an orphan role with no `role="list"` owner. `grep -n 'aria-live' resources/views/components/search.blade.php` returns nothing. `` already does this correctly (`choices.blade.php:124-130,154-157`: `role="combobox"`, `aria-autocomplete="list"`, `role="listbox"`/`role="option"`, `aria-activedescendant`). - Fix: in `search.blade.php`, add `role="list"` to the `[data-search-results]` div (or wrap the slot in ``), and either (a) mirror ``: `role="combobox" aria-autocomplete="list"` on the input and roving `aria-activedescendant` over the results, or (b) keep the plain search field, drop `aria-expanded`, and add a visually hidden `` that `search.js` fills with the result count whenever `[data-search-results]` mutates (the `MutationObserver` plumbing already exists in `resources/js/chips.js:99-117` as a pattern). - Effort: M - Breaks API? no ### IN-02 · field · a disabled text field still lights its outline on hover - Severity: must-fix - M3 says: "**Disabled**: communicated via color change + reduced elevation; not focusable, draggable, or pressable; **no hover/other state layer**" — RF §States (foundations/interaction/states/applying-states). - Library does: `resources/css/components/field.css:390-392` declares `.field-box:hover { --field-edge: var(--md-sys-color-on-surface); }` **on `.field-box`**, while the disabled value at `field.css:423-425` is declared on `.field` and only *inherits* down. A direct declaration beats an inherited one regardless of selector specificity, so hovering a disabled outlined field paints a full-opacity `on-surface` outline instead of `on-surface/12`. The rule is also outside `@media (hover: hover)`, unlike the filled variant's background at `field.css:458-462`, so the state sticks after a tap on touch. (The filled background *is* correctly suppressed — `field.css:514-519` wins on specificity there; only `--field-edge` breaks.) - Fix: `resources/css/components/field.css:390` → wrap in `@media (hover: hover)` and scope to `.field:not(:has(.field-control:disabled)) .field-box:hover`. - Effort: S - Breaks API? no ### IN-03 · checkbox / radio / toggle · a control without a label has an 18–52 px hit target - Severity: must-fix - M3 says: checkbox "Target size 48dp", radio "Target size 48dp", switch "Target | Size | 48dp" (RB §Checkbox/Specs, §Radio Button/Specs, §Switch/Specs); "The size of state layers is 40dp while the interactive target size is 48dp" (RF §States); and for all three, "Don't apply density by default — this drops targets below the 48x48 CSS px minimum" (RB §Checkbox/Accessibility, §Radio/Accessibility, §Switch/Accessibility). - Library does: in `resources/views/components/checkbox.blade.php:27-55` the `