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
55 KiB
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 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-64putsaria-controlsandaria-expandedon a bare<input type="search">with norole="combobox". ARIA 1.2 does not listaria-expandedas supported ontextbox, 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 asrole="listitem"(resources/views/components/list-item.blade.php:41) — an orphan role with norole="list"owner.grep -n 'aria-live' resources/views/components/search.blade.phpreturns 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, addrole="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 rovingaria-activedescendantover the results, or (b) keep the plain search field, droparia-expanded, and add a visually hidden<span aria-live="polite">thatsearch.jsfills with the result count whenever[data-search-results]mutates (theMutationObserverplumbing already exists inresources/js/chips.js:99-117as 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-392declares.field-box:hover { --field-edge: var(--md-sys-color-on-surface); }on.field-box, while the disabled value atfield.css:423-425is declared on.fieldand only inherits down. A direct declaration beats an inherited one regardless of selector specificity, so hovering a disabled outlined field paints a full-opacityon-surfaceoutline instead ofon-surface/12. The rule is also outside@media (hover: hover), unlike the filled variant's background atfield.css:458-462, so the state sticks after a tap on touch. (The filled background is correctly suppressed —field.css:514-519wins on specificity there; only--field-edgebreaks.) - 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-55the<label>wraps only<span data-checkbox>whenlabelandhintare both blank, and[data-checkbox]is1.125remsquare (resources/css/components/selection.css:105-108). The 40 px state layer is a::beforewithpointer-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>documentsaria-labelas 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 putmin-block-size:3remon 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-250maps'md' => 'h-11 group-data-focused/thumb:h-9.5'— 44 px, the same as XS and S. The header comment atslider.blade.php:44states the intent as "handles of 44, 44, 44, 68 and 108px", citing MDC-Android'smd.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, labelInverse on surface, fontLabel large(SliderTokens.ValueIndicatorContainerColor / ValueIndicatorLabelTextColor / ValueIndicatorLabelTextFont). - Library does:
resources/views/components/slider.blade.php:346—h-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 atslider.blade.php:39-41explains the 32 px as Flutter'sRoundedRectSliderValueIndicatorShape, i.e. a non-Expressive source. - Fix:
slider.blade.php:346→h-11 min-w-12; then re-derive the$labelBottomoffsets atslider.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 givesValueIndicatorActiveBottomSpace = 12dpinSliderTokens.ktwhere 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 isOn primary; a stop/icon on the inactive track isOn secondary container. - Library does:
resources/views/components/slider.blade.php:227-238inverts both —$tickInk['primary'] = 'bg-primary data-active:bg-secondary-container'(so an inactive-track tick isprimaryand an active-track tick issecondary-container), and$iconInk['primary'] = 'text-primary data-active:text-secondary-container'. The end stop indicators atslider.blade.php:311use$activeInk(bg-primary) even though they sit on the inactive track. Contested reading:SliderTokens.kthasStopIndicatorColor = SecondaryContainerandStopIndicatorColorSelected = 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 paralleltext-on-…for$iconInk), and give$stopsatslider.blade.php:311bg-on-secondary-container. Repeat for thesecondary/tertiary/error/success/warning/inforows. - 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'sSearchBardoes draw aHorizontalDividerbetween 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 theborder-top(keeppadding-block: 0.5rem). If a separation cue is wanted, useoutline-variant, not the darkeroutlinerole. - 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:140sets[data-search-view] { background-color: var(--md-sys-color-surface-container-high); }for both presentations;search.css:174-181changes 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); }toresources/css/components/search.cssaround 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-37raises the root toz-index: 50when open (search.css:24-26) and closes onpointerdown.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 insearch.cssasposition: 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.kt→TimeSelector24HVerticalContainerWidth = 114.0.dp. - Library does:
resources/css/components/timepicker.css:106-107sets[data-timepicker-box] { width: 6rem; height: 5rem; }unconditionally. The dial already carries the cycle asdata-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:214→x-bind:data-cycle="is24 ? '24' : '12'") and add[data-timepicker-display][data-cycle="24"] [data-timepicker-box] { width: 7.125rem; }intimepicker.css, scoped out of the landscape block attimepicker.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.ktgivesPeriodSelectorSelectedContainerColor = TertiaryContainer,PeriodSelectorSelectedLabelTextColor = OnTertiaryContainer,PeriodSelectorOutlineColor = Outline,PeriodSelectorOutlineWidth = 1.0.dp,PeriodSelectorContainerShape = CornerSmall. The time selector (hour/minute), separately, isPrimaryContainer/OnPrimaryContainer. - Library does:
resources/css/components/timepicker.css:160-169gives the selected period buttonbackground-color: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container)— identical to the selected hour/minute box attimepicker.css:120-124, so the two different M3 emphases collapse into one. It also drops the 1 dpOutlinestroke 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 currentisUpdatedTimepickerToggleEnableddefault 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-containerattimepicker.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.kt→IconSize = 18.0.dp(=ContainerSize, i.e. the icon fills the box). - Library does:
resources/views/components/checkbox.blade.php:41-42renders both<x-icon name="check" class="size-4" />andname="remove" class="size-4"— 16 px inside an 18 px box (selection.css:105-108).resources/css/components/selection.css:1-3citesCheckboxTokensas the source, so the 18 dp figure was rounded to Tailwind's nearest step. - Fix:
checkbox.blade.php:41-42→class="size-4.5"(Tailwind 4's 4.5 = 18 px; the chip already usessize-4.5for 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-54rendersrole="group"with every chip its own natural tab stop;resources/js/chips.jsimplements 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/endhandler to the[data-chip-set]div inchip-set.blade.php(both thescrolland wrap branches) that moves focus across[data-chip]using the existingCONTROLSselector fromchips.js:13, with a rovingtabindexso 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:250gives the remove buttonafter:-inset-x-2 after:-inset-y-3.75on 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:250→after:-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, withgap-1between them (line 35); same atresources/views/pagination/laravel/tailwind.blade.php:7-9. (The twosimple-tailwindviews 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
$stepto'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-45tightens--cell-yfrom0.75remto0.5remunder@media (pointer: fine)with no opt-out, and to0.25rematdata-size="xs". Withbody-md's 20 px line box that is a 36 px row (28 px atxs). 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 adensityprop onresources/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-30rendersclass="group/sort focus-ring inline-flex cursor-pointer items-center gap-1 rounded-corner-xs"with no padding of its own. Its box is thetitle-smline 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(ormin-h-12with the<th>padding removed for sorting cells) so the button covers its cell. - Effort: S
- Breaks API? no (the
classprop 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-45sets onlydata-invalid, andresources/css/components/field.css:403-415,446-448changes only colours — outline, label and support text all move toerror. No icon is added.<x-input>'sicon-rightslot is free, so the hook exists. - Fix: in
field.blade.php, when$messages !== []and no$trailingis set, render<x-icon name="error" class="field-trailing size-(--field-icon)" aria-label="{{ __('Error') }}" />into the trailing position; suppress it atdata-size="xs"where there is no room. - Effort: S
- Breaks API? no (additive; callers passing
icon-rightkeep 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 frominput.blade.php:38,textarea.blade.php:27anddatepicker.blade.php:161. The comment atfield.css:417-418explains the intent (a date picker makes its own input read-only). The nativereadonlyattribute 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-189binds typing), so onlymode="modal"/"input"and caller-setreadonlyfields 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-41sizes[data-search-bar]only by height; width is the parent's.[data-search-view](search.css:130-147) isinset-inline: 0withmax-height: min(40rem, 70dvh)and nomin-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→ addmax-width: 45rem;(720 px) to[data-search-bar]and[data-search-view], andmin-height: 15rem(240 px) to the docked view. The focus-widen motion would need a--search-widthcustom 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-35gives the barpadding-inline: 0.25remandresources/css/components/search.css:53-61gives the leading/clear boxeswidth: 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:28→padding-inline: 0.75remwhen:not([data-open])and0.25remwhen[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-600handles 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
gridKeymap inresources/js/datepicker.js:585-592that 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-145binds ArrowDown, ArrowUp, Enter, Escape and Tab only. - Fix: add
x-on:keydown.home.prevent/.end.preventatchoices.blade.php:142settingactive = 0/active = filtered.length - 1and callingreveal(). - 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
keydownhandler inresources/js/slider.jsthat, 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:158and thestate-layerutility (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-251and:333-345render two<button aria-pressed>in arole="group". A screen reader hears "AM, toggle button, pressed" rather than "AM, radio button, selected, 1 of 2"; both convey the state, andaria-pressedavoids needing arrow-key roving, so this is defensible. Noted because the M3 table is explicit. - Fix: if changed,
role="radiogroup"on the wrapper androle="radio" aria-checkedon 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/maxextension and only has to stay inside M3's rules. - Library does:
resources/css/components/timepicker.css:172-174—background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent)with the content at 38 % (correct). - Fix:
timepicker.css:173→12%. - 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,39offersinline→sm:flex sm:flex-wrap sm:gap-x-6, andvalue(the pre-selection) is optional (radio.blade.php:19). Neither is wrong — M3's is a caution — but the SKILL entry (SKILL.md:552) presentsinlinewithout the caveat. - Fix: add one sentence to
SKILL.md:552and to theradio.blade.phpheader noting M3 prefers a vertical stack, and that a group should normally ship a pre-selectedvalue. - 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-44binds botharia-label(flipping between "Show password" and "Hide password") andaria-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→ droparia-pressedand keep the flipping label (the M3-documented pattern), or keeparia-pressedwith 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-68—border-bottom: 1px solid color-mix(in srgb, var(--md-sys-color-outline-variant) 60%, transparent). (The header rule attable.css:58correctly uses fulloutline-variant.) Data tables have no M3 spec, so this is a foundations-level nit, not a spec breach. - Fix:
table.css:67→var(--md-sys-color-outline-variant), and let the heavier header rule read as heavier by keeping the header'stitle-smweight contrast, or usesurface-container-highestif 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-largeinput,body-smallfloated 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, hoveron-surface, focusprimary, filled containersurface-container-highest, filled resting indicatoron-surface-variant, filled hoveron-surface8 %, disabled outline 12 % / content 38 % / filled container 4 % — all matchOutlinedTextFieldTokens/FilledTextFieldTokens(field.css:33-44,390-437,450-520). - Error replaces the hint, never stacks (
field.blade.php:73-81), withrole="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 typing —
datepicker.js:350-366only 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 trackprimary, handleon-primary, hover/focus/pressed handleprimary-container, unselected tracksurface-container-highest, handleoutline, disabled 12 %/38 %) —selection.css:219-343. - Checkbox / radio boxes — 18 px box with 2 px
on-surface-variantoutline and 2 dp corner, 20 px radio with a 2 px ring, both in 40 px state layers,primarywhen selected, error variants (selection.css:103-217). Indeterminate uses a dash icon and is kept in sync through theindeterminateDOM 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
secondaryat +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::aftertarget strip, filter selectedsecondary-container/on-secondary-container, strokeoutline-variant(the Aug-2024 update), elevated onsurface-container-lowat 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-largeheadline (title-largefor range),surface-container-highat elevation 3,corner-xlmodal /corner-lgdocked, 40 px day in a 48 px cell,body-largeday and weekday labels, today outlined 1 pxprimary, selectedprimary/on-primary, range bandsecondary-container/on-secondary-container40 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-cellaria-labelnaming the full date,aria-selected,aria-current="date", rovingtabindex,aria-livemonth 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-largewith acorner-smallcontainer, 52 × 80 (vertical) / 216 × 38 (horizontal) period selector, 256 px dial insurface-container-highestwithbody-largenumbers, 48 px selector handle, 8 px centre, 2 px track, 96 × 72 input fields indisplay-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),
primaryactive track and handle,secondary-containerinactive track,inverse-surface/inverse-on-surfacevalue indicator inlabel-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 isrole="group"witharia-labelledbyand "Range start"/"Range end" handle labels; the drawing isaria-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 semantics —role="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-largeinput, 48 px leading/clear targets, docked viewcorner-xlat elevation 3, 72 px full-screen header with safe-area padding,corner-nonefull-screen — all matchingSearchBarTokens/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, witharia-current="page"(pagination/livewire/tailwind.blade.php:55). This is the right reading of RF §Interaction/Selection ("active indicator" components). - Table roles — headers
title-smallon-surface-variantover anoutline-variantrule, selected rowsecondary-container/on-secondary-containerkeyed onaria-selected="true",aria-sorton 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.phphas 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;maxlengthpasses 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 inSKILL.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 — 600–639 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 — 600–639 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.