Files
livewire-material/docs/audits/m3-alignment/inputs.md
T
Andreas Reinhold / reiniandClaude Fable 5.1 651a513d1e Plan the Material 3 alignment, with the audits and Google's references
Every foundations, styles and components page of m3.material.io (238, from the
sitemap) extracted into docs/reference/m3, five audit reports with 142 findings in
docs/audits/m3-alignment, and the 2.0.0 plan in docs/plans/material-3-alignment.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 03:53:40 +02:00

55 KiB
Raw Blame History

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: <x-search> announces nothing when results appear and puts aria-expanded on a plain <input type="search"> 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 1852 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 <input type="search"> 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 <div> whose children are <x-list-item>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. <x-choices> 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 <x-list>), and either (a) mirror <x-choices>: 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 <span aria-live="polite"> 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 1852 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 <label> wraps only <span data-checkbox> when label and hint are both blank, and [data-checkbox] is 1.125rem square (resources/css/components/selection.css:105-108). The 40 px state layer is a ::before with pointer-events: none (selection.css:51-59), so it adds nothing to the target. Result: an 18 × 18 target. [data-radio] is 20 × 20 (selection.css:169-172) and [data-switch] is 52 × 32 (selection.css:225-231) — 32 px tall, also under 48. A label-less checkbox is a real use (the select-all in a table header, a row checkbox), and <x-toggle> documents aria-label as the label-less path (toggle.blade.php:5), so the case is expected.
  • Fix: in resources/css/components/selection.css, add a pointer-catching pseudo-element rather than growing the visual box — e.g. [data-checkbox]::after { content:""; position:absolute; inset:-0.9375rem; } (18 → 48), [data-radio]::after { inset:-0.875rem; } (20 → 48), and [data-switch]::after { content:""; position:absolute; inset:-0.5rem 0; } (32 → 48). Keep them behind the input so clicks still reach it, or put min-block-size:3rem on the [data-selection] row.
  • Effort: S
  • Breaks API? no

IN-04 · slider · the M-size handle is 44 px where M3 Expressive specifies 52 dp

  • Severity: should-fix
  • M3 says: sliders/specs, "Handle height | 44dp | 44dp | 52dp | 68dp | 108dp" for XS/S/M/L/XL (RB §Sliders/Specs, verbatim site table).
  • Library does: resources/views/components/slider.blade.php:247-250 maps 'md' => 'h-11 group-data-focused/thumb:h-9.5' — 44 px, the same as XS and S. The header comment at slider.blade.php:44 states the intent as "handles of 44, 44, 44, 68 and 108px", citing MDC-Android's md.comp.slider.* tokens, which disagrees with the site table for M.
  • Fix: slider.blade.php:248'md' => 'h-13 group-data-focused/thumb:h-11.5' (52 px, 46 px focused, keeping the 6 px focus-ring decrease the comment describes), and correct the header comment.
  • Effort: S
  • Breaks API? no

IN-05 · slider · the value indicator is a 32 × 36 pill where M3 Expressive specifies 44 × 48

  • Severity: should-fix
  • M3 says: sliders/specs, "Label container height | 44dp (all sizes)" and "Label container width | 48dp (all sizes)" (RB §Sliders/Specs). Container Inverse surface, label Inverse on surface, font Label large (SliderTokens.ValueIndicatorContainerColor / ValueIndicatorLabelTextColor / ValueIndicatorLabelTextFont).
  • Library does: resources/views/components/slider.blade.php:346h-8 min-w-9 ... px-2.5 = 32 px tall, 36 px minimum width. The colours and font are right (bg-inverse-surface text-inverse-on-surface type-label-lg). The header at slider.blade.php:39-41 explains the 32 px as Flutter's RoundedRectSliderValueIndicatorShape, i.e. a non-Expressive source.
  • Fix: slider.blade.php:346h-11 min-w-12; then re-derive the $labelBottom offsets at slider.blade.php:252 (they are hard-coded against a 32 px pill: calc(50%+1.625rem) = handle half + 4 px gap + 32 px). Note M3 also gives ValueIndicatorActiveBottomSpace = 12dp in SliderTokens.kt where the library uses 4 px — worth reconciling in the same change.
  • Effort: M
  • Breaks API? no

IN-06 · slider · stop indicators and the inset icon use inverted colour roles

  • Severity: should-fix
  • M3 says: the 9 slider colour roles, in anatomy order, are "1. Inverse surface 2. Inverse on surface 3. Primary 4. On primary 5. Primary 6. Secondary container 7. On secondary container 8. On secondary container 9. On primary" — docs/reference/m3/raw/components_sliders_specs.md:150-160 (verbatim). That is: a stop/icon on the active track is On primary; a stop/icon on the inactive track is On secondary container.
  • Library does: resources/views/components/slider.blade.php:227-238 inverts both — $tickInk['primary'] = 'bg-primary data-active:bg-secondary-container' (so an inactive-track tick is primary and an active-track tick is secondary-container), and $iconInk['primary'] = 'text-primary data-active:text-secondary-container'. The end stop indicators at slider.blade.php:311 use $activeInk (bg-primary) even though they sit on the inactive track. Contested reading: SliderTokens.kt has StopIndicatorColor = SecondaryContainer and StopIndicatorColorSelected = SecondaryContainer, which would make the active-track stop secondary-container (the library's choice) and the inactive-track stop invisible. The site's own colour list is the more coherent of the two and is the newer Expressive text.
  • Fix: in slider.blade.php:227-238, swap to 'primary' => 'bg-on-secondary-container data-active:bg-on-primary' (and the parallel text-on-… for $iconInk), and give $stops at slider.blade.php:311 bg-on-secondary-container. Repeat for the secondary/tertiary/error/success/warning/info rows.
  • Effort: S
  • Breaks API? no

IN-07 · search · the view draws the divided style's divider, which M3 Expressive deprecates

  • Severity: should-fix
  • M3 says: "| Style | Contained | -- | Available | | | Divided | Available | Not recommended. Use contained. |" and "In the divided (baseline) style, a divider separates the search bar and results" — docs/reference/m3/raw/components_search_specs.md:106-107,134. The divided style "Doesn't have the latest visual style, motion, or flexibility" (ibid. :46).
  • Library does: resources/css/components/search.css:160[data-search-results] { border-top: 1px solid var(--md-sys-color-outline); }. The file's own header (search.css:6-9) describes the contained style. Second reading: Compose's SearchBar does draw a HorizontalDivider between the input and the content, so this may be tracking the Compose implementation rather than the site; the site's contained-style images show no divider.
  • Fix: resources/css/components/search.css:160 → drop the border-top (keep padding-block: 0.5rem). If a separation cue is wanted, use outline-variant, not the darker outline role.
  • Effort: S
  • Breaks API? no

IN-08 · search · the full-screen layout uses the docked layout's container colour

  • Severity: should-fix
  • M3 says: "Full-screen search color roles used in light and dark themes: 1. Surface container low 2. On surface variant 3. On surface variant 4. Surface container high 5. On surface variant 6. On surface" vs "Docked search color roles: 1. Surface container high …" — docs/reference/m3/raw/components_search_specs.md:196-232. RB §Search/Behaviour also warns: "Never use Surface container high on a Surface container background (contrast too low, blends in) — keep container roles at least one step apart."
  • Library does: resources/css/components/search.css:140 sets [data-search-view] { background-color: var(--md-sys-color-surface-container-high); } for both presentations; search.css:174-181 changes only geometry for [data-full-screen].
  • Fix: add [data-search][data-full-screen] [data-search-view] { background-color: var(--md-sys-color-surface-container-low); } to resources/css/components/search.css around line 174.
  • Effort: S
  • Breaks API? no

IN-09 · search · the docked view has no scrim

  • Severity: should-fix
  • M3 says: "Docked (opens a results list below the bar with a scrim over the rest of the content — best for medium/expanded windows)" — RB §Search/Variants and configurations.
  • Library does: resources/views/components/search.blade.php:27-37 raises the root to z-index: 50 when open (search.css:24-26) and closes on pointerdown.outside, but paints nothing over the page. The full-screen presentation covers the page instead (search.css:174-181), which is correct for compact.
  • Fix: in search.blade.php, add a <div data-search-scrim x-show="open && ! fullScreen"> before [data-search-bar] and style it in search.css as position: fixed; inset: 0; z-index: -1; background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent); — the same 32 % the datepicker backdrop uses (datepicker.css:111-113).
  • Effort: M
  • Breaks API? no

IN-10 · timepicker · the 24-hour vertical time selector stays 96 px where M3 specifies 114 dp

  • Severity: should-fix
  • M3 says: time-pickers/specs, "Time selector container | Width | 96dp" and "| Width (24h vertical) | 114dp" (RB §Time Pickers/Specs); confirmed by docs/reference/m3/tokens/TimePickerTokens.ktTimeSelector24HVerticalContainerWidth = 114.0.dp.
  • Library does: resources/css/components/timepicker.css:106-107 sets [data-timepicker-box] { width: 6rem; height: 5rem; } unconditionally. The dial already carries the cycle as data-cycle="24" on [data-timepicker-dial] (timepicker.blade.php:265), but the display boxes never read it. The header comment (timepicker.blade.php:33) names only "96×80 time selector boxes", so the 114 dp token was not carried over.
  • Fix: put the cycle on the display too (timepicker.blade.php:214x-bind:data-cycle="is24 ? '24' : '12'") and add [data-timepicker-display][data-cycle="24"] [data-timepicker-box] { width: 7.125rem; } in timepicker.css, scoped out of the landscape block at timepicker.css:403-447.
  • Effort: S
  • Breaks API? no

IN-11 · timepicker · the selected AM/PM button is primary-container, not tertiary-container

  • Severity: should-fix
  • M3 says: the dial colour list includes "Tertiary container, On tertiary container" (RB §Time Pickers/Specs, 17 site roles), and docs/reference/m3/tokens/TimePickerTokens.kt gives PeriodSelectorSelectedContainerColor = TertiaryContainer, PeriodSelectorSelectedLabelTextColor = OnTertiaryContainer, PeriodSelectorOutlineColor = Outline, PeriodSelectorOutlineWidth = 1.0.dp, PeriodSelectorContainerShape = CornerSmall. The time selector (hour/minute), separately, is PrimaryContainer / OnPrimaryContainer.
  • Library does: resources/css/components/timepicker.css:160-169 gives the selected period button background-color: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container) — identical to the selected hour/minute box at timepicker.css:120-124, so the two different M3 emphases collapse into one. It also drops the 1 dp Outline stroke and replaces the shared corner-small container with two separate pills (full → 12 px on selection). The header (timepicker.blade.php:37-39) justifies this as Compose's current isUpdatedTimepickerToggleEnabled default and mentions "not the outlined pair its tokens still describe" — but not the tertiary→primary colour swap, which is the part a reader would not expect.
  • Fix: either restore tertiary-container / on-tertiary-container at timepicker.css:165-167 (keeping the shape morph, which is a defensible Expressive reading), or document the colour swap explicitly in the header comment. Recommend the former: it restores the hour/minute vs. AM/PM distinction M3 draws.
  • Effort: S
  • Breaks API? no

IN-12 · checkbox · the tick and dash icons are 16 px where M3 specifies 18 dp

  • Severity: should-fix
  • M3 says: checkbox/specs, "Icon size | 18dp"; docs/reference/m3/tokens/CheckboxTokens.ktIconSize = 18.0.dp (= ContainerSize, i.e. the icon fills the box).
  • Library does: resources/views/components/checkbox.blade.php:41-42 renders both <x-icon name="check" class="size-4" /> and name="remove" class="size-4" — 16 px inside an 18 px box (selection.css:105-108). resources/css/components/selection.css:1-3 cites CheckboxTokens as the source, so the 18 dp figure was rounded to Tailwind's nearest step.
  • Fix: checkbox.blade.php:41-42class="size-4.5" (Tailwind 4's 4.5 = 18 px; the chip already uses size-4.5 for its 18 dp icons, chip.blade.php:233,254,282).
  • Effort: S
  • Breaks API? no

IN-13 · chip-set · chips are Tab stops only; M3's arrow-key navigation is missing

  • Severity: should-fix
  • M3 says: chips/accessibility keyboard table — "Tab | Moves focus to enabled chip or chip group; Space or Enter | Activates, selects, or deselects the focused chip; Backspace or Delete | Removes currently focused input chip; Arrows | Moves focus between chips" (RB §Chips/Accessibility), and "only one chip can be in focus even though many can be selected".
  • Library does: resources/views/components/chip-set.blade.php:31-54 renders role="group" with every chip its own natural tab stop; resources/js/chips.js implements Backspace/Delete (chips.js:38-52) and focus hand-off on removal (chips.js:60-79) but binds no arrow keys. A ten-chip filter set therefore costs ten Tab presses to pass.
  • Fix: add an x-on:keydown.arrow-right/left/home/end handler to the [data-chip-set] div in chip-set.blade.php (both the scroll and wrap branches) that moves focus across [data-chip] using the existing CONTROLS selector from chips.js:13, with a roving tabindex so the set is one tab stop. Note the trade-off: a filter chip is a real <input type="checkbox"> in a <label> (chip.blade.php:181-189), so roving tabindex must be applied to the inputs, not the labels.
  • Effort: M
  • Breaks API? no

IN-14 · chip (input) · the remove button's target is 34 px wide against M3's 48 dp minimum

  • Severity: should-fix
  • M3 says: input chip specs, "Target size for close icon | Min 48dp"; and "minimum target size 48dp regardless of placement/density (target may extend beyond the visible chip container)" — RB §Chips/Specs and §Chips/Behaviour.
  • Library does: resources/views/components/chip.blade.php:250 gives the remove button after:-inset-x-2 after:-inset-y-3.75 on an 18 px icon (size-4.5, line 247) → 18 + 2×15 = 48 px tall but 18 + 2×8 = 34 px wide.
  • Fix: chip.blade.php:250after:-inset-x-3.75 after:-inset-y-3.75 (48 × 48). The chip's own 48 px target strip (chip.blade.php:93) already overlaps the label, so the extra 7 px each side only overlaps the label's target, which M3 explicitly permits ("target may extend beyond the visible chip container"); the remove button is later in source order and sits above it.
  • Effort: S
  • Breaks API? no

IN-15 · pagination · page and step controls are 40 px targets

  • Severity: should-fix
  • M3 says: "The size of state layers is 40dp while the interactive target size is 48dp" (RF §States); accessibility: keep targets ≥ 48 × 48 CSS px (RF §Accessibility/Values). Pagination is not an M3 component, so foundations are the yardstick per the brief.
  • Library does: resources/views/pagination/livewire/tailwind.blade.php:22$step = 'grid size-10 place-items-center …' (40 px) is used for the page buttons, the prev/next buttons and the current page, with gap-1 between them (line 35); same at resources/views/pagination/laravel/tailwind.blade.php:7-9. (The two simple-tailwind views are not affected — they use <x-button variant="outlined">, whose sizing belongs to the buttons group.) The file's own header (line 7) calls them "40px icon-button targets", conflating M3's 40 dp state layer with its 48 dp target.
  • Fix: keep the 40 px visual and add the target — in the two full pagination views change $step to 'relative grid size-10 place-items-center after:absolute after:-inset-1 …', or wrap each button in a 48 px grid cell. Also correct the header comment.
  • Effort: S
  • Breaks API? no

IN-16 · table · density is applied by default on every fine-pointer device

  • Severity: should-fix
  • M3 says: "Don't apply density by default — this drops targets below the 48x48 CSS px minimum; offer an explicit density opt-in instead, keeping opt-out targets at ≥48x48 CSS px" — RF §Accessibility/Rules, repeated on checkbox, chips, radio, switch and text-field accessibility pages. (Data tables themselves have no M3 spec — RB §Data Tables confirms the component is absent from m3.material.io — so foundations govern.)
  • Library does: resources/css/components/table.css:37-45 tightens --cell-y from 0.75rem to 0.5rem under @media (pointer: fine) with no opt-out, and to 0.25rem at data-size="xs". With body-md's 20 px line box that is a 36 px row (28 px at xs). Even on a coarse pointer the default row is 12 + 20 + 12 = 44 px, under 48. Rows are clickable (data-list-row, resources/js/list-rows.js), so the row is a target.
  • Fix: resources/css/components/table.css — raise the coarse-pointer default to --cell-y: 0.875rem (14 px → 48 px rows) and turn the fine-pointer step into an opt-in ([data-table][data-density="compact"]) rather than a media query, exposed as a density prop on resources/views/components/table.blade.php:10-12.
  • Effort: M
  • Breaks API? yes — adds a prop and changes default row height; existing size="xs" keeps its meaning.

IN-17 · sort-header · the sort button's hit target is about 20 px tall

  • Severity: should-fix
  • M3 says: 48 × 48 minimum target, no density by default (RF §Accessibility/Values and /Rules).
  • Library does: resources/views/components/sort-header.blade.php:21-30 renders class="group/sort focus-ring inline-flex cursor-pointer items-center gap-1 rounded-corner-xs" with no padding of its own. Its box is the title-sm line box (20 px, resources/css/tokens/type.css:45) plus a 16 px icon — roughly 20 px tall. The surrounding <th> padding (table.css:47-51) belongs to the cell, not the button, so it is not part of the target.
  • Fix: sort-header.blade.php:26 → add -my-2 py-2 -mx-1 px-1 (or min-h-12 with the <th> padding removed for sorting cells) so the button covers its cell.
  • Effort: S
  • Breaks API? no (the class prop still lands on the <th>)

IN-18 · field · the error state has only one visual indicator

  • Severity: should-fix
  • M3 says: "States have two visual indicators to ensure accessibility" (RF §States/Principles), and for text fields specifically: error text "strongly recommended to pair with a trailing error icon for a second, non-color-dependent cue" (RB §Text Fields/Behaviour); a non-actionable error icon gets the accessibility label "Error." (RB §Text Fields/Accessibility).
  • Library does: resources/views/components/field.blade.php:39-45 sets only data-invalid, and resources/css/components/field.css:403-415,446-448 changes only colours — outline, label and support text all move to error. No icon is added. <x-input>'s icon-right slot is free, so the hook exists.
  • Fix: in field.blade.php, when $messages !== [] and no $trailing is set, render <x-icon name="error" class="field-trailing size-(--field-icon)" aria-label="{{ __('Error') }}" /> into the trailing position; suppress it at data-size="xs" where there is no room.
  • Effort: S
  • Breaks API? no (additive; callers passing icon-right keep theirs)

IN-19 · field · a read-only field is drawn with a dashed outline

  • Severity: nice-to-have
  • M3 says: "Read-only fields: same visual style as an editable field, but clearly labeled read-only" — RB §Text Fields/Behaviour.
  • Library does: resources/css/components/field.css:419-421.field[data-readonly] .field-outline { border-style: dashed; }, set from input.blade.php:38, textarea.blade.php:27 and datepicker.blade.php:161. The comment at field.css:417-418 explains the intent (a date picker makes its own input read-only). The native readonly attribute does carry the state to AT, so the accessibility half of M3's rule is met; only "same visual style" is not.
  • Fix: either drop the dashed rule, or keep it and record it in the header comment as a deliberate departure. If kept, note that the date picker's docked field is not read-only (datepicker.blade.php:182-189 binds typing), so only mode="modal"/"input" and caller-set readonly fields are affected.
  • Effort: S
  • Breaks API? no

IN-20 · search · the bar has no minimum or maximum width and does not widen on focus

  • Severity: nice-to-have
  • M3 says: search bar "Container | Width | Min: 360dp, max: 720dp"; docked container the same; and the Expressive update lists "Motion: the search bar grows wider when focused" — RB §Search/Specs and §Search/Status.
  • Library does: resources/css/components/search.css:28-41 sizes [data-search-bar] only by height; width is the parent's. [data-search-view] (search.css:130-147) is inset-inline: 0 with max-height: min(40rem, 70dvh) and no min-height (M3: docked height "Min: 240dp, max: 2/3 of screen height" — 70 dvh vs 66.7 % is close enough to ignore). No focus-widening transition.
  • Fix: search.css:28 → add max-width: 45rem; (720 px) to [data-search-bar] and [data-search-view], and min-height: 15rem (240 px) to the docked view. The focus-widen motion would need a --search-width custom property toggled by [data-open] and animated on the effects-default spring.
  • Effort: S (widths) / M (motion)
  • Breaks API? no

IN-21 · search · the unfocused leading padding is 16 px where M3 gives 24 dp

  • Severity: nice-to-have
  • M3 says: unfocused search bar "Leading padding | Unfocused: 24dp, focused: 12dp" and "Trailing padding | Unfocused: 24dp, focused: 12dp"; contained focused style "Leading padding | 16dp | Trailing padding | 16dp" — docs/reference/m3/raw/components_search_specs.md:338-346,376-386.
  • Library does: resources/css/components/search.css:28-35 gives the bar padding-inline: 0.25rem and resources/css/components/search.css:53-61 gives the leading/clear boxes width: 3rem — so the icon glyph starts 4 + 12 = 16 px from the container edge in every state.
  • Both readings: the site's measurement diagram is an image, so "leading padding" may mean edge-to-tap- target (24 + 12 = 36 px to the glyph, which looks wrong in a 56 dp bar) or edge-to-glyph (24 px). Under the second reading the focused/contained value (16 dp) is exactly what the library does and only the unfocused state is 8 px short.
  • Fix: if pursued, search.css:28padding-inline: 0.75rem when :not([data-open]) and 0.25rem when [data-open], transitioned on the effects-fast duration.
  • Effort: S
  • Breaks API? no

IN-22 · datepicker · Shift+M and Shift+Y do not reach the month/year dropdowns

  • Severity: nice-to-have
  • M3 says: date-pickers/accessibility keyboard table — "Shift + M | Moves to the month list dropdown; Shift + Y | Moves to the year list dropdown" (RB §Date Pickers/Accessibility).
  • Library does: resources/js/datepicker.js:578-600 handles Arrows, Home, End, PageUp/PageDown (with Shift for ±12 months), Space and Enter; there is no Shift+M / Shift+Y branch. The menu buttons exist and are focusable (datepicker.blade.php:287-296,309-318,328-337).
  • Fix: add two cases to the gridKey map in resources/js/datepicker.js:585-592 that focus [data-datepicker-menu-button] (the month one in the docked layout, the combined one in the modal).
  • Effort: S
  • Breaks API? no

IN-23 · choices · the combobox has no Home/End keys

  • Severity: nice-to-have
  • M3 says: nothing directly — M3 has no combobox component. WAI-ARIA APG's combobox-with-listbox pattern, which the component already follows (role="combobox", aria-autocomplete, aria-activedescendant), specifies Home/End within the popup.
  • Library does: resources/views/components/choices.blade.php:140-145 binds ArrowDown, ArrowUp, Enter, Escape and Tab only.
  • Fix: add x-on:keydown.home.prevent / .end.prevent at choices.blade.php:142 setting active = 0 / active = filtered.length - 1 and calling reveal().
  • Effort: S
  • Breaks API? no

IN-24 · slider · Space+Arrows (the large-interval step) is not implemented

  • Severity: nice-to-have
  • M3 says: sliders/accessibility keyboard table — "Space & Arrows | Increase/decrease the value by one interval or one stop indicator" (RB §Sliders/Accessibility), on top of Arrows (one value) and Home/End.
  • Library does: the native <input type="range"> supplies Arrows and Home/End (slider.blade.php:357-379); the SKILL documents PageUp/PageDown as a tenth of the range (SKILL.md:562), which is an extension, not M3's Space+Arrows.
  • Fix: add a keydown handler in resources/js/slider.js that, while Space is held, multiplies the arrow step. Low value — PageUp/PageDown already covers the need and is the more common web idiom.
  • Effort: S
  • Breaks API? no

IN-25 · chips · no dragged state

  • Severity: nice-to-have
  • M3 says: chips are the only component in this group whose state list includes Dragged (RB §Chips/Specs, "States (site, all 4 variants): Enabled, Disabled, Hovered, Focused, Pressed, Dragged"); dragged state layer opacity is 0.16 (RF §States/Values). Input chips also support "reordering, moving between fields ('To' → 'CC')" (RB §Chips/Behaviour).
  • Library does: resources/views/components/chip.blade.php:158 and the state-layer utility (resources/css/tokens/state.css:13-45) cover hover 8 %, focus 10 % and active 10 % only. No chip is draggable.
  • Fix: only worth doing alongside a reordering feature — then add [draggable]:active/[data-dragged] at 16 %.
  • Effort: L
  • Breaks API? no (additive)

IN-26 · timepicker · the AM/PM buttons are toggle buttons, not radios

  • Severity: nice-to-have
  • M3 says: time-pickers/accessibility labelling table — "AM/PM selection | AM or PM | Radio button (in list) (Wiz/Compose) | Checkbox (in list) (Android Views)" (RB §Time Pickers/Accessibility).
  • Library does: resources/views/components/timepicker.blade.php:239-251 and :333-345 render two <button aria-pressed> in a role="group". A screen reader hears "AM, toggle button, pressed" rather than "AM, radio button, selected, 1 of 2"; both convey the state, and aria-pressed avoids needing arrow-key roving, so this is defensible. Noted because the M3 table is explicit.
  • Fix: if changed, role="radiogroup" on the wrapper and role="radio" aria-checked on the buttons, with Left/Right arrow handling.
  • Effort: S
  • Breaks API? no

IN-27 · timepicker · the disabled period button uses a 10 % container instead of 12 %

  • Severity: nice-to-have
  • M3 says: disabled container opacity is 0.12, disabled content 0.38 (RF §States/Values). M3 defines no disabled state for the period selector at all (its state list is Enabled, Hover, Focus, Pressed — RB §Time Pickers/Specs), so this is the library's own min/max extension and only has to stay inside M3's rules.
  • Library does: resources/css/components/timepicker.css:172-174background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent) with the content at 38 % (correct).
  • Fix: timepicker.css:17312%.
  • Effort: S
  • Breaks API? no

IN-28 · radio · inline lays radios in a row, which M3 cautions against

  • Severity: nice-to-have
  • M3 says: radio buttons are "Always vertically stacked (never horizontal — flagged as a 'Caution', not an outright 'Don't')" (RB §Radio Button/Behaviour); also "Recommended for five or fewer options" and "One option should always be pre-selected in a radio group".
  • Library does: resources/views/components/radio.blade.php:18,39 offers inlinesm:flex sm:flex-wrap sm:gap-x-6, and value (the pre-selection) is optional (radio.blade.php:19). Neither is wrong — M3's is a caution — but the SKILL entry (SKILL.md:552) presents inline without the caveat.
  • Fix: add one sentence to SKILL.md:552 and to the radio.blade.php header noting M3 prefers a vertical stack, and that a group should normally ship a pre-selected value.
  • Effort: S
  • Breaks API? no

IN-29 · password · the reveal button carries both a changing label and aria-pressed

  • Severity: nice-to-have
  • M3 says: "Interactive trailing icons need a functional label (e.g., 'Show password' / 'Hide password')" — RB §Text Fields/Accessibility. It says nothing about a pressed state.
  • Library does: resources/views/components/password.blade.php:41-44 binds both aria-label (flipping between "Show password" and "Hide password") and aria-pressed. Screen readers then announce e.g. "Hide password, toggle button, pressed" — the state twice, once inverted relative to the label's promise. M3 names only the label pattern; the repo's own recent commit "Keep aria-pressed off a selected link button" shows the same concern elsewhere.
  • Fix: password.blade.php:42,44 → drop aria-pressed and keep the flipping label (the M3-documented pattern), or keep aria-pressed with a fixed "Show password" label. Not both.
  • Effort: S
  • Breaks API? no

IN-30 · table · the row divider is a colour outside the role set

  • Severity: nice-to-have
  • M3 says: colour is applied through the roles; RF §Design tokens frames system tokens as the sanctioned vocabulary and the styles pages give no "60 % of outline variant" value. Opacity modifiers in M3 are reserved for state layers (8/10/10/16 %) and disabled (12/38 %).
  • Library does: resources/css/components/table.css:66-68border-bottom: 1px solid color-mix(in srgb, var(--md-sys-color-outline-variant) 60%, transparent). (The header rule at table.css:58 correctly uses full outline-variant.) Data tables have no M3 spec, so this is a foundations-level nit, not a spec breach.
  • Fix: table.css:67var(--md-sys-color-outline-variant), and let the heavier header rule read as heavier by keeping the header's title-sm weight contrast, or use surface-container-highest if a lighter rule is wanted.
  • Effort: S
  • Breaks API? no

Deliberate deviations

The code explains these; the verdict column is whether the stated reason survives Google's text.

Where Deviation and its stated reason Holds up?
field.css:8-12 The outlined field's notch is MUI's <fieldset>/<legend> technique so the gap is right on any background Yes. M3 does not prescribe a mechanism; the rendered result (4 dp corners, 1 dp / 2 dp focus outline, body-small floated label) matches OutlinedTextFieldTokens exactly.
field.blade.php:9-13 size="sm" (40 px) / xs (32 px) for unlabelled toolbar controls Yes — M3's rule is "don't apply density by default; offer an explicit opt-in" (RF §Accessibility). These are opt-in and documented as unlabelled-control sizes.
field.css:417-421 Read-only fields get a dashed outline "marked on the field, not read off the control" Partly — see IN-19; M3 says a read-only field keeps the same visual style.
select.blade.php:1-8 The native <select> is kept "on purpose", with the M3 menu only where appearance: base-select exists Yes. M3's exposed-dropdown guidance is about the rendered result, and the native control gives the keyboard, type-ahead and AT semantics for free.
file.blade.php:1-13 "M3 has no file field" — the native input is kept and only ::file-selector-button is restyled as a tonal pill Yes. RB confirms no file-input component exists; the pill uses secondary-container / on-secondary-container with 8 % hover and 12 %/38 % disabled — inside M3's rules.
chip.blade.php:29-30 "input chips are flat only, as in M3" Partly. RB §Chips/Variants says "All chips can be elevated (default elevation 0)"; Compose has no ElevatedInputChip, which is presumably the real source. The restriction is safe but the stated justification is the implementation, not the spec.
chip.blade.php:88-89 Padding is 1 px short of the token because a CSS border takes room where Compose draws it inside Yes — a correct compensation; the rendered box matches 16/8/12/4 dp.
timepicker.blade.php:37-39 The period selector follows Compose's isUpdatedTimepickerToggleEnabled default — "two separate shape-morphing toggle buttons in primary-container, not the outlined pair its tokens still describe" Partly — see IN-11. The shape morph is a plausible Expressive reading; the tertiary→primary colour swap is undocumented and the site still lists Tertiary container.
datepicker.js:18-19 "The keyboard is WAI-ARIA's date picker dialog: … Home and End go to the start and end of the week" Defensible. M3's own table says "Home/End
slider.blade.php:39-41 The value indicator is "M3's as Flutter draws it (RoundedRectSliderValueIndicatorShape): a 32px inverse-surface pill" because "Compose leaves the value label to the app" No — see IN-05. M3 Expressive's sliders/specs table gives an explicit 44 dp × 48 dp label container that neither source was consulted for.
search.css:83-87 [data-search-bar] button:focus-visible { outline-offset: -3px } instead of the package's +2px Yes, in context — the ring is drawn inside a 48 px box that is flush with the 56 px bar; a +2 px ring would clip.
table.css:7-10 Density is keyed "on the pointer, not the width, so a touch tablet in landscape keeps rows a finger can hit" Partly — the pointer key is a good idea, but the coarse-pointer default is still 44 px (IN-16).
chip-set.blade.php:12-14 scroll keeps chips on one line that scrolls sideways "as M3 lays chips out on a narrow screen" Yes. RB §Chips/Behaviour: "place inline as a single row, wrapping to new lines when needed, or scroll horizontally if the field is only one row tall".

Token-citation disagreements

Places where a header comment's androidx/MDC figure contradicts the M3 site's spec table:

File:line Comment claims M3 site says Effect in code
slider.blade.php:44 "handles of 44, 44, 44, 68 and 108px" (md.comp.slider.* tokens) 44 / 44 / 52 / 68 / 108 (sliders/specs) Yes — slider.blade.php:248 is 44 px (IN-04)
slider.blade.php:40-41 value indicator "a 32px inverse-surface pill … 4px above the handle" (Flutter) Label container 44 dp high, 48 dp wide, all sizes; ValueIndicatorActiveBottomSpace = 12dp Yes — slider.blade.php:346,252 (IN-05)
timepicker.blade.php:33 "96×80 time selector boxes" 96 dp, and 114 dp for the 24-hour vertical layout Yes — timepicker.css:106 (IN-10)
timepicker.blade.php:37-39 period selector "in primary-container, not the outlined pair its tokens still describe" PeriodSelectorSelectedContainerColor = TertiaryContainer; site colour list includes Tertiary container / On tertiary container Yes — timepicker.css:165-167 (IN-11)
selection.css:1-3 cites CheckboxTokens IconSize = 18.0.dp Yes — checkbox.blade.php:41-42 uses size-4 (16 px) (IN-12)
search.css:1-10 cites SearchBarTokens / SearchViewTokens; "surface-container-high" for the view Docked: Surface container high ✓; Full-screen: Surface container low Yes — search.css:140 (IN-08)
pagination/livewire/tailwind.blade.php:7 "Page numbers are 40px icon-button targets" 40 dp is the state layer; the target is 48 dp (RF §States) Yes — size-10 (IN-15)
chip.blade.php:35-38 32 px, small corner, label-large, 18 px icons, 24 px avatar, 16/8/8 and 12/8/4 padding identical (chips/specs, all four variants) No — all correct
field.css:8-12 FilledTextFieldTokens / OutlinedTextFieldTokens 56 dp height, CornerExtraSmall = 4 dp, outline 1/2 dp, indicator 1/2 dp, disabled 12 %/38 %/4 %, BodyLarge input, BodySmall supporting No — all correct
datepicker.blade.php:39-45 360 px, extra-large (modal) / large (docked) corner, elevation 3, surface-container-high, 40 px days in 48 px cells, today outlined primary, range middle secondary-container DatePickerModalTokens: 360 × 568, CornerExtraLarge, Level3, Surface container high, cell 40 × 40, today outline 1 dp, range indicator 40 dp No — all correct (the 48 px cell is the library's own target addition, which M3's 48 dp target rule requires)
selection.css:219-223 switch handle centres at 16 px / 36 px, sizes 16 / 24 / 28 px "measured inside the track's 2px outline" SwitchTokens: track 32 × 52, outline 2, handles 16 / 24 / 28 Numbers correct; the phrase "inside the track's 2px outline" is wrong (the handle is positioned against the track's outer box, selection.css:249-251) — comment only

Aligned

Verified correct; no need to re-check.

  • Text field geometry — 56 px container, 16 px side padding (12 px with icons via --field-start), 24 px leading icon, 4 dp corner, 1 px → 2 px outline/indicator on focus, body-large input, body-small floated label and supporting text, 4 px supporting-text top padding (field.css:27-73,295-344,439-444).
  • Text field colours and states — outline outline, hover on-surface, focus primary, filled container surface-container-highest, filled resting indicator on-surface-variant, filled hover on-surface 8 %, disabled outline 12 % / content 38 % / filled container 4 % — all match OutlinedTextFieldTokens / FilledTextFieldTokens (field.css:33-44,390-437,450-520).
  • Error replaces the hint, never stacks (field.blade.php:73-81), with role="alert" on the error — exactly RB §Text Fields/Behaviour and /Accessibility.
  • Required asterisk is in the label (field.css:346-349, in both the label and the notch legend) — RB §Text Fields/Accessibility, "the label must include the asterisk".
  • Date input is never masked while typingdatepicker.js:350-366 only commits a whole, valid date; parse (datepicker.js:274-300) accepts any separator and optional leading zeros. This is the exact rule in RB §Date Pickers/Accessibility.
  • Switch — every dimension matches SwitchTokens: 52 × 32 track, 2 px outline, 16 / 24 / 28 px handle, 40 px state layer, 16 px icons, and the full colour map (selected track primary, handle on-primary, hover/focus/pressed handle primary-container, unselected track surface-container-highest, handle outline, disabled 12 %/38 %) — selection.css:219-343.
  • Checkbox / radio boxes — 18 px box with 2 px on-surface-variant outline and 2 dp corner, 20 px radio with a 2 px ring, both in 40 px state layers, primary when selected, error variants (selection.css:103-217). Indeterminate uses a dash icon and is kept in sync through the indeterminate DOM property (field.js:32-34,49-80) — the only correct way to do it in HTML.
  • State-layer opacities everywhere in the group — 8 % hover (always behind @media (hover: hover)), 10 % focus, 10 % press, tinted with the content's "on" colour: selection.css:73-101, field.css:268-282, datepicker.css:250-268, chip.blade.php:158,249, state.css:13-45.
  • Focus ring — 3 px secondary at +2 px offset, consistently (state.css:46-53, field.css:274-278, selection.css:98-101,241-244, datepicker.css:270-275, timepicker.css:207-208, slider.blade.php:335).
  • Chips — 32 px height, 8 dp corner, label-large, 18 px icons, 24 px avatar, 16/8 dp label padding (1 px short for the CSS border), 8 dp between elements, 8 px chip-to-chip gap, a 48 px ::after target strip, filter selected secondary-container/on-secondary-container, stroke outline-variant (the Aug-2024 update), elevated on surface-container-low at elevation 1 → 2 on hover, disabled 12 %/38 % (chip.blade.php:91-125,197-198, chip-set.blade.php:51).
  • Chip keyboard removal — Backspace/Delete on a focused input chip removes it and moves focus to the previous/next chip, with a wire:key-aware re-focus across Livewire morphs (chips.js:38-118). Remove buttons are named "Remove {label}" (chip.blade.php:242). Exactly RB §Chips/Accessibility.
  • Date picker anatomy and geometry — 360 px container, 120 px header (128 px for range), headline-large headline (title-large for range), surface-container-high at elevation 3, corner-xl modal / corner-lg docked, 40 px day in a 48 px cell, body-large day and weekday labels, today outlined 1 px primary, selected primary/on-primary, range band secondary-container/on-secondary-container 40 px tall, 72 × 36 year cells, divider under the header (datepicker.css:64-436).
  • Date picker semantics<table role="grid"> with <th scope="col">, per-cell aria-label naming the full date, aria-selected, aria-current="date", roving tabindex, aria-live month announcement, and both entry methods always available — matching M3's labelling table (datepicker.blade.php:284,344-378).
  • Time picker geometry — 24 dp container padding, 96 × 80 time selectors in display-large with a corner-small container, 52 × 80 (vertical) / 216 × 38 (horizontal) period selector, 256 px dial in surface-container-highest with body-large numbers, 48 px selector handle, 8 px centre, 2 px track, 96 × 72 input fields in display-medium — every number in RB §Time Pickers/Specs (timepicker.css:50-460).
  • Slider — track heights 16/24/40/56/96, corners 8/8/12/16/28, 4 px handle narrowing to 2 px on press/focus, 6 px active-handle spacing, 4 px stop indicators, 24/24/32 px inset icons restricted to M/L/XL standard sliders only (never range or centered, exactly RB §Sliders/Variants), primary active track and handle, secondary-container inactive track, inverse-surface/inverse-on-surface value indicator in label-large, disabled 38 %/12 % (slider.blade.php:132-252,283-354).
  • Slider accessibility — real <input type="range">s, so Arrows/Home/End and AT come free; a range slider is role="group" with aria-labelledby and "Range start"/"Range end" handle labels; the drawing is aria-hidden (slider.blade.php:265-379).
  • Reduced motion — all six spring/easing duration tokens collapse to 0 ms under prefers-reduced-motion: reduce (resources/css/tokens/motion.css:63-75), and every component in this group animates through those tokens, so nothing needs a per-component opt-out.
  • <x-choices> combobox semanticsrole="combobox", aria-autocomplete="list", aria-controls, aria-expanded, aria-activedescendant, role="listbox"/role="option"/aria-selected, aria-disabled (choices.blade.php:121-179). This is what <x-search> should look like (IN-01).
  • Search geometry — 56 px pill, corner-full, surface-container-high, 8 % hover, body-large input, 48 px leading/clear targets, docked view corner-xl at elevation 3, 72 px full-screen header with safe-area padding, corner-none full-screen — all matching SearchBarTokens / SearchViewTokens (search.css:20-181). Escape releases focus, the back arrow dismisses, and the full-screen view traps focus and locks scroll (search.blade.php:29-32, search.js:39-97).
  • Pagination colour choice — the current page is secondary-container/on-secondary-container, never the action colour, with aria-current="page" (pagination/livewire/tailwind.blade.php:55). This is the right reading of RF §Interaction/Selection ("active indicator" components).
  • Table roles — headers title-small on-surface-variant over an outline-variant rule, selected row secondary-container/on-secondary-container keyed on aria-selected="true", aria-sort on the sorted <th> (table.css:53-77, sort-header.blade.php:20). RF §Interaction/Selection lists data tables among the components that inherit selection styling, and this is that styling.

Missing

M3 components, variants or configurations in this group that the library does not implement.

  • Slider — vertical orientation. RB §Sliders/Variants: the Expressive configuration table lists "Orientation | Horizontal (default) | Available | Available; Vertical | -- | Available". The library is horizontal only (slider.blade.php has no orientation prop). M3's caveat that range sliders must stay horizontal would apply.
  • Date pickers — the full-screen range picker. RB §Date Pickers/Anatomy describes a 14-element full-screen range-selection picker with a close (×) icon button and a Save confirmation, used at compact breakpoints. <x-datepicker range> always uses the 360 px modal dialog (datepicker.css:64-129), which on a phone is cramped for a two-month range scroll.
  • Search — the other two entry points. RB §Search/Behaviour names three: search bar (implemented), search app bar (a top app bar whose whole row is the search field, opening the Search view), and search icon button (search as a secondary action that expands). Only the first exists.
  • Search — the Expressive focus-widen motion ("the search bar grows wider when focused", RB §Search/Status). See IN-20.
  • Text fields — the character counter. RB §Text Fields/Variants lists "supporting text + character counter" as a configuration of both variants, with its own accessibility label ("Character count, 5/20"). No <x-input>/<x-textarea> prop provides it; maxlength passes through to the control but nothing is rendered.
  • Text fields — width bounds at medium/expanded. RB §Text Fields/Behaviour: "compact breakpoints can let a text field span full width; medium/expanded should bound it with flexible margins/other containers — never let it span the full width of a large screen." <x-form> (form.blade.php:14) is an unbounded one-column grid; nothing in the package caps a field's width. Currently the caller's job, undocumented in SKILL.md:521-535.
  • Checkbox — the ≥ 840 dp grouping rule. RB §Checkbox/Behaviour: "in expanded breakpoints (≥840dp), group checkboxes inside a contained region (e.g., a side sheet)". No affordance or documentation.
  • Chips — the horizontal-overflow affordances. RB §Chips/Accessibility prescribes either a leading "Show all" filter chip that reflows the list downward, or a leading button opening a menu of all chip options. <x-chip-set scroll> fades the scrollable edge instead — a reasonable web idiom, but neither of M3's two named patterns is offered.
  • Time picker — the 24-hour inner/outer ring is implemented (timepicker.blade.php:126-129), so this is not missing; noted because it is easy to assume otherwise.

Breakpoint map

Component Library breakpoint used M3 window size class it stands in for Gap
<x-search> full-screen ↔ docked (max-width: 39.99rem) = < 640 px (resources/js/search.js:11) Compact < 600 dp (full-screen), medium/expanded ≥ 600 dp (docked) 40 px late — 600639 px gets the full-screen view where M3 wants docked
<x-datepicker> docked → modal dialog (max-width: 39.99rem) = < 640 px (resources/js/datepicker.js:23) Compact < 600 dp (M3: "Used at compact breakpoints as a full-screen dialog"; docked "Used at medium/expanded") 40 px late; also M3 wants full-screen at compact, the library shows a 360 px centred dialog
<x-timepicker> landscape layout (orientation: landscape) and (min-width: 37rem) = ≥ 592 px (timepicker.css:403) No width class — M3 keys this on device orientation and viewport height (RB §Time Pickers/Behaviour) 8 px under 600; the height-based dial shrink at max-height: 22.75rem / 21.625rem (timepicker.css:449,455) is Compose's ClockFaceSizeModifier, not an M3 window class — acceptable
<x-radio inline> sm: = ≥ 640 px (radio.blade.php:39) Compact < 600 dp stacks; medium+ may lay out horizontally (M3 cautions against horizontal at any width) 40 px late, and the direction of the rule is a caution either way (IN-28)
Pagination numbers vs "Page 2 of 7" max-sm: / sm: = 640 px (pagination/livewire/tailwind.blade.php:31-32,48,53) Compact < 600 dp 40 px late — 600639 px loses the page numbers where M3's medium class would keep them
<x-table> density @media (pointer: fine) (table.css:37) None — M3 has no pointer-keyed density rule; density is an explicit user opt-in at any size Not a size-class substitution; the problem is that it is on by default (IN-16)
<x-field> / <x-form> width none Medium/expanded should bound a text field's width (RB §Text Fields/Behaviour) Not implemented at all — see Missing
<x-chip-set scroll> none (a prop, not a breakpoint) M3 expects wrap-or-scroll chosen by available width Caller decides; no adaptive switch
<x-chip type="filter"> trailing icon none M3: a filter chip's trailing icon is for medium/expanded; at compact the whole chip must be the tap target Satisfied incidentally — the whole chip is always the target (chip.blade.php:200)
<x-search> docked max height min(40rem, 70dvh) (search.css:136) Docked container height min 240 dp, max 2/3 of screen height 70 dvh vs 66.7 % — negligible; the missing 240 dp minimum is the real gap (IN-20)

Note on the Tailwind scale generally: this group consistently uses sm (640 px) where M3's compact boundary is 600 dp. The fix is one shared media query — a --material-compact: (max-width: 37.49rem) custom media (or a xs-ish Tailwind screen at 600 px) referenced by search.js:11, datepicker.js:23, radio.blade.php:39 and the pagination views — rather than five separate edits.