Cut duplicated and speculative code across the package

An over-engineering audit of the whole tree, applied in five reviewed
batches. Behaviour stays the same except where UPGRADE.md says otherwise.

PHP: the showcase and error-page stylesheets are prebuilt into
resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import
(first occurrence kept, the order an application's build gives), instead
of Stylesheets::bundle() inlining imports on every request; only the
import walk DesignGuard needs stays. SchemeStylesheet::withProfiles()
replaces three copies of the scheme-plus-profiles loop, material:scheme
leaves spec and contrast checks to the node script that already made
them, and the error page's scheme cache, the hashed view namespace, the
translations path with no lang/ folder and DesignGuard's 1.x-name hints
are gone.

JS: the androidx shape port progress.js and both bin scripts each carried
lives once in resources/js/shapes.js (the generated SVGs are unchanged);
util.js holds ringIndex(), ms(), reopenGuard() and remember(), which
were written out several times; listeners are released through
AbortController; tooltip.js's hoverPopover() serves the rich tooltip too.

CSS: every rule for an element inside the navigation rail queries
`--md-navigation-rail-value` instead of repeating the seven collapsed
conditions under five media branches; badge, alert, progress, slider and
button read one non-inheriting colour-role table (components/color.css);
the dialog chrome, the submenu's popover chrome, the chip's state layer
and touch target, and the visually-hidden inputs use the shared rules
they copied; foundation/tokens.css is folded into foundation.css.

Views: Support\Field and Support\Link replace the error-key, bound-value
and link-attribute blocks copied into the fields and link components;
the timepicker period group, the menu filter and the showcase head are
partials; the datepicker's steppers and entry fields are loops; component
docblocks no longer restate SKILL.md.

Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces
four per-group files, DesignGuardTest and the layout-component tests use
datasets, browser tests share one ready() helper, CSS parsing lives in
ComponentStylesheet alone. docs/audits and the finding IDs citing it are
removed, as are pestphp/pest-plugin-laravel, the unused composer scripts
and check:font; the lint job runs in the feature job, which now installs
node packages so the prebuilt-stylesheet staleness test runs in CI.

Feature suite 1177 passed, Chrome browser suite 299 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-17 19:29:21 +02:00
co-authored by Claude Opus 5
parent 471d927e64
commit 247c596c3a
233 changed files with 16635 additions and 10579 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
* Livewire Material for an application that wants everything: the foundation, every layout
* stylesheet and every component stylesheet, each imported once. The blocks below are the same
* groups `components.css` and `layout.css` used before this file replaced both of them (plan step
* 37) — one block per audit stream, so a stylesheet still lands in a predictable place — plus the
* 37) — one block per group, so a stylesheet still lands in a predictable place — plus the
* Layout block those two files didn't share.
*
* This is not the recommended way to bring the package's CSS in: importing `all.css` pulls in
@@ -38,6 +38,7 @@
/* Foundation components */
@import './components/icon.css';
@import './components/shape.css';
@import './components/color.css';
/* Actions and communication */
@import './components/loading.css';
+3 -3
View File
@@ -1,9 +1,9 @@
/*
* <x-account-menu>: the avatar trigger of an `<x-menu>`, its name-and-email header and its theme
* item — an M3 "extra" the audit found already inside the foundations (docs/audits/m3-alignment/
* navigation.md): a 40px trigger reaching M3's 48px target through `md-touch-target` (N-01), with
* item — not one of M3's own catalogued components, so its states are judged against the general
* foundations instead: a 40px trigger reaching M3's 48px target through `md-touch-target`, with
* `md-state-layer` for the hover and pressed feedback every other trigger in the library already
* has (N-15) and `md-focus-ring` for its keyboard ring. The avatar image sits behind the state
* has and `md-focus-ring` for its keyboard ring. The avatar image sits behind the state
* layer's `::before` (z-index -1, foundation/interaction.css) at z-index -2, so a hover or a press
* still washes over the picture instead of under it; nothing clips the pseudo-target the touch
* class draws past the 40px circle.
+4 -33
View File
@@ -9,12 +9,13 @@
* neutral (surface-container-high, on-surface — it has no state colour of its own). The dismiss
* button is drawn at 40px, pulled 8px out into the alert's padding, and renders the foundation's
* `md-state-layer`, `md-focus-ring` and `md-touch-target` (foundation/interaction.css) to reach
* M3's 48px target (ACT-18); nothing here copies their rules.
* M3's 48px target; nothing here copies their rules.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './icon.css';
@import './color.css';
@layer material.components {
[data-md-alert] {
@@ -23,38 +24,8 @@
gap: 12px;
border-radius: var(--md-sys-shape-corner-md);
padding: var(--md-sys-measurement-space200);
background-color: var(--md-sys-color-info-container);
color: var(--md-sys-color-on-info-container);
}
[data-md-alert][data-md-color='success'] {
background-color: var(--md-sys-color-success-container);
color: var(--md-sys-color-on-success-container);
}
[data-md-alert][data-md-color='warning'] {
background-color: var(--md-sys-color-warning-container);
color: var(--md-sys-color-on-warning-container);
}
[data-md-alert][data-md-color='error'] {
background-color: var(--md-sys-color-error-container);
color: var(--md-sys-color-on-error-container);
}
[data-md-alert][data-md-color='primary'] {
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-on-primary-container);
}
[data-md-alert][data-md-color='secondary'] {
background-color: var(--md-sys-color-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
[data-md-alert][data-md-color='tertiary'] {
background-color: var(--md-sys-color-tertiary-container);
color: var(--md-sys-color-on-tertiary-container);
background-color: var(--md-container, var(--md-sys-color-info-container));
color: var(--md-on-container, var(--md-sys-color-on-info-container));
}
[data-md-alert][data-md-color='neutral'] {
+5 -5
View File
@@ -15,11 +15,11 @@
* small 64px, title-large title (label-medium subtitle), 4px from the edges, the title 16px
* from the start or straight after the navigation icon
* center the same, the title centred — a three-column grid, so it shrinks against whatever is
* beside it rather than ellipsising underneath it (N-11: a fixed inset fit exactly one
* of M3's two trailing buttons)
* beside it rather than ellipsising underneath it: a fixed inset fit exactly one
* of M3's two trailing buttons
* medium 112px (136px with a subtitle): the 64px row of icons over a headline-medium title
* large 120px (152px with a subtitle): a display-small title
* search the row holds a search bar, full width to 312px and then half of what is left (N-09)
* search the row holds a search bar, full width to 312px and then half of what is left
*
* The container is the surface, and surface-container once content scrolls under it. A medium or
* large bar collapses into the small one without script moving anything: the bar is sticky at a
@@ -169,7 +169,7 @@
/* Centred: a three-column row — leading, headline, trailing — so the headline is centred in the
window and shrinks against whatever is actually beside it. M3 allows two trailing icon
buttons, which a fixed inset for one would have let a long title ellipsise underneath (N-11). */
buttons, which a fixed inset for one would have let a long title ellipsise underneath. */
[data-md-app-bar][data-md-variant='center'] [data-md-app-bar-row] {
display: grid;
grid-template-columns: 1fr auto 1fr;
@@ -196,7 +196,7 @@
}
/* The search bar fills the space between the leading and trailing elements until it is 312dp
wide, and then grows into only half of what is left (N-09). The percentage is of the row
wide, and then grows into only half of what is left. The percentage is of the row
rather than of that space, which CSS cannot name without measuring it; below 312px the cap
never binds, so a phone still gets the whole row. */
[data-md-app-bar-search] {
+6 -46
View File
@@ -20,12 +20,14 @@
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './color.css';
@layer material.components {
[data-md-badge] {
--md-badge-color: var(--md-sys-color-error);
--md-badge-on-color: var(--md-sys-color-on-error);
--md-badge-container: var(--md-sys-color-error-container);
--md-badge-on-container: var(--md-sys-color-on-error-container);
--md-badge-color: var(--md-color, var(--md-sys-color-error));
--md-badge-on-color: var(--md-on-color, var(--md-sys-color-on-error));
--md-badge-container: var(--md-container, var(--md-sys-color-error-container));
--md-badge-on-container: var(--md-on-container, var(--md-sys-color-on-error-container));
display: inline-flex;
flex-shrink: 0;
@@ -65,48 +67,6 @@
border: 1px solid;
}
[data-md-badge][data-md-color='primary'] {
--md-badge-color: var(--md-sys-color-primary);
--md-badge-on-color: var(--md-sys-color-on-primary);
--md-badge-container: var(--md-sys-color-primary-container);
--md-badge-on-container: var(--md-sys-color-on-primary-container);
}
[data-md-badge][data-md-color='secondary'] {
--md-badge-color: var(--md-sys-color-secondary);
--md-badge-on-color: var(--md-sys-color-on-secondary);
--md-badge-container: var(--md-sys-color-secondary-container);
--md-badge-on-container: var(--md-sys-color-on-secondary-container);
}
[data-md-badge][data-md-color='tertiary'] {
--md-badge-color: var(--md-sys-color-tertiary);
--md-badge-on-color: var(--md-sys-color-on-tertiary);
--md-badge-container: var(--md-sys-color-tertiary-container);
--md-badge-on-container: var(--md-sys-color-on-tertiary-container);
}
[data-md-badge][data-md-color='success'] {
--md-badge-color: var(--md-sys-color-success);
--md-badge-on-color: var(--md-sys-color-on-success);
--md-badge-container: var(--md-sys-color-success-container);
--md-badge-on-container: var(--md-sys-color-on-success-container);
}
[data-md-badge][data-md-color='warning'] {
--md-badge-color: var(--md-sys-color-warning);
--md-badge-on-color: var(--md-sys-color-on-warning);
--md-badge-container: var(--md-sys-color-warning-container);
--md-badge-on-container: var(--md-sys-color-on-warning-container);
}
[data-md-badge][data-md-color='info'] {
--md-badge-color: var(--md-sys-color-info);
--md-badge-on-color: var(--md-sys-color-on-info);
--md-badge-container: var(--md-sys-color-info-container);
--md-badge-on-container: var(--md-sys-color-on-info-container);
}
[data-md-badge][data-md-color='neutral'] {
--md-badge-color: var(--md-sys-color-on-surface-variant);
--md-badge-on-color: var(--md-sys-color-surface);
+2 -2
View File
@@ -8,13 +8,13 @@
* [data-md-bottom-sheet-probe] hidden, measures a stop's px height for a drag to settle on
* [data-md-bottom-sheet-panel] surface-container-low, extra-large top corners, elevation 1;
* data-md-preset while `heights`/`snap` gives it stops
* [data-md-bottom-sheet-handle] the drag target and M3's 48dp hit area (C-01)
* [data-md-bottom-sheet-handle] the drag target and M3's 48dp hit area
* [data-md-bottom-sheet-grip] the 32×4px bar, the shared classes' touch target
* [data-md-bottom-sheet-announce] a live region, only while stops exist
* [data-md-bottom-sheet-body], [data-md-bottom-sheet-title], [data-md-bottom-sheet-actions]
*
* 640px max width, centred; 28px top corners only (`DockedContainerShape` = CornerExtraLargeTop);
* 50dvh default open height under a ceiling of the screen less M3's 72dp top margin (C-19). It
* 50dvh default open height under a ceiling of the screen less M3's 72dp top margin. It
* rises on emphasized decelerate and only falls on emphasized accelerate — the asymmetric timing a
* more specific selector for the open state supplies, since a CSS transition always takes its
* duration and easing from the *after-change* style (the technique resources/css/components/
+1 -4
View File
@@ -143,10 +143,7 @@
[data-md-button-group='connected'] > * {
--md-button-group-corner: var(--md-button-group-inner);
border-start-start-radius: var(--md-button-group-corner);
border-end-start-radius: var(--md-button-group-corner);
border-start-end-radius: var(--md-button-group-corner);
border-end-end-radius: var(--md-button-group-corner);
border-radius: var(--md-button-group-corner);
}
[data-md-button-group='connected'] > :active {
+11 -45
View File
@@ -11,7 +11,7 @@
* lg 96px 48px 12px headline-sm 32 / 32px 2px xl lg
* xl 136px 64px 16px headline-lg 40 / 40px 3px xl lg
*
* (ButtonXSmall…XLargeTokens; the xs padding is the 16px Phase D settled on, ACT-09.) An icon button
* (ButtonXSmall…XLargeTokens; the xs padding is the 16px Phase D settled on.) An icon button
* (`data-md-icon-button`) is as tall and `narrow`, `default` or `wide` across by
* XSmall…XLargeIconButtonTokens: 28/32/40, 32/40/52, 48/56/72, 64/96/128 and 104/136/184px. The
* views size the glyphs through `<x-icon size>`; `--md-button-icon` carries the same size for the
@@ -30,7 +30,7 @@
* plain declaration. Primary's tonal button is secondary-container and its standard icon button
* on-surface-variant, as M3 draws them; the text button's label is primary, as Compose draws it,
* not the token's on-surface-variant, which its own source marks as wrong; a selected text button
* takes the tonal container (the library's deliberate deviations, docs/audits/m3-alignment).
* takes the tonal container (a deliberate deviation from the token).
*
* Motion: corners, padding, margins and `bottom` on the fast spatial spring, colour and shadow on
* the fast effects spring beside it — a colour must never overshoot (tokens/motion.css).
@@ -54,13 +54,14 @@
@import './icon.css';
@import './loading.css';
@import './tooltip.css';
@import './color.css';
@layer material.components {
[data-md-button] {
--md-button-color: var(--md-sys-color-primary);
--md-button-on-color: var(--md-sys-color-on-primary);
--md-button-tone: var(--md-sys-color-secondary-container);
--md-button-on-tone: var(--md-sys-color-on-secondary-container);
--md-button-color: var(--md-color, var(--md-sys-color-primary));
--md-button-on-color: var(--md-on-color, var(--md-sys-color-on-primary));
--md-button-tone: var(--md-container, var(--md-sys-color-secondary-container));
--md-button-on-tone: var(--md-on-container, var(--md-sys-color-on-secondary-container));
--md-button-tone-selected: var(--md-sys-color-secondary);
--md-button-on-tone-selected: var(--md-sys-color-on-secondary);
--md-button-quiet: var(--md-sys-color-on-surface-variant);
@@ -223,49 +224,14 @@
border-radius: var(--md-button-pressed);
}
/* Colour roles. */
[data-md-button][data-md-color='secondary'] {
--md-button-color: var(--md-sys-color-secondary);
--md-button-on-color: var(--md-sys-color-on-secondary);
/* Colour roles: every hue but primary comes straight off the shared table (color.css). Its
`primary` entry is plain primary-container, so the one hue this file draws differently keeps
a small override of its own. */
[data-md-button][data-md-color='primary'] {
--md-button-tone: var(--md-sys-color-secondary-container);
--md-button-on-tone: var(--md-sys-color-on-secondary-container);
}
[data-md-button][data-md-color='tertiary'] {
--md-button-color: var(--md-sys-color-tertiary);
--md-button-on-color: var(--md-sys-color-on-tertiary);
--md-button-tone: var(--md-sys-color-tertiary-container);
--md-button-on-tone: var(--md-sys-color-on-tertiary-container);
}
[data-md-button][data-md-color='error'] {
--md-button-color: var(--md-sys-color-error);
--md-button-on-color: var(--md-sys-color-on-error);
--md-button-tone: var(--md-sys-color-error-container);
--md-button-on-tone: var(--md-sys-color-on-error-container);
}
[data-md-button][data-md-color='success'] {
--md-button-color: var(--md-sys-color-success);
--md-button-on-color: var(--md-sys-color-on-success);
--md-button-tone: var(--md-sys-color-success-container);
--md-button-on-tone: var(--md-sys-color-on-success-container);
}
[data-md-button][data-md-color='warning'] {
--md-button-color: var(--md-sys-color-warning);
--md-button-on-color: var(--md-sys-color-on-warning);
--md-button-tone: var(--md-sys-color-warning-container);
--md-button-on-tone: var(--md-sys-color-on-warning-container);
}
[data-md-button][data-md-color='info'] {
--md-button-color: var(--md-sys-color-info);
--md-button-on-color: var(--md-sys-color-on-info);
--md-button-tone: var(--md-sys-color-info-container);
--md-button-on-tone: var(--md-sys-color-on-info-container);
}
/* Every colour but primary selects into the colour itself and is quiet in it. */
[data-md-button]:not([data-md-color='primary']) {
--md-button-tone-selected: var(--md-button-color);
+1 -2
View File
@@ -3,8 +3,7 @@
* OutlinedCardTokens.kt, androidx Compose Material 3, Apache-2.0; docs/reference/m3
* § Cards → Specs) — filled (surface-container-highest), elevated (surface-container-low at
* elevation 1) and outlined (surface, an outline-variant edge); one 12dp corner for all three,
* which never morphs (M3 specifies shape morph for buttons, FABs and list items, never cards
* docs/audits/m3-alignment/containment.md, C-23).
* which never morphs (M3 specifies shape morph for buttons, FABs and list items, never cards).
*
* Every card renders the shared `md-state-layer` class, and a row (`data-md-list-row`,
* `resources/js/list-rows.js`) `md-focus-ring` too (foundation/interaction.css), for its tint and
+3 -4
View File
@@ -1,10 +1,9 @@
/*
* <x-carousel-item>: one slide of an <x-carousel>, masked and shifted by the keyline maths
* carousel.css's header describes, unless the row is full-screen or multi-aspect, where nothing
* masks it at all (docs/reference/m3/components-actions-communication-containment.md § Carousel;
* docs/audits/m3-alignment/containment.md, C-11, C-12, C-25).
* masks it at all (docs/reference/m3/components-actions-communication-containment.md § Carousel).
*
* [data-md-carousel-item] the tab stop (C-18), a focusable `group`, `md-focus-ring`
* [data-md-carousel-item] the tab stop, a focusable `group`, `md-focus-ring`
* [data-md-carousel-surface] the masked, shifted, extra-large-corner (28px) box
* [data-md-carousel-content] the slot (an <img>, cropped)
* [data-md-carousel-label] the `label` prop's words, over a scrim gradient
@@ -26,7 +25,7 @@
* The label's ink is a literal `white`, not a role: it sits over `--md-sys-color-scrim`, which is
* black in every scheme, theme and contrast level, so white is the one ink that always has the
* scrim's contrast — a role (`inverse-on-surface`) is dark ink in a dark scheme and fails there
* (C-25, deliberately kept, as the component's original header explained).
* (deliberately kept).
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
+4 -8
View File
@@ -7,7 +7,7 @@
* and sizes as inline custom properties every scroll frame; this file draws everything the script
* does not — the row's layout per layout, and the controls — and carousel-item.css draws the
* item and its mask (docs/reference/m3/components-actions-communication-containment.md
* § Carousel; docs/audits/m3-alignment/containment.md).
* § Carousel).
*
* [data-md-carousel] the row wrapper; its value is the layout
* [data-md-carousel-probe] hidden, measures the preferred item width for the script
@@ -17,12 +17,12 @@
* [data-md-carousel-previous], [data-md-carousel-next]
*
* Specs table: 16dp leading/trailing padding, 8dp top/bottom and between items, for every layout
* but uncontained (leading only) and full-screen (0, edge to edge — C-12); a `>` combinator
* but uncontained (leading only) and full-screen (0, edge to edge); a `>` combinator
* throughout, never a bare descendant one, because a carousel item can itself hold a nested
* carousel (a `multi-aspect` gallery inside a `hero` slide) whose own root would otherwise match
* its parent's rules too. Full-screen scrolls vertically, one edge-to-edge item at a time, capped
* at the 840px medium window it is meant for (C-11); reduced motion needs no rule here at all —
* the script writes zero inset, shift and full opacity itself for every layout (C-05).
* at the 840px medium window it is meant for; reduced motion needs no rule here at all —
* the script writes zero inset, shift and full opacity itself for every layout.
*
* The previous/next icons mirror in RTL by scaling the whole button, as the row's arrow keys and
* scroll direction already do (`resources/js/carousel.js`'s own `state.rtl`), rather than through
@@ -53,10 +53,6 @@
[data-md-carousel] > [data-md-carousel-scroller] {
display: flex;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
[data-md-carousel='full-screen'] > [data-md-carousel-scroller] {
+8 -35
View File
@@ -76,55 +76,28 @@
/* ---- Assist, filter and suggestion: the chip is the control ------------------------------ */
/* The state layer and the 48px target are the foundation's `md-state-layer md-touch-target`
classes now (chip.blade.php, as group and scheme-picker's own controls render them;
foundation/interaction.css), at the same opacities and off the same `currentColor` this
chip always painted them from. Three extras stay chip's own: a filter chip's label is never
itself `:focus-visible` — the checkbox inside it is, caught with `:has()`, the same way the
ring below already had to; `data-md-disabled` marks a chip with no native `disabled` to
catch (a link, or a toggle button with no press of its own). */
[data-md-chip]:not([data-md-chip='input']) {
isolation: isolate;
padding-inline: 15px;
outline: none;
cursor: pointer;
/* The 48px target, past the chip's 32px. */
&::after {
content: '';
position: absolute;
inset-inline: 0;
top: 50%;
height: var(--md-sys-measurement-space600);
translate: 0 -50%;
}
&::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
background-color: currentColor;
opacity: 0;
pointer-events: none;
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
}
@media (hover: hover) {
&:hover::before {
opacity: var(--md-sys-state-hover-state-layer-opacity);
}
}
&:focus-visible::before,
&:has(:focus-visible)::before {
opacity: var(--md-sys-state-focus-state-layer-opacity);
}
&:active::before {
opacity: var(--md-sys-state-pressed-state-layer-opacity);
}
&:is(:focus-visible, :has(:focus-visible)) {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
&:is(:disabled, [aria-disabled='true'], [data-md-disabled])::before {
&[data-md-disabled]::before {
display: none;
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
* <x-collapse>: not an M3 component — M3 has expandable list items and menu expansion but no
* standalone disclosure — built on the native `<details>` so it opens without script, keeps its
* state through a Livewire morph, and is announced as a disclosure by the browser. Judged against
* foundations rather than any M3 component spec (docs/audits/m3-alignment/containment.md, C-24).
* foundations rather than any M3 component spec, since M3 has no standalone disclosure.
*
* The height eases open and shut on the fast spatial spring, the same one that turns the chevron,
* from resources/js/collapse.js: it animates the `<details>`' own `block-size`. This file cannot —
+94
View File
@@ -0,0 +1,94 @@
/*
* The colour-role table M3's semantic colours share, so a component that offers the same set of
* roles does not repeat it: `data-md-color` picks primary (most components' default, drawn
* without the attribute), secondary, tertiary, error, success, warning or info, and this sets the
* four generic variables a component's own stylesheet reads — `--md-color`/`--md-on-color` the
* role itself, `--md-container`/`--md-on-container` its tonal container.
*
* A component gives its own prefixed variables (`--md-badge-color`, …) a fallback of its default
* role, `var(--md-color, var(--md-sys-color-<default>))`, so the generic ones only take over once
* `data-md-color` is actually written on that element. The four are registered as not inheriting,
* so a component inside a coloured one (a badge on an error button) still falls back to its own
* default rather than taking its ancestor's role. A component whose primary
* deliberately reads secondary-container instead of primary-container (button.css, slider.css,
* progress.css) keeps a small override of its own for an explicit `data-md-color="primary"` too:
* this file's `primary` entry is the plain M3 mapping every other role, and every other component,
* wants.
*
* A component that supports only part of this set (fab.css's primary/secondary/tertiary) does not
* import this file: wiring it in would make every role it leaves out suddenly take effect through
* the bare `[data-md-color]` selector below, a behaviour change rather than a rewrite.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@layer material.components {
@property --md-color {
syntax: '*';
inherits: false;
}
@property --md-on-color {
syntax: '*';
inherits: false;
}
@property --md-container {
syntax: '*';
inherits: false;
}
@property --md-on-container {
syntax: '*';
inherits: false;
}
[data-md-color='primary'] {
--md-color: var(--md-sys-color-primary);
--md-on-color: var(--md-sys-color-on-primary);
--md-container: var(--md-sys-color-primary-container);
--md-on-container: var(--md-sys-color-on-primary-container);
}
[data-md-color='secondary'] {
--md-color: var(--md-sys-color-secondary);
--md-on-color: var(--md-sys-color-on-secondary);
--md-container: var(--md-sys-color-secondary-container);
--md-on-container: var(--md-sys-color-on-secondary-container);
}
[data-md-color='tertiary'] {
--md-color: var(--md-sys-color-tertiary);
--md-on-color: var(--md-sys-color-on-tertiary);
--md-container: var(--md-sys-color-tertiary-container);
--md-on-container: var(--md-sys-color-on-tertiary-container);
}
[data-md-color='error'] {
--md-color: var(--md-sys-color-error);
--md-on-color: var(--md-sys-color-on-error);
--md-container: var(--md-sys-color-error-container);
--md-on-container: var(--md-sys-color-on-error-container);
}
[data-md-color='success'] {
--md-color: var(--md-sys-color-success);
--md-on-color: var(--md-sys-color-on-success);
--md-container: var(--md-sys-color-success-container);
--md-on-container: var(--md-sys-color-on-success-container);
}
[data-md-color='warning'] {
--md-color: var(--md-sys-color-warning);
--md-on-color: var(--md-sys-color-on-warning);
--md-container: var(--md-sys-color-warning-container);
--md-on-container: var(--md-sys-color-on-warning-container);
}
[data-md-color='info'] {
--md-color: var(--md-sys-color-info);
--md-on-color: var(--md-sys-color-on-info);
--md-container: var(--md-sys-color-info-container);
--md-on-container: var(--md-sys-color-on-info-container);
}
}
+2 -16
View File
@@ -51,6 +51,7 @@
@import './field.css';
@import './icon.css';
@import './button.css';
@import './modal.css';
@layer material.components {
/* ---- The support line under the field ------------------------------------------------------ */
@@ -112,25 +113,10 @@
}
}
/* The entry pop and the scrim are modal.css's shared dialog chrome, imported above. */
[data-md-datepicker-picker]:modal {
inset: 0;
margin: auto;
opacity: 1;
scale: 1;
transition-property: opacity, scale;
transition-duration: var(--md-sys-motion-spatial-fast-duration);
transition-timing-function: var(--md-sys-motion-spatial-fast);
}
@starting-style {
[data-md-datepicker-picker]:modal {
opacity: 0;
scale: 0.95;
}
}
[data-md-datepicker-picker]:modal::backdrop {
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
}
[data-md-datepicker-surface] {
+1 -1
View File
@@ -13,7 +13,7 @@
* [data-md-drawer-subtitle], [data-md-drawer-close]
* [data-md-drawer-body] a size container, so its contents lay out by the sheet's own
* width (`@md:`), never the viewport's
* [data-md-drawer-actions] left-aligned, 72dp (C-10 — a dialog's are trailing-aligned)
* [data-md-drawer-actions] left-aligned, 72dp — a dialog's are trailing-aligned
*
* 400px cap, 24dp start/end padding, a large corner on the inner edge only — from the specs
* table. It enters on the emphasized-decelerate easing rather than a spring, because a sheet
+7 -7
View File
@@ -29,13 +29,13 @@
* as the old `font-sans` was; only the fallback, which has no `@font-face`, uses a system stack.
*
* Colours from the roles (`docs/reference/m3/styles.md` § Typography, "Accessibility
* requirements": surface/on-surface); the display code over its primary-container shape is the
* pairing `docs/audits/m3-alignment/containment.md`'s "Aligned" section confirms; 24px page
* gutters and the 600px growth of the art and headline are the same numbers the audit found
* clean. `--md-sys-typescale-emphasized-display-lg` is the code's weight and roundedness; the
* shape's 60s linear turn has no motion token (`--md-sys-motion-*` pairs a duration with a spring
* for a state change, not an ambient loop) and stays a literal, gated the way the audit's
* "Aligned" section already found it: behind `prefers-reduced-motion: no-preference`.
* requirements": surface/on-surface); the display code over its primary-container shape is a
* correct on-primary-container/primary-container contrast pairing; 24px page gutters and the
* 600px growth of the art and headline follow the same spacing rules as every other M3 surface in
* the library. `--md-sys-typescale-emphasized-display-lg` is the code's weight and roundedness;
* the shape's 60s linear turn has no motion token (`--md-sys-motion-*` pairs a duration with a
* spring for a state change, not an ambient loop) and stays a literal, gated behind
* `prefers-reduced-motion: no-preference` like every other ambient animation in the library.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
+1 -1
View File
@@ -10,7 +10,7 @@
*
* The entry is `@starting-style` on the item itself; the exit is the popover's exit copy
* (resources/js/popover-exit.js), whose items fab-menu.css sets to the closed values, so the item
* has somewhere to animate to (ACT-33).
* has somewhere to animate to.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
+3 -3
View File
@@ -4,17 +4,17 @@
* FabMenuBaselineTokens (androidx Compose Material 3, Apache-2.0): the trigger is a 56px FAB,
* `data-md-color`'s container, large corner, elevation 3, rising to elevation 4 under a hovering
* pointer; while its list is open (`aria-expanded`) it turns fully round and paints the colour
* itself, on the default spatial and effects springs (ACT-19 splits the two). It renders the
* itself, on the default spatial and effects springs (split between the two). It renders the
* foundation's `md-state-layer` and `md-focus-ring` (foundation/interaction.css); its 56px already
* meets M3's target, so it needs no `md-touch-target`. The list sits
* `data-md-position`'s corner of the trigger, 8px padding — the room the close button keeps below
* the first item — and scrolls past `calc(100dvh - 128px)` on a short window, behind the close
* button, which stays fixed (ACT-32).
* button, which stays fixed.
*
* The items (fab-menu-item.css) rise into place as the list opens and sink back as it closes: the
* entry is `@starting-style` on the item itself; the exit is the popover's exit copy
* (`data-md-popover-exit`, resources/js/popover-exit.js), which stays on screen after the popover
* has closed, so the items have somewhere to animate to (ACT-33) in every engine, Firefox included.
* has closed, so the items have somewhere to animate to in every engine, Firefox included.
* `[data-md-fab-menu-popover]:not(:popover-open) > *` and the copy's `[data-md-popover-closing] > *`
* are the closed values; inside the layer they beat fab-menu-item.css's resting `translate: 0 0`
* and `opacity: 1` on specificity — two attributes, or an attribute and a pseudo-class, against the
-5
View File
@@ -126,11 +126,6 @@
font-family: var(--md-ref-typeface-mono);
}
[data-md-field-control]::placeholder {
color: var(--md-sys-color-on-surface-variant);
opacity: 1;
}
/* Autofill paints its own background, which on a card is a pale block in the
wrong colour. Delaying the transition for a week keeps the field's own. */
[data-md-field-control]:-webkit-autofill {
+3 -12
View File
@@ -97,18 +97,9 @@
display: none;
}
/* The radio or checkbox stays in the page, for the form, the keyboard and a screen reader. */
& > input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
}
/* The radio or checkbox stays in the page, for the form, the keyboard and a screen reader
— `md-visually-hidden` on the element itself (text.css), which outranks this layer
whatever wins the specificity, `material.text` sitting above `material.components`. */
}
[data-md-group][data-md-inline] [data-md-group-segment] {
+10 -10
View File
@@ -4,7 +4,7 @@
* Material 3, Apache-2.0) — the tallest element sets the height, and a three-line item top-aligns
* rather than centring (docs/reference/m3/components-actions-communication-containment.md
* § Lists → Specs). Leading/trailing gap and container padding are both `space200` (16px,
* `ItemLeadingSpace`/`ItemTrailingSpace`/container padding, C-14).
* `ItemLeadingSpace`/`ItemTrailingSpace`/container padding).
*
* `data-md-list-row` (`resources/js/list-rows.js`, shared with `<x-card>` and an application's own
* table rows) and `[data-md-list-open]:focus-visible` answer with the state layer — the row's ink at
@@ -12,25 +12,25 @@
* `[data-md-card]`, which draws its own tint through the shared `md-state-layer` class instead
* (card.css). The layer is mixed over the row's own fill, `--md-list-row-fill` (a segmented tile's
* surface, a selected item's secondary-container, table.css's selected row), so a hovered, pressed
* or focused row keeps its fill under the tint instead of trading it for a translucent one (C-08's
* second fix). `data-md-selected` is M3's selected item, secondary-container filled, which also
* or focused row keeps its fill under the tint instead of trading it for a translucent one.
* `data-md-selected` is M3's selected item, secondary-container filled, which also
* lifts the description/overline/trailing/icon colour rules below since they exclude it — the
* container's own colour takes over by inheritance. The same attribute on any other row that is
* not a card — an application's own `<li>`, `<div>` or `<tr>` — draws the same fill and ink, as
* 1.x's `data-selected` did; a card keeps its own container. A selected option draws a second cue, a
* trailing check, so selection is never colour alone (C-03). `aria-disabled` inks the whole item
* trailing check, so selection is never colour alone. `aria-disabled` inks the whole item
* on-surface at the disabled-content opacity (38%) and blocks the pointer; the view drops the
* item's link entirely rather than leaving a focusable, activatable control behind it (C-02).
* item's link entirely rather than leaving a focusable, activatable control behind it.
*
* `data-md-list="segmented"` items (M3 Expressive) are `surface-container` tiles (one tone up from
* `ItemSegmentedContainerColor`, so they read against the package's own `surface` page;
* C-21), 4px corners that open to 16px at the list's own ends and while hovered (12px), pressed,
* focused or selected (16px) — the specs page's interaction-state expressive shapes (C-22). Leading
* `ItemSegmentedContainerColor`, so they read against the package's own `surface` page),
* 4px corners that open to 16px at the list's own ends and while hovered (12px), pressed,
* focused or selected (16px) — the specs page's interaction-state expressive shapes. Leading
* icons there are drawn at 20px, not scaled down from 24 — list-item.blade.php passes `size` to
* `<x-icon>` from the parent's `@aware(['segmented'])`, so no CSS override is needed here (C-26).
* `<x-icon>` from the parent's `@aware(['segmented'])`, so no CSS override is needed here.
*
* `data-md-dividers` on the list insets its rule 16px from both ends (`DividerLeadingSpace`/
* `DividerTrailingSpace`, C-16) rather than full-bleed.
* `DividerTrailingSpace`) rather than full-bleed.
*
* Leading media: `data-md-list-item-avatar` is 40px, full corner, initials in primary-container
* when it is a `<span>`, an image otherwise; `data-md-list-item-image` is a 56px small-corner
+1 -1
View File
@@ -8,7 +8,7 @@
*
* `selectable`/`selection` switch the container's role between `list` and `listbox`
* (list.blade.php; docs/reference/m3/components-actions-communication-containment.md
* § Lists → Accessibility, C-03) — a role takes no CSS of its own.
* § Lists → Accessibility) — a role takes no CSS of its own.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
+19 -45
View File
@@ -3,20 +3,20 @@
* the trigger and popover of a nested menu beside it.
*
* SegmentedMenuTokens (androidx Compose Material 3, Apache-2.0): 48px row (M3's published "List
* item height" wins over the token's own 44dp `Item`, docs/audits/m3-alignment/actions.md §
* ACT-28), 16px either side (`ItemLeadingSpace`/`ItemTrailingSpace`, § ACT-11), 12px between the
* icon, the label and the trailing content (`ItemBetweenSpace`, off the 8dp grid), 4px corners
* (`ItemShape`) that open to 12px at either end of the list (`ItemFirstChildShape`/
* `ItemLastChildShape` — the library's own list ends, 12px, win over `GroupShape`'s 8dp so a
* cluster's ends and a list's ends match, menu-group.css). A selected row
* (`role="menuitemcheckbox"`, the tick beside the colour and shape § ACT-27) takes
* `ItemSelectedShape`'s 12px corner in tertiary-container; `current` — a menu of places rather
* than choices — takes the same 12px corner in secondary-container, the navigation-indicator
* role. `description` (`ItemSupportingTextFont`) grows the row by 8px top and bottom; `shortcut`
* (`ItemTrailingSupportingTextFont`) sits at the end. Every quiet part of the row — the icon, the
* description, the trailing text — inks together as `--md-menu-item-ink`; corner and colour are
* two springs, not one (§ ACT-19), and `icon-class` paints only the leading icon, over that ink
* but never over disabled. The row renders the foundation's `md-state-layer` and `md-focus-ring`
* item height" wins over the token's own 44dp `Item`), 16px either side
* (`ItemLeadingSpace`/`ItemTrailingSpace`), 12px between the icon, the label and the trailing
* content (`ItemBetweenSpace`, off the 8dp grid), 4px corners (`ItemShape`) that open to 12px at
* either end of the list (`ItemFirstChildShape`/`ItemLastChildShape` — the library's own list
* ends, 12px, win over `GroupShape`'s 8dp so a cluster's ends and a list's ends match,
* menu-group.css). A selected row (`role="menuitemcheckbox"`, the tick beside the colour and
* shape) takes `ItemSelectedShape`'s 12px corner in tertiary-container; `current` — a menu of
* places rather than choices — takes the same 12px corner in secondary-container, the
* navigation-indicator role. `description` (`ItemSupportingTextFont`) grows the row by 8px top
* and bottom; `shortcut` (`ItemTrailingSupportingTextFont`) sits at the end. Every quiet part of
* the row — the icon, the description, the trailing text — inks together as
* `--md-menu-item-ink`; corner and colour are two springs, not one — a colour must never
* overshoot — and `icon-class` paints only the leading icon, over that ink but never over
* disabled. The row renders the foundation's `md-state-layer` and `md-focus-ring`
* (foundation/interaction.css) — no `md-touch-target`, since the row is already 48px tall — and
* keeps only one refinement: the ring reads inward (-3px), because a row sits edge to edge in the
* list and an outward one would run past it. A disabled item stays focusable and keeps its ring,
@@ -36,6 +36,7 @@
@import './icon.css';
@import './badge.css';
@import './menu.css';
@layer material.components {
[data-md-menu-item] {
@@ -145,40 +146,13 @@
}
/* The submenu popover: a menu of its own, sharing the container colour of the list it opens
from. The fallbacks are the standard menu's, for a submenu inside some other list. */
from. Its size, radius, padding, shadow and pop-and-fade entry/exit are menu.css's shared
popover chrome (imported above); only its axis and its position are its own — inline,
beside the item, rather than the standard menu's block axis under its trigger. */
[data-md-submenu] {
inset: auto;
margin: 0;
margin-inline: var(--md-sys-measurement-space50);
min-inline-size: 112px;
max-inline-size: 280px;
max-block-size: min(288px, calc(100dvh - 32px));
overflow-y: auto;
border-width: 0;
border-radius: var(--md-sys-shape-corner-lg);
padding: var(--md-sys-measurement-space50);
background-color: var(--material-menu-surface, var(--md-sys-color-surface-container-low));
color: var(--material-menu-ink, var(--md-sys-color-on-surface));
box-shadow: var(--md-sys-elevation-2);
margin: 0 var(--md-sys-measurement-space50);
transform-origin: top;
position-area: inline-end span-block-end;
position-try-fallbacks: flip-inline;
opacity: 0;
scale: 0.95;
transition-property: opacity, scale;
transition-duration: var(--md-sys-motion-effects-fast-duration), var(--md-sys-motion-spatial-fast-duration);
transition-timing-function: var(--md-sys-motion-effects-fast), var(--md-sys-motion-spatial-fast);
&:popover-open:not([data-md-popover-closing]) {
opacity: 1;
scale: 1;
}
@starting-style {
&:popover-open {
opacity: 0;
scale: 0.95;
}
}
}
}
+19 -11
View File
@@ -20,8 +20,8 @@
*
* `[data-md-menu]` is `<x-menu>`'s root, holding the trigger and the popover; the popover itself
* is `[data-md-menu-popover]`, capped at 288px so a long menu scrolls instead of running off the
* top layer's edge (ACT-04), and transitions in by growing from its trigger's corner while it
* fades, on the spatial and effects springs respectively (ACT-26). `data-md-vibrant` swaps its
* top layer's edge, and transitions in by growing from its trigger's corner while it
* fades, on the spatial and effects springs respectively. `data-md-vibrant` swaps its
* container for tertiary-container; a nested submenu inherits the colour through
* `--material-menu-surface`/`--material-menu-ink`, which the sheet's own copy of the list falls
* back to instead (it is not a descendant of the popover).
@@ -50,13 +50,14 @@
display: inline-flex;
}
[data-md-menu-popover] {
--material-menu-surface: var(--md-sys-color-surface-container-low);
--material-menu-ink: var(--md-sys-color-on-surface);
/* The popover chrome a submenu shares (menu-item.css, which imports this file for it): size
limits, the surface's own colour (a submenu with no ancestor popover falls back to the
standard menu's own), radius, padding, shadow, and the pop-and-fade entry/exit — M3's
effects-fast fade behind the spatial-fast scale. Each writes its own whole `margin`, so no
rule here depends on which file a bundler puts first. */
[data-md-menu-popover],
[data-md-submenu] {
inset: auto;
margin: 0;
margin-block: var(--md-sys-measurement-space50);
min-inline-size: 112px;
max-inline-size: 280px;
max-block-size: min(288px, calc(100dvh - 32px));
@@ -64,10 +65,9 @@
border-width: 0;
border-radius: var(--md-sys-shape-corner-lg);
padding: var(--md-sys-measurement-space50);
background-color: var(--material-menu-surface);
color: var(--material-menu-ink);
background-color: var(--material-menu-surface, var(--md-sys-color-surface-container-low));
color: var(--material-menu-ink, var(--md-sys-color-on-surface));
box-shadow: var(--md-sys-elevation-2);
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
opacity: 0;
scale: 0.95;
transition-property: opacity, scale;
@@ -87,6 +87,14 @@
}
}
[data-md-menu-popover] {
--material-menu-surface: var(--md-sys-color-surface-container-low);
--material-menu-ink: var(--md-sys-color-on-surface);
margin: var(--md-sys-measurement-space50) 0;
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}
[data-md-menu-popover]:has(> [data-md-menu-filter]) {
padding: 0;
}
+22 -5
View File
@@ -12,11 +12,11 @@
*
* 560/280px width (max/min), 48px scrim margin, 28px corner and 24dp padding all match the specs
* page; a compact window's full-screen dialog swaps the box for the whole screen with a 56px bar
* (C-17: this used to be 64px) instead of the headline block. It opens on the fast spatial spring
* (this used to be 64px) instead of the headline block. It opens on the fast spatial spring
* and closes at once — a native `<dialog>` makes an exit transition awkward, since
* `@starting-style` only ever supplies an entry.
*
* The dividers (§ Dialogs → Anatomy, C-06): a 1px outline-variant rule under the pinned head and
* The dividers (§ Dialogs → Anatomy): a 1px outline-variant rule under the pinned head and
* over the pinned actions, each only while the body has more content hidden on its side.
* resources/js/dialog.js marks the `<dialog>` with data-md-overflow-top/-bottom as the body
* scrolls; `data-md-modal-divider` says an element takes part at all — the head and the actions
@@ -53,6 +53,19 @@
padding: 0;
background-color: transparent;
color: var(--md-sys-color-on-surface);
}
/* Dialog chrome shared with the time picker's dialog and a modal date picker (timepicker.css,
* datepicker.css, both import this file for it): M3's spatial-fast pop and a 32%-scrim
* backdrop, entering only — a native `<dialog>` makes an exit transition awkward, since
* `@starting-style` only ever supplies an entry. A plain selector list, not `:is()`: `:is()`
* would flatten every branch to the specificity of `[data-md-datepicker-picker]:modal`, the
* one with a pseudo-class, and outrank whatever else the modal or the time picker's dialog is
* layered against.
*/
[data-md-modal],
[data-md-timepicker-dialog],
[data-md-datepicker-picker]:modal {
opacity: 1;
scale: 1;
transition-property: opacity, scale;
@@ -61,13 +74,17 @@
}
@starting-style {
[data-md-modal] {
[data-md-modal],
[data-md-timepicker-dialog],
[data-md-datepicker-picker]:modal {
opacity: 0;
scale: 0.95;
}
}
[data-md-modal]::backdrop {
[data-md-modal]::backdrop,
[data-md-timepicker-dialog]::backdrop,
[data-md-datepicker-picker]:modal::backdrop {
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
}
@@ -100,7 +117,7 @@
}
}
/* The phone-only header bar: M3's full-screen dialog header, 56dp (C-17). */
/* The phone-only header bar: M3's full-screen dialog header, 56dp. */
[data-md-modal-bar] {
display: flex;
height: var(--md-sys-measurement-space700);
@@ -14,7 +14,7 @@
* wide. From 600px icon and label share a 40px horizontal indicator with 16px leading and
* trailing space; the label stays label-medium — `NavigationBarTokens.LabelTextFont` is the bar's
* only label token, and Compose's `ShortNavigationBarItem` passes it for both icon positions (the
* *rail's* horizontal item is label-large, a different component's token; N-08). An active item is
* *rail's* horizontal item is label-large, a different component's token). An active item is
* secondary-container behind on-secondary-container (icon and, from 600px, label); its indicator's
* fill is a background image so it can grow from its centre (navigation-item.css) rather than
* stretch the icon.
+1 -1
View File
@@ -6,7 +6,7 @@
* `NavigationRailColorTokens.kt` gives one colour for all six states, active or not:
* `ItemActiveHoveredStateLayer = … = ItemInactivePressedStateLayer = OnSecondaryContainer`.
* `NavigationBarTokens.kt` states no state-layer tokens of its own, so the bar's item takes the
* rail's (N-19). Hover only where a pointer can hover, so a touch screen does not keep the last
* rail's. Hover only where a pointer can hover, so a touch screen does not keep the last
* hover after a tap; state.css's own opacities (8% hover, 10% focus and press) rather than the
* package's `md-state-layer` class, because the element focused and pressed is the whole item
* while the layer is drawn only on the smaller indicator or pill inside it — `md-state-layer`
@@ -108,14 +108,7 @@
/* ---- Collapsed: icon over label, the 56×32 indicator, badge back on the icon. ---- */
[data-md-navigation-rail-item] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
flex-direction: column;
justify-content: center;
gap: 4px;
@@ -127,136 +120,13 @@
font: var(--md-sys-typescale-label-md);
letter-spacing: var(--md-sys-typescale-label-md-tracking);
white-space: normal;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
flex-direction: column;
justify-content: center;
gap: 4px;
height: auto;
min-height: 64px;
margin-inline: 0;
padding: 6px 4px;
border-radius: 0;
font: var(--md-sys-typescale-label-md);
letter-spacing: var(--md-sys-typescale-label-md-tracking);
white-space: normal;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
flex-direction: column;
justify-content: center;
gap: 4px;
height: auto;
min-height: 64px;
margin-inline: 0;
padding: 6px 4px;
border-radius: 0;
font: var(--md-sys-typescale-label-md);
letter-spacing: var(--md-sys-typescale-label-md-tracking);
white-space: normal;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
flex-direction: column;
justify-content: center;
gap: 4px;
height: auto;
min-height: 64px;
margin-inline: 0;
padding: 6px 4px;
border-radius: 0;
font: var(--md-sys-typescale-label-md);
letter-spacing: var(--md-sys-typescale-label-md-tracking);
white-space: normal;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
flex-direction: column;
justify-content: center;
gap: 4px;
height: auto;
min-height: 64px;
margin-inline: 0;
padding: 6px 4px;
border-radius: 0;
font: var(--md-sys-typescale-label-md);
letter-spacing: var(--md-sys-typescale-label-md-tracking);
white-space: normal;
}
}
}
[data-md-navigation-rail-item] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
--navigation-item-layer: 0;
--navigation-indicator-layer: var(--navigation-layer);
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
--navigation-item-layer: 0;
--navigation-indicator-layer: var(--navigation-layer);
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
--navigation-item-layer: 0;
--navigation-indicator-layer: var(--navigation-layer);
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
--navigation-item-layer: 0;
--navigation-indicator-layer: var(--navigation-layer);
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
--navigation-item-layer: 0;
--navigation-indicator-layer: var(--navigation-layer);
}
}
}
[data-md-navigation-rail-item] [data-md-navigation-label] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
flex: none;
display: -webkit-box;
max-width: 100%;
@@ -265,381 +135,52 @@
text-align: center;
overflow-wrap: anywhere;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
flex: none;
display: -webkit-box;
max-width: 100%;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
text-align: center;
overflow-wrap: anywhere;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
flex: none;
display: -webkit-box;
max-width: 100%;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
text-align: center;
overflow-wrap: anywhere;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
flex: none;
display: -webkit-box;
max-width: 100%;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
text-align: center;
overflow-wrap: anywhere;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
flex: none;
display: -webkit-box;
max-width: 100%;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
text-align: center;
overflow-wrap: anywhere;
}
}
}
[data-md-navigation-rail-item][data-md-active] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
background-image: none;
color: var(--md-sys-color-secondary);
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
background-image: none;
color: var(--md-sys-color-secondary);
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
background-image: none;
color: var(--md-sys-color-secondary);
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
background-image: none;
color: var(--md-sys-color-secondary);
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
background-image: none;
color: var(--md-sys-color-secondary);
}
}
}
[data-md-navigation-rail-item] [data-md-navigation-indicator] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
width: 56px;
height: 32px;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
width: 56px;
height: 32px;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
width: 56px;
height: 32px;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
width: 56px;
height: 32px;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
width: 56px;
height: 32px;
}
}
}
[data-md-navigation-rail-item][data-md-active] [data-md-navigation-indicator] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
background-image: linear-gradient(var(--md-sys-color-secondary-container), var(--md-sys-color-secondary-container));
color: var(--md-sys-color-on-secondary-container);
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
background-image: linear-gradient(var(--md-sys-color-secondary-container), var(--md-sys-color-secondary-container));
color: var(--md-sys-color-on-secondary-container);
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
background-image: linear-gradient(var(--md-sys-color-secondary-container), var(--md-sys-color-secondary-container));
color: var(--md-sys-color-on-secondary-container);
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
background-image: linear-gradient(var(--md-sys-color-secondary-container), var(--md-sys-color-secondary-container));
color: var(--md-sys-color-on-secondary-container);
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
background-image: linear-gradient(var(--md-sys-color-secondary-container), var(--md-sys-color-secondary-container));
color: var(--md-sys-color-on-secondary-container);
}
}
}
[data-md-navigation-rail-item]:focus-visible {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
outline: none;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
outline: none;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
outline: none;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
outline: none;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
outline: none;
}
}
}
[data-md-navigation-rail-item]:focus-visible [data-md-navigation-indicator] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
}
}
[data-md-navigation-badge-end],
[data-md-navigation-badge-icon] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
display: none;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
display: none;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
display: none;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
}
[data-md-navigation-badge-icon] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
display: contents;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
display: contents;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
display: contents;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: contents;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: contents;
}
}
}
}
@@ -14,46 +14,9 @@
}
[data-md-navigation-rail-section]:not(:first-child) {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
margin-top: 12px;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
margin-top: 12px;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
margin-top: 12px;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
margin-top: 12px;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
margin-top: 12px;
}
}
}
[data-md-navigation-rail-heading] {
@@ -67,48 +30,9 @@
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
white-space: nowrap;
text-overflow: ellipsis;
}
[data-md-navigation-rail-heading] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
display: none;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
display: none;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
display: none;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
}
}
+19 -464
View File
@@ -19,8 +19,8 @@
* [data-md-navigation-rail-item] navigation-rail-item.css
* [data-md-navigation-rail-footer] never scrolls
*
* **Collapsed**: a rail (and anything in it) is drawn collapsed while any of these hold, each its
* own rule below wherever a property differs by shape — a fixed `collapsed` mode; a `collapsible` rail the visitor collapsed
* **Collapsed**: a rail (and anything in it) is drawn collapsed while any of these hold, which the
* rail's own two rules below publish as `--md-navigation-rail-value` for everything inside it — a fixed `collapsed` mode; a `collapsible` rail the visitor collapsed
* (`<html data-rail="collapsed">`, set before the first paint by `<x-theme-script>`) and not
* currently open over the page; a `modal` rail not open; below `medium` (600px) a `collapsible`
* rail regardless of choice, M3's floor ("compact → use a navigation bar, not a standard rail");
@@ -56,14 +56,14 @@
*
* `data-md-divider` and `data-md-fill="false"` are both Missing until this rewrite: an optional
* vertical divider on the page's edge, M3's own answer to a page scrolling under a fixed rail, and
* a transparent container as long as the items keep 3:1 contrast (N-22). `data-md-align="center"`
* a transparent container as long as the items keep 3:1 contrast. `data-md-align="center"`
* is M3's other alignment, preferred on a tablet for reach — only the destinations move; the
* header, footer and menu button stay at their ends, and `safe` gives the top back once there are
* more destinations than fit.
*
* The rail-header FAB rules at the end draw M3's nested-FAB elevation (level 0, not a standalone
* FAB's 3 — N-03) and morph it into an extended FAB as the rail opens, rather than swapping two by
* `display` (N-23). Both sit in this file's own `material.components` layer and win by specificity
* FAB's 3) and morph it into an extended FAB as the rail opens, rather than swapping two by
* `display`. Both sit in this file's own `material.components` layer and win by specificity
* alone, whichever copy of fab.css a build places last: the flat shadow outranks fab.css's resting
* and hover shadows, and the collapsed FAB's `gap` and minimum width outrank fab.css's extended-size
* and collapse-on-scroll rules, which would otherwise tie with them.
@@ -187,14 +187,7 @@
alone; the label is taken out of the drawing, not out of the page (navigation-rail-item.css
renders it always — it is what names the destination). */
[data-md-navigation-rail][data-md-width='narrow'] [data-md-navigation-rail-item] [data-md-navigation-label] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
position: absolute;
width: 1px;
height: 1px;
@@ -204,64 +197,6 @@
clip-path: inset(50%);
white-space: nowrap;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}
}
/* A modal rail keeps its collapsed width in the layout while it is open over it, as Compose's
@@ -284,7 +219,7 @@
/* "Compact → don't use a standard rail, use a navigation bar." A `collapsible` rail takes
the width the visitor chose, which on a 360px screen would be two-thirds of it, so it is
floored at its collapsed width (N-24). `collapsed` and `expanded` mean what they say and
floored at its collapsed width. `collapsed` and `expanded` mean what they say and
are left alone: wrap one in an application's own `medium:` element if it must not show
on a phone. */
[data-md-navigation-rail='collapsible'] {
@@ -315,7 +250,7 @@
/* M3's two optional container treatments: "optional vertical divider separates rail from
content, placed on the content-adjacent edge" — also its answer to a page scrolling under a
fixed rail — and "container fill can be turned off (transparent) as long as items keep ≥3:1
contrast" (N-22). Neither applies to a rail open over a scrim, a surface over the page. */
contrast". Neither applies to a rail open over a scrim, a surface over the page. */
[data-md-navigation-rail][data-md-divider]:not([data-md-open], [data-md-closing='sheet']) > [data-md-navigation-rail-panel] {
border-inline-end: 1px solid var(--md-sys-color-outline-variant);
}
@@ -393,14 +328,7 @@
the collapsed branches, which stop the moment the rail is open — that is what makes it the
expanded rail that comes back, on the unconditional rule below. */
[data-md-navigation-rail][data-md-hide-when-collapsed] > [data-md-navigation-rail-panel] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
display: none;
translate: -100% 0;
transition:
@@ -410,64 +338,6 @@
translate: 100% 0;
}
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
display: none;
translate: -100% 0;
transition:
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
&:is([dir='rtl'], [dir='rtl'] *) {
translate: 100% 0;
}
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
display: none;
translate: -100% 0;
transition:
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
&:is([dir='rtl'], [dir='rtl'] *) {
translate: 100% 0;
}
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
translate: -100% 0;
transition:
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
&:is([dir='rtl'], [dir='rtl'] *) {
translate: 100% 0;
}
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
translate: -100% 0;
transition:
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
&:is([dir='rtl'], [dir='rtl'] *) {
translate: 100% 0;
}
}
}
}
[data-md-navigation-rail][data-md-hide-when-collapsed][data-md-open] > [data-md-navigation-rail-panel] {
@@ -591,46 +461,9 @@
}
[data-md-navigation-rail][data-md-width='narrow'] [data-md-navigation-rail-header]:has([data-md-navigation-rail-menu]) [data-md-navigation-rail-menu-row] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
padding-inline-start: 20px;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
padding-inline-start: 20px;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
padding-inline-start: 20px;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
padding-inline-start: 20px;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
padding-inline-start: 20px;
}
}
}
[data-md-navigation-rail-menu] {
@@ -658,137 +491,24 @@
[data-md-navigation-rail-menu-collapse-icon],
[data-md-navigation-rail-menu-expand-icon] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
display: none;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
display: none;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
display: none;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
}
[data-md-navigation-rail-menu-expand-icon] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
display: contents;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
display: contents;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
display: contents;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: contents;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: contents;
}
}
}
[data-md-navigation-rail-brand] {
min-width: 0;
flex: 1 1 0%;
}
[data-md-navigation-rail-brand] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
display: none;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
display: none;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
display: none;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
display: none;
}
}
}
/* The FAB row: 20px either side in the default 96px rail, 12px in a narrow 80 while collapsed
@@ -803,46 +523,9 @@
}
[data-md-navigation-rail][data-md-width='narrow'] [data-md-navigation-rail-fab-row] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
padding-inline: 12px;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
padding-inline: 12px;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
padding-inline: 12px;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
padding-inline: 12px;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
padding-inline: 12px;
}
}
}
[data-md-navigation-rail-destinations] {
@@ -858,50 +541,10 @@
[data-md-navigation-rail-footer] {
display: flex;
flex-direction: column;
}
[data-md-navigation-rail-destinations],
[data-md-navigation-rail-footer] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
gap: 4px;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
gap: 4px;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
gap: 4px;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
gap: 4px;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
gap: 4px;
}
}
}
[data-md-navigation-rail-footer] {
@@ -921,7 +564,7 @@
/*
* A FAB in the rail's header rests at elevation 0, not the 3 a standalone FAB has
* (§ Navigation Rail/Behaviour: "when nested within another component, such as the navigation
* rail, the FAB's resting elevation should be level 0" — N-03). The doubled `[data-md-fab]`
* rail, the FAB's resting elevation should be level 0"). The doubled `[data-md-fab]`
* attribute only adds specificity, over fab.css's `[data-md-fab]:hover` too, as
* `[data-md-navigation-rail-item][data-md-navigation-rail-item]::before`
* (navigation-rail-item.css) does for the same reason.
@@ -933,7 +576,7 @@
/*
* "Rail transitions from collapsed to expanded should animate the FAB into and out of an
* extended FAB" (N-23) — so the header takes one `<x-fab label icon>` and morphs it, rather
* extended FAB" — so the header takes one `<x-fab label icon>` and morphs it, rather
* than swapping two by `display`, which showed the collapsed one at once while the rail's
* width was still springing. The label's width springs on the rail's own spatial spring; with
* the gap and the extended FAB's own minimum width both zeroed, `aspect-ratio` squares what is
@@ -949,109 +592,21 @@
transition:
max-width var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-spatial-default),
opacity var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);
}
[data-md-navigation-rail-header] [data-md-fab] > span {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
max-width: 0;
opacity: 0;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
max-width: 0;
opacity: 0;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
max-width: 0;
opacity: 0;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
max-width: 0;
opacity: 0;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
max-width: 0;
opacity: 0;
}
}
}
/* `[data-md-extended]` doubled for the same reason as the shadow's `[data-md-fab]` above: it
outranks fab.css's `[data-md-fab][data-md-extended][data-md-size]` gap and its collapsed
collapse-on-scroll FAB's minimum width, three attributes each. */
[data-md-navigation-rail-header] [data-md-fab][data-md-extended][data-md-extended] {
&:where(
[data-md-navigation-rail='collapsed'],
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]),
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
min-inline-size: 0;
aspect-ratio: 1;
gap: 0;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
min-inline-size: 0;
aspect-ratio: 1;
gap: 0;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
min-inline-size: 0;
aspect-ratio: 1;
gap: 0;
}
}
@media (840px <= width < 1200px) {
&:where(
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]),
:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
min-inline-size: 0;
aspect-ratio: 1;
gap: 0;
}
}
@media (width >= 1200px) {
&:where(
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]),
[data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *
) {
min-inline-size: 0;
aspect-ratio: 1;
gap: 0;
}
}
}
}
+7 -29
View File
@@ -23,10 +23,12 @@
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './color.css';
@layer material.components {
[data-md-progress] {
color: var(--md-sys-color-primary);
stroke: var(--md-sys-color-secondary-container);
color: var(--md-color, var(--md-sys-color-primary));
stroke: var(--md-container, var(--md-sys-color-secondary-container));
& > svg {
display: block;
@@ -80,33 +82,9 @@
height: 52px;
}
[data-md-progress][data-md-color='secondary'] {
color: var(--md-sys-color-secondary);
/* Every other hue comes from the shared colour-role table (color.css): its own `primary` entry
is plain primary-container, so the one hue this file draws differently keeps its override. */
[data-md-progress][data-md-color='primary'] {
stroke: var(--md-sys-color-secondary-container);
}
[data-md-progress][data-md-color='tertiary'] {
color: var(--md-sys-color-tertiary);
stroke: var(--md-sys-color-tertiary-container);
}
[data-md-progress][data-md-color='error'] {
color: var(--md-sys-color-error);
stroke: var(--md-sys-color-error-container);
}
[data-md-progress][data-md-color='success'] {
color: var(--md-sys-color-success);
stroke: var(--md-sys-color-success-container);
}
[data-md-progress][data-md-color='warning'] {
color: var(--md-sys-color-warning);
stroke: var(--md-sys-color-warning-container);
}
[data-md-progress][data-md-color='info'] {
color: var(--md-sys-color-info);
stroke: var(--md-sys-color-info-container);
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
* back to the closed opacity.
*
* resources/js/rich-tooltip.js shows and hides the bubble and keeps the trigger described with
* `aria-describedby` — and, while `persistent`, `aria-haspopup`/`aria-expanded` (ACT-22) — none of
* `aria-describedby` — and, while `persistent`, `aria-haspopup`/`aria-expanded` — none of
* which this stylesheet draws.
*/
+3 -11
View File
@@ -84,17 +84,9 @@
}
}
& > input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
}
/* The radio stays in the page, for the form, the keyboard and a screen reader —
`md-visually-hidden` on the element itself (text.css), which outranks this layer
whatever wins the specificity, `material.text` sitting above `material.components`. */
}
[data-md-scheme-picker-swatches] {
-5
View File
@@ -168,11 +168,6 @@
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
}
[data-md-search-input]::placeholder {
color: var(--md-sys-color-on-surface-variant);
opacity: 1;
}
[data-md-search-input]::-webkit-search-cancel-button,
[data-md-search-input]::-webkit-search-decoration {
appearance: none;
+3 -3
View File
@@ -2,8 +2,8 @@
* <x-section-nav>: the navigation inside one area of an application — M3's secondary tabs from
* `medium` (600px), five or more of them the scrollable tab bar M3's own accessibility page
* blesses rather than a grid that would break the tab bar's divider and strand the upper rows'
* indicators against nothing (docs/audits/m3-alignment/navigation.md N-16); below `medium`, where a
* row of them never fits, a button naming the current section opens the same list as a menu.
* indicators against nothing; below `medium`, where a row of them never fits, a button naming the
* current section opens the same list as a menu.
*
* [data-md-section-nav] min-inline-size: 0
* [data-md-section-nav-picker] hidden from medium
@@ -14,7 +14,7 @@
*
* Reuses `<x-tabs>`'s own hooks and stylesheet rather than a tab bar of its own: a link marked
* `aria-current="page"` takes the same active indicator and colour tabs.css already draws for
* `aria-selected="true"` (N-21), so nothing here repeats a rule tabs.css owns; the only addition is
* `aria-selected="true"`, so nothing here repeats a rule tabs.css owns; the only addition is
* this bar's own list items — flex rows, so tabs.css's `li > [data-md-tab]` share of the width
* applies, and `min-inline-size: 0`, so a long title in a fixed row wraps to its second line and
* truncates there (tabs.css, M3's "may wrap to a max second line if needed with truncation")
+10 -42
View File
@@ -45,13 +45,14 @@
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './icon.css';
@import './color.css';
@layer material.components {
[data-md-slider] {
--slider-active: var(--md-sys-color-primary);
--slider-inactive: var(--md-sys-color-secondary-container);
--slider-on-active: var(--md-sys-color-on-primary);
--slider-on-inactive: var(--md-sys-color-on-secondary-container);
--slider-active: var(--md-color, var(--md-sys-color-primary));
--slider-inactive: var(--md-container, var(--md-sys-color-secondary-container));
--slider-on-active: var(--md-on-color, var(--md-sys-color-on-primary));
--slider-on-inactive: var(--md-on-container, var(--md-sys-color-on-secondary-container));
--slider-disabled-active: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
--slider-disabled-inactive: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent);
--slider-across: 48px;
@@ -84,44 +85,11 @@
--slider-handle: 108px;
}
[data-md-slider][data-md-color='secondary'] {
--slider-active: var(--md-sys-color-secondary);
--slider-on-active: var(--md-sys-color-on-secondary);
}
[data-md-slider][data-md-color='tertiary'] {
--slider-active: var(--md-sys-color-tertiary);
--slider-inactive: var(--md-sys-color-tertiary-container);
--slider-on-active: var(--md-sys-color-on-tertiary);
--slider-on-inactive: var(--md-sys-color-on-tertiary-container);
}
[data-md-slider][data-md-color='error'] {
--slider-active: var(--md-sys-color-error);
--slider-inactive: var(--md-sys-color-error-container);
--slider-on-active: var(--md-sys-color-on-error);
--slider-on-inactive: var(--md-sys-color-on-error-container);
}
[data-md-slider][data-md-color='success'] {
--slider-active: var(--md-sys-color-success);
--slider-inactive: var(--md-sys-color-success-container);
--slider-on-active: var(--md-sys-color-on-success);
--slider-on-inactive: var(--md-sys-color-on-success-container);
}
[data-md-slider][data-md-color='warning'] {
--slider-active: var(--md-sys-color-warning);
--slider-inactive: var(--md-sys-color-warning-container);
--slider-on-active: var(--md-sys-color-on-warning);
--slider-on-inactive: var(--md-sys-color-on-warning-container);
}
[data-md-slider][data-md-color='info'] {
--slider-active: var(--md-sys-color-info);
--slider-inactive: var(--md-sys-color-info-container);
--slider-on-active: var(--md-sys-color-on-info);
--slider-on-inactive: var(--md-sys-color-on-info-container);
/* Every other hue comes from the shared colour-role table (color.css): its own `primary` entry
is plain primary-container, so the one hue this file draws differently keeps its override. */
[data-md-slider][data-md-color='primary'] {
--slider-inactive: var(--md-sys-color-secondary-container);
--slider-on-inactive: var(--md-sys-color-on-secondary-container);
}
/* Disabled: the colours give way to on-surface at M3's disabled opacities. */
+1 -4
View File
@@ -66,10 +66,7 @@
[data-md-split-button] [data-md-split] {
--md-split-button-corner: var(--md-split-button-inner);
border-start-start-radius: var(--md-split-button-corner);
border-end-start-radius: var(--md-split-button-corner);
border-start-end-radius: var(--md-split-button-corner);
border-end-end-radius: var(--md-split-button-corner);
border-radius: var(--md-split-button-corner);
}
[data-md-split-button] [data-md-split]:active {
+8 -8
View File
@@ -12,7 +12,7 @@
* [data-md-tab-content] [data-md-icon] (optional), [data-md-tab-label],
* [data-md-tab-indicator]
* [data-md-tab-label] [data-md-tab-text], [data-md-badge] (optional)
* [data-md-tab-panel] role="tabpanel", hidden server-side until chosen (N-05)
* [data-md-tab-panel] role="tabpanel", hidden server-side until chosen
*
* "Labels: single row by default; may wrap to a max second line if needed with truncation, or use
* scrollable tabs to give longer titles more room" (§ Tabs, "Behaviour and guidelines"). A fixed
@@ -25,7 +25,7 @@
*
* The active indicator is drawn in every tab and shown under the chosen one, so it is right before
* Alpine starts and after a morph: 3px with rounded top corners under the content of a primary tab
* (at least 24px wide, inset 2px at each side — N-18), 2px across the whole of a secondary one.
* (at least 24px wide, inset 2px at each side), 2px across the whole of a secondary one.
* When the choice changes, a view transition moves it from the old tab to the new (resources/js/tabs.js),
* as M3's slides.
*
@@ -33,10 +33,10 @@
* rest of the package draws 3px thick 2px outside the element (tokens/state.css). `--tabs-ring` is
* the room that takes: the bar carries it as padding on every side, and the state layer and the
* indicator reach back out into it, so the divider still sits against the tabs and the indicator
* still sits on the divider (N-20). That makes the bar 10px taller than M3's 48px tab. A scrollable
* set's first tab is offset 52dp from the leading edge, so it reads as scrollable (N-10). A link
* still sits on the divider. That makes the bar 10px taller than M3's 48px tab. A scrollable
* set's first tab is offset 52dp from the leading edge, so it reads as scrollable. A link
* marked `aria-current="page"` (`<x-section-nav>`'s tabs) takes the active indicator and colour
* exactly as `aria-selected="true"` does (N-21).
* exactly as `aria-selected="true"` does.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@@ -63,7 +63,7 @@
}
/* "Offset the first scrollable tab 52dp from the leading edge so it's clear that more content
is available" (N-10). Logical, so a right-to-left page mirrors it. */
is available". Logical, so a right-to-left page mirrors it. */
[data-md-tabs-bar][data-md-scrollable] {
padding-inline-start: 52px;
scroll-padding-inline-start: 52px;
@@ -141,7 +141,7 @@
}
/* A link-based bar marks the page with aria-current, not aria-selected; both are the chosen
tab, and both take the variant's active colour (N-21). */
tab, and both take the variant's active colour. */
[data-md-tab]:is([aria-selected='true'], [aria-current='page']) {
color: var(--md-sys-color-primary);
}
@@ -196,7 +196,7 @@
white-space: normal;
}
/* Primary indicators are inset 2dp at each side (N-18); both variants sit on the divider, which
/* Primary indicators are inset 2dp at each side; both variants sit on the divider, which
the ring's room has pushed below the tab. */
[data-md-tab-indicator] {
position: absolute;
+6 -6
View File
@@ -1,18 +1,18 @@
/*
* <x-theme-toggle>: `mode="toggle"|"cycle"` an icon button between light, dark (and system for
* cycle); `mode="picker"|"contrast"` a connected group over native radios (docs/audits/m3-alignment/
* navigation.md N-04, N-17 — the Expressive-deprecated segmented button's successor,
* `<x-group>`, already fixes both there). This file only draws the icon button; the group's own
* shape, colour and 48px minimum below `medium` are group.css's.
* cycle); `mode="picker"|"contrast"` a connected group over native radios — the Expressive-
* deprecated segmented button's successor, `<x-group>`, already gives it the keyboard handling and
* shape a hand-rolled control would have to reimplement. This file only draws the icon button; the
* group's own shape, colour and 48px minimum below `medium` are group.css's.
*
* [data-md-theme-toggle="toggle"|"cycle"] the icon button: 40px reaching 48px through
* md-touch-target (N-01), morphing corner
* md-touch-target, morphing corner
* [data-md-theme-toggle="picker"|"contrast"] the group's own wrapper (`role="group"`)
*
* The icon button also carries `data-md-icon-button`, the hook `resources/css/components/
* toolbar.css` matches generically (not `[data-md-button][data-md-icon-button]`, as button.css
* requires): a theme toggle placed in a toolbar's `actions` takes the toolbar's own icon colour
* (N-13) the same way one of its `<x-button icon>` children would.
* the same way one of its `<x-button icon>` children would.
*
* The corner morphs from full to `sm` while pressed on the fast spatial spring — M3 Expressive's
* pressed-state shape change, the same motion `<x-button>`'s own icon buttons use
+3 -17
View File
@@ -64,6 +64,7 @@
@import './field.css';
@import './icon.css';
@import './button.css';
@import './modal.css';
@layer material.components {
@property --timepicker-angle {
@@ -77,7 +78,8 @@
caret-color: transparent;
}
/* The dialog: M3's spatial-fast pop, a 32%-scrim backdrop, no default border or padding. */
/* The dialog's size, no default border or padding; the entry pop and the scrim are
modal.css's shared dialog chrome, imported above. */
[data-md-timepicker-dialog] {
max-width: calc(100vw - var(--md-sys-measurement-space400));
max-height: calc(100dvh - var(--md-sys-measurement-space400));
@@ -87,22 +89,6 @@
border: 0;
background: transparent;
color: var(--md-sys-color-on-surface);
opacity: 1;
scale: 1;
transition-property: opacity, scale;
transition-duration: var(--md-sys-motion-spatial-fast-duration);
transition-timing-function: var(--md-sys-motion-spatial-fast);
}
@starting-style {
[data-md-timepicker-dialog] {
opacity: 0;
scale: 0.95;
}
}
[data-md-timepicker-dialog]::backdrop {
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
}
[data-md-timepicker-surface] {
+5 -5
View File
@@ -15,19 +15,19 @@
* as M3 nudges a snackbar clear of a docked toolbar and never in front of navigation;
* `data-md-position="bottom-start"` moves it to the start edge, with M3's margin growing from `medium`. resources/js/snackbar.js
* publishes the snackbar's own height as `--material-snackbar-height`, so a FAB (fab.css) can
* lift clear of it M3 puts a snackbar above a FAB, never in front of or behind one (ACT-17).
* lift clear of it M3 puts a snackbar above a FAB, never in front of or behind one.
* Below `medium` it is full width; from there it hugs its line length, which an application's own
* class can still bound.
*
* The action and the close button are both drawn at 40px and render the foundation's
* `md-state-layer`, `md-focus-ring` and `md-touch-target` (foundation/interaction.css) to reach
* M3's 48px target (ACT-18); nothing here copies their rules.
* M3's 48px target; nothing here copies their rules.
*
* It enters on a translate and a fade on the fast spatial spring, `@starting-style` giving both a
* start. No state icon: M3 says to avoid one in a snackbar (ACT-20). The live region is the host
* itself, in the page before any message is, never the snackbar that comes and goes (ACT-02);
* start. No state icon: M3 says to avoid one in a snackbar. The live region is the host
* itself, in the page before any message is, never the snackbar that comes and goes;
* resources/js/snackbar.js queues, times and dismisses a toast, including on Escape while it
* holds the focus and on Alt+G, which moves the focus to one carrying an action (ACT-34).
* holds the focus and on Alt+G, which moves the focus to one carrying an action.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
+5 -5
View File
@@ -15,7 +15,7 @@
* 8px at its ends and 4px between controls; `vertical` stands it on end
*
* Placed over the page, a horizontal toolbar keeps 16dp from the window's edge and a vertical one
* 24dp, which is M3's minimum for each (N-12).
* 24dp, which is M3's minimum for each.
*
* On web and large screens a docked toolbar "can be rounded", and dividers can organise its
* controls into groups: `rounded` does the first from `expanded` (840px), and a divider in the
@@ -23,7 +23,7 @@
*
* `<x-button>` and `<x-fab>` draw their own ink from `data-md-color`/`data-md-variant`, not a
* utility, so a toolbar's recolouring rules — vibrant's icon buttons, a standard toolbar's primary
* ones (N-13), a docked FAB's flattened shadow — sit in this file's own `material.components`
* ones, a docked FAB's flattened shadow — sit in this file's own `material.components`
* layer like everything else, each more specific than the button.css or fab.css rule it overrides,
* whichever file an application imports first. The recolouring leaves a disabled button alone, so
* button.css's disabled colours still apply inside a toolbar.
@@ -82,7 +82,7 @@
}
/* M3's colour list for a standard toolbar ends "Standard button (Primary)", as the vibrant list
ends "Standard button (On primary container)" the row above (N-13). Both rows name the
ends "Standard button (On primary container)" the row above. Both rows name the
*standard* button, so a button that fills its own container is left alone: it carries the
matching label colour (`filled` on-primary, `tonal` on-tone, either selected), and painting
that label primary put a primary icon on a primary fill the current page's button in a
@@ -140,7 +140,7 @@
* A toolbar at the bottom shares the screen region with a navigation bar, so it clears the one
* <x-scaffold> publishes as --material-bottom-bar, exactly as the FAB and the snackbar do
* `max()`, not a sum, because that height already swallows the bottom safe area; without a bar
* the safe area alone applies (N-02). A *docked* toolbar and a navigation bar must never be on
* the safe area alone applies. A *docked* toolbar and a navigation bar must never be on
* screen together at all; the offset is the backstop, not a licence.
*/
[data-md-toolbar-place='bottom'] {
@@ -160,7 +160,7 @@
}
/* "Horizontal toolbars should have a minimum 16dp margin from the edge of the window. Vertical
toolbars should have a minimum 24dp margin" (N-12). */
toolbars should have a minimum 24dp margin". */
[data-md-toolbar][data-md-vertical][data-md-toolbar-place='end'],
[data-md-toolbar-group][data-md-vertical][data-md-toolbar-place='end'] {
inset-inline-end: calc(var(--md-sys-measurement-space300) + var(--material-safe-right, env(safe-area-inset-right)));
+9 -2
View File
@@ -10,7 +10,8 @@
* Every rule of the package sits in a sub-layer of `material`, in this order, lowest first:
*
* material.reset foundation/reset.css the browser's defaults taken back
* material.tokens foundation/tokens.css every `--md-sys-*` and `--md-ref-*` property
* material.tokens tokens/*.css, imported here directly every `--md-sys-*` and
* `--md-ref-*` property
* material.base foundation/base.css, foundation/interaction.css the page, the font
* face, and md-state-layer, md-focus-ring, md-touch-target, md-link
* material.layout the layout components: scaffold, panes, the canonical layouts
@@ -33,7 +34,13 @@
@import './foundation/reset.css';
@import './foundation/hidden.css';
@import './foundation/tokens.css';
@import './tokens/scheme.css';
@import './tokens/shape.css';
@import './tokens/elevation.css';
@import './tokens/motion.css';
@import './tokens/type.css';
@import './tokens/state.css';
@import './tokens/spacing.css';
@import './foundation/base.css';
@import './foundation/interaction.css';
@import './text.css';
-27
View File
@@ -1,27 +0,0 @@
/*
* The tokens: every `--md-sys-*` and `--md-ref-*` custom property the package reads.
*
* Each token file wraps its own declarations in `material.tokens`, because an `@import` cannot sit
* inside a layer block and `layer()` on the import would nest the layer a second time. Being the
* lowest layer but one, a token here loses to the same property declared anywhere an application
* writes it: its generated `material-scheme.css`, unlayered, replaces the default scheme below
* whatever its selectors, and a typeface or a spacing step is overridden the same way.
*
* scheme the colour roles of M3's baseline scheme, light and dark, three contrast levels
* shape the corner scale
* elevation the five shadow levels
* motion the springs of both motion schemes and M3's legacy easings
* type the 30 type styles and the brand typeface
* state the state-layer and disabled opacities
* spacing the measurement scale, space25 space900
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import '../tokens/scheme.css';
@import '../tokens/shape.css';
@import '../tokens/elevation.css';
@import '../tokens/motion.css';
@import '../tokens/type.css';
@import '../tokens/state.css';
@import '../tokens/spacing.css';
+5 -37
View File
@@ -24,12 +24,10 @@
* bar, the FAB wrapper and `<main>` stack regardless of the row's own display.
*
* `[data-md-scaffold-actions]`'s column layout is `rail-collapsed:flex-col`, the one Tailwind
* variant scaffold.blade.php still used (`resources/css/tailwind.css`, removed with this rule):
* written out branch for branch as the navigation rail's own rewrite did for its internal parts
* (`resources/css/components/navigation-rail.css`) the three width-independent conditions in one
* `:where()` group, the four width-gated ones each in their own `@media` block, since CSS cannot
* merge different queries. Only the "descendant of a collapsed rail" half of each pair applies:
* the actions row can never be the rail element itself.
* variant scaffold.blade.php still used (`resources/css/tailwind.css`, removed with this rule): the
* actions row is always inside a rail, never the rail element itself, so it reads
* `--md-navigation-rail-value` with `@container style()` like any other application content does
* (`resources/css/components/navigation-rail.css`), rather than copying the rail's own conditions.
*
* `[data-md-scaffold-bar]` pins the navigation bar to the window's bottom edge below `medium`, at
* z-index 30: over the page, under the rail's scrim (40) and the snackbar (50, toast.css). While a
@@ -137,39 +135,9 @@
padding-block-start: var(--md-sys-measurement-space100);
padding-inline: 20px;
&:where(
[data-md-navigation-rail='collapsed'] *,
[data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *,
[data-md-navigation-rail='modal']:not([data-md-open]) *
) {
@container style(--md-navigation-rail-value: collapsed) {
flex-direction: column;
}
@media (width < 600px) {
&:where([data-md-navigation-rail='collapsible']:not([data-md-open]) *) {
flex-direction: column;
}
}
@media (width < 840px) {
&:where([data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
flex-direction: column;
}
}
@media (width >= 840px) {
@media (width < 1200px) {
&:where(:is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
flex-direction: column;
}
}
}
@media (width >= 1200px) {
&:where([data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) *) {
flex-direction: column;
}
}
}
[data-md-scaffold-content] > main {
+5 -41
View File
@@ -144,53 +144,17 @@
}
/* Shape: a corner swatch, filled with a tonal colour that is decoration only (never meaning,
same as the corner it demonstrates), one per corner token. */
same as the corner it demonstrates), one per corner token. The swatch is a `<x-surface>`
hook (`data-md-surface`, layout/surface.css) for its `data-md-corner` table alone this
rule's own background outranks surface.css's, `material.components` over `material.layout`
whatever either's specificity, so the swatch keeps its tonal fill rather than surface.css's
default surface-container. */
[data-md-showcase-shape-swatch] {
display: block;
height: 80px;
background-color: var(--md-sys-color-primary-container);
}
[data-md-showcase-shape-swatch][data-md-corner='none'] {
border-radius: var(--md-sys-shape-corner-none);
}
[data-md-showcase-shape-swatch][data-md-corner='xs'] {
border-radius: var(--md-sys-shape-corner-xs);
}
[data-md-showcase-shape-swatch][data-md-corner='sm'] {
border-radius: var(--md-sys-shape-corner-sm);
}
[data-md-showcase-shape-swatch][data-md-corner='md'] {
border-radius: var(--md-sys-shape-corner-md);
}
[data-md-showcase-shape-swatch][data-md-corner='lg'] {
border-radius: var(--md-sys-shape-corner-lg);
}
[data-md-showcase-shape-swatch][data-md-corner='lg-increased'] {
border-radius: var(--md-sys-shape-corner-lg-increased);
}
[data-md-showcase-shape-swatch][data-md-corner='xl'] {
border-radius: var(--md-sys-shape-corner-xl);
}
[data-md-showcase-shape-swatch][data-md-corner='xl-increased'] {
border-radius: var(--md-sys-shape-corner-xl-increased);
}
[data-md-showcase-shape-swatch][data-md-corner='xxl'] {
border-radius: var(--md-sys-shape-corner-xxl);
}
[data-md-showcase-shape-swatch][data-md-corner='full'] {
border-radius: var(--md-sys-shape-corner-full);
}
/* Elevation: a tile at one shadow level, on the surface-container-high M3 pairs a shadow with. */
[data-md-showcase-elevation-tile] {
display: flex;
+2 -2
View File
@@ -15,8 +15,8 @@
*
* The brand typeface is Google Sans Flex (font.css). Emphasized styles are one weight step
* heavier and fully rounded ("ROND" 100) the axis that typeface exists for, verified by
* `npm run check:font` and they carry their own tracking, because M3 widens four of them and
* takes Display Large's negative tracking back to zero.
* tests/Feature/FontTest.php and they carry their own tracking, because M3 widens four of them
* and takes Display Large's negative tracking back to zero.
*
* `--md-ref-typeface-mono` is the package's, not M3's (M3 names a brand and a plain typeface and
* nothing for code): the platform's own monospace faces, nothing downloaded, for what a person