@stackific/md3 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,38 +1,149 @@
1
1
  # @stackific/md3
2
2
 
3
- Material Design 3 framework SCSS + plain JavaScript. No build step required. No React, Vue, or other framework lock-in. Drop it into any HTML page and write semantic markup.
3
+ > Material Design 3 framework. Compiled CSS + plain-JS runtime. Drop into any HTML page; no framework required.
4
4
 
5
- - **Light + dark + auto** modes that follow OS preference, persist across reloads, and update live when the OS theme flips.
6
- - **Multiple named brand themes** in a single bundle, swap with one attribute.
7
- - **No JS framework required.** Works in plain HTML, also fine with React, Vue, Svelte, htmx, etc.
8
- - **Runtime weight**: ~17 kB JS, ~22 kB CSS (gzipped) for the full default build.
9
- - **Optional dynamic theming** from a hex / image / file via `material-dynamic-colors` (opt-in dependency).
5
+ - Light, dark, and OS-following `auto` modes CSS-driven, no JS needed.
6
+ - 18 baked brand themes, swap with one attribute.
7
+ - Optional runtime theme generation from a hex / image / file (via `material-dynamic-colors`).
8
+ - Material Symbols icon fonts ship inside the CSS no Google Fonts import.
10
9
 
11
10
  ---
12
11
 
13
- ## Quick start (CDN)
12
+ ## Table of contents
13
+
14
+ - [Install](#install)
15
+ - [Quick start](#quick-start)
16
+ - [Bootstrap](#bootstrap) — `data-theme`, `data-mode`, `data-ui`
17
+ - [Runtime API](#runtime-api) — `ui(...)`
18
+ - [Main layout](#main-layout)
19
+ - **Components**
20
+ - [Badge](#badge)
21
+ - [Button](#button)
22
+ - [Card](#card)
23
+ - [Checkbox](#checkbox)
24
+ - [Chip](#chip)
25
+ - [Container](#container)
26
+ - [Dialog](#dialog)
27
+ - [Divider](#divider)
28
+ - [Expansion](#expansion)
29
+ - [Field](#field) (Input · Select · Textarea · Search · Custom inputs)
30
+ - [Grid](#grid)
31
+ - [Header & Footer](#header--footer)
32
+ - [Icon](#icon)
33
+ - [Layout](#layout) (`.absolute` / `.fixed`)
34
+ - [List](#list)
35
+ - [Media](#media) (`<img>`, `<svg>`, `<video>`)
36
+ - [Menu](#menu)
37
+ - [Navigation](#navigation)
38
+ - [Overlay](#overlay)
39
+ - [Page](#page)
40
+ - [Progress](#progress)
41
+ - [Radio](#radio)
42
+ - [Shape](#shape)
43
+ - [Slider](#slider)
44
+ - [Snackbar](#snackbar)
45
+ - [Switch](#switch)
46
+ - [Table](#table)
47
+ - [Tabs](#tabs)
48
+ - [Tooltip](#tooltip)
49
+ - [Typography](#typography)
50
+ - **Helpers**
51
+ - [Alignments](#alignments)
52
+ - [Blurs](#blurs)
53
+ - [Color roles](#color-roles)
54
+ - [Directions](#directions)
55
+ - [Elevations](#elevations)
56
+ - [Forms](#forms-helpers) (border, round, corners, …)
57
+ - [Margins](#margins)
58
+ - [Opacities](#opacities)
59
+ - [Paddings](#paddings)
60
+ - [Positions](#positions)
61
+ - [Responsive](#responsive)
62
+ - [Ripples](#ripples)
63
+ - [Scrolls](#scrolls)
64
+ - [Shadows](#shadows)
65
+ - [Sizes](#sizes)
66
+ - [Spaces](#spaces)
67
+ - [Waves](#waves)
68
+ - [Zoom](#zoom)
69
+ - [Design tokens](#design-tokens)
70
+ - [Themes](#themes)
71
+ - [Theme & Mode selector](#theme-and-mode-selector)
72
+ - [Breakpoints](#breakpoints)
73
+ - [Repository](#repository)
14
74
 
15
- Paste into any HTML file:
75
+ ---
76
+
77
+ ## Install
78
+
79
+ **CDN** — paste into any HTML page:
80
+
81
+ ```html
82
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stackific/md3/dist/md3.css">
83
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@stackific/md3/dist/md3.js"></script>
84
+ ```
85
+
86
+ **npm / pnpm / yarn:**
87
+
88
+ ```sh
89
+ npm install @stackific/md3
90
+ # or: pnpm add @stackific/md3
91
+ # or: yarn add @stackific/md3
92
+ ```
93
+
94
+ ```js
95
+ import "@stackific/md3"; // runtime, registers globalThis.ui
96
+ import "@stackific/md3/style"; // compiled CSS
97
+ ```
98
+
99
+ **Local files** — download `dist/md3.css`, `dist/md3.js`, and the `dist/assets/*` they reference from the published package, then serve them from your own host:
100
+
101
+ ```html
102
+ <link rel="stylesheet" href="/md3/md3.css">
103
+ <script type="module" src="/md3/md3.js"></script>
104
+ ```
105
+
106
+ For Vite users, build with `assetsInlineLimit: 0` to keep the bundled CSS at its original size (otherwise the font files get inlined as data URIs).
107
+
108
+ **Optional — runtime theme generation from a hex / image / file:**
109
+
110
+ ```sh
111
+ npm install material-dynamic-colors
112
+ ```
113
+
114
+ ```js
115
+ import "material-dynamic-colors"; // once, at app entry
116
+ await ui("theme", "#1447E6");
117
+ ```
118
+
119
+ Without it, only baked-theme swaps and precomputed `{ light, dark }` objects work via `ui("theme", …)`.
120
+
121
+ ---
122
+
123
+ ## Quick start
16
124
 
17
125
  ```html
18
126
  <!doctype html>
19
- <html data-theme="stackific">
127
+ <html data-theme="stackific" data-mode="auto">
20
128
  <head>
129
+ <meta charset="UTF-8">
130
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
21
131
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stackific/md3/dist/md3.css">
22
132
  <script type="module" src="https://cdn.jsdelivr.net/npm/@stackific/md3/dist/md3.js"></script>
133
+ <title>Hello M3</title>
23
134
  </head>
24
135
  <body>
25
136
  <main class="responsive">
26
137
  <h1>Hello, M3</h1>
27
- <button>Click me</button>
28
- <div class="field label">
29
- <input type="text">
30
- <label>Your name</label>
31
- </div>
32
- <label class="switch">
33
- <input type="checkbox">
34
- <span></span>
35
- </label>
138
+ <nav>
139
+ <button>Primary</button>
140
+ <button class="border">Outlined</button>
141
+ <button class="transparent circle"><i>search</i></button>
142
+ </nav>
143
+ <article>
144
+ <h5>Card</h5>
145
+ <p>Drop md3 into a plain HTML page and write semantic markup.</p>
146
+ </article>
36
147
  </main>
37
148
  </body>
38
149
  </html>
@@ -40,235 +151,2462 @@ Paste into any HTML file:
40
151
 
41
152
  That's it. You should see an M3-styled page that picks light or dark automatically from your OS preference.
42
153
 
43
- ## Install via npm
154
+ ---
44
155
 
45
- ```sh
46
- npm install @stackific/md3
47
- # or: pnpm add @stackific/md3
48
- # or: yarn add @stackific/md3
156
+ ## Bootstrap
157
+
158
+ ```html
159
+ <html data-theme="stackific" data-mode="auto">
49
160
  ```
50
161
 
51
- In your app entry:
162
+ | Attribute | Values | Notes |
163
+ | ------------ | ---------------------------- | ------------------------------------------------------ |
164
+ | `data-theme` | any baked theme name | Pure attribute swap; no JS required for static pages. Omit to use the default (`stackific`). |
165
+ | `data-mode` | `auto` \| `light` \| `dark` | `auto` follows `prefers-color-scheme` in CSS (no JS). |
166
+ | `data-ui` | `#some-id` | On any element, clicking delegates to `ui("#some-id")`. Anchors without `href` also respond to Enter. |
52
167
 
53
- ```js
54
- import "@stackific/md3"; // runtime — registers window.ui()
55
- import "@stackific/md3/style"; // compiled CSS with M3 tokens + utilities
56
- ```
168
+ Mode is persisted to `localStorage["md3:mode"]`, theme to `localStorage["md3:theme"]`. The stored value beats the hardcoded `data-mode` / `data-theme` on `<html>`.
169
+
170
+ ---
171
+
172
+ ## Runtime API
57
173
 
58
- The CSS includes all `@font-face` declarations for Material Symbols, so icons render without any Google Fonts import.
174
+ A single global function `ui(...)` (also `import { ui } from "@stackific/md3"`). The runtime auto-rescans the DOM via a `MutationObserver`, so calling `ui()` after injecting markup is usually optional.
175
+
176
+ | Call | Effect |
177
+ | ----------------------------------- | --------------------------------------------------------------- |
178
+ | `ui()` | Re-scan the DOM (fields, sliders, ripples, progress, triggers). |
179
+ | `ui("guid")` | Returns a UUIDv4 string. |
180
+ | `ui("mode")` | Returns current mode: `"auto"` \| `"light"` \| `"dark"`. |
181
+ | `ui("mode", "dark")` | Set mode. Persists to `localStorage["md3:mode"]`. |
182
+ | `ui("themes")` | Returns array of baked theme names. |
183
+ | `ui("theme", "stackific")` | Swap baked theme. Persists to `localStorage["md3:theme"]`. |
184
+ | `await ui("theme", "#1447E6")` | Generate theme from hex. Requires `material-dynamic-colors`. |
185
+ | `await ui("theme", url \| File \| Blob)` | Generate theme from image / blob. Requires `material-dynamic-colors`. |
186
+ | `ui("theme", { light, dark })` | Apply precomputed CSS-string per mode. |
187
+ | `await ui("theme")` | Returns the currently-applied `{ light, dark }` token strings. |
188
+ | `ui("#my-dialog")` | Toggle a `<dialog>`. |
189
+ | `ui("#my-menu")` | Toggle a `<menu>`. |
190
+ | `ui("#my-snackbar")` | Show snackbar for 6000 ms. |
191
+ | `ui("#my-snackbar", 3000)` | Show snackbar for N ms. |
192
+ | `ui("#my-snackbar", -1)` | Show until clicked. |
193
+ | `ui("#my-page")` | Activate a `.page` (siblings deactivate). |
194
+ | `ui("#anything-else")` | Toggle `.active` on the target. |
195
+
196
+ `ui("#id")` toggles: calling it on an open element closes it, calling it on a closed one opens it.
197
+
198
+ **The JS file is almost optional.** Theme/mode swapping, dialogs, menus, snackbars, overlays, pages, and tabs all work through the CSS `.active` class plus `data-ui="#id"` clicks the browser handles natively. The runtime is required only for: slider value rendering, textarea auto-resize (on browsers without `field-sizing`), the file/color/password field wiring, and `await ui("theme", …)` MDC generation.
59
199
 
60
200
  ---
61
201
 
62
- ## Theme + mode
202
+ ## Main layout
63
203
 
64
- Two HTML attributes drive everything:
204
+ Any element that contains `<main>` becomes a grid with slots for edge `<nav>`s, `<header>`, `<main>`, and `<footer>`. You don't need to use every slot.
65
205
 
66
206
  ```html
67
- <html data-theme="stackific" data-mode="auto">
207
+ <nav class="left">...</nav>
208
+ <nav class="right">...</nav>
209
+ <nav class="top">...</nav>
210
+ <nav class="bottom">...</nav>
211
+ <header class="responsive | fixed">...</header>
212
+ <main class="responsive">...</main>
213
+ <footer class="responsive | fixed">...</footer>
68
214
  ```
69
215
 
70
- | Attribute | Values | Purpose |
71
- |---|---|---|
72
- | `data-theme` | any name listed in `$themes` | Which brand theme supplies the tokens |
73
- | `data-mode` | `"auto"`, `"light"`, `"dark"` | Color mode preference |
216
+ Grid composition:
74
217
 
75
- `data-mode="auto"` is a real attribute state — CSS handles the `auto → dark` switch via `@media (prefers-color-scheme: dark)`, so the page tracks your OS preference live. No JavaScript needed for that to work.
218
+ ```
219
+ nav.left | nav.top | nav.right
220
+ nav.left | header | nav.right
221
+ nav.left | main | nav.right
222
+ nav.left | footer | nav.right
223
+ nav.left | nav.bottom | nav.right
224
+ ```
76
225
 
77
- ### Switching at runtime
226
+ `.responsive` caps content at 75 rem and centers it. `.fixed` makes header / footer sticky. For RTL languages set `dir="rtl"` on `<body>` or any ancestor.
78
227
 
79
- ```js
80
- ui("mode", "dark"); // or "light", or "auto"
81
- document.documentElement.dataset.theme = "another"; // pure attribute swap, no deps
228
+ ### Compact (mobile)
229
+
230
+ ```html
231
+ <nav class="bottom">
232
+ <a><i>home</i><span>Home</span></a>
233
+ <a><i>search</i><span>Search</span></a>
234
+ <a><i>share</i><span>Share</span></a>
235
+ </nav>
236
+ <main class="responsive">
237
+ <h3>Compact</h3>
238
+ </main>
239
+ ```
240
+
241
+ ### Medium (tablet rail)
242
+
243
+ ```html
244
+ <nav class="left">
245
+ <a><i>home</i><span>Home</span></a>
246
+ <a><i>search</i><span>Search</span></a>
247
+ <a><i>share</i><span>Share</span></a>
248
+ </nav>
249
+ <main class="responsive">
250
+ <h3>Medium</h3>
251
+ </main>
82
252
  ```
83
253
 
84
- Mode is persisted to `localStorage` under the key `md3:mode` and restored on the next page load. Theme is persisted to `md3:theme`.
254
+ ### Expanded (desktop drawer)
255
+
256
+ ```html
257
+ <nav class="left max">
258
+ <a><i>home</i><span>Home</span></a>
259
+ <a><i>search</i><span>Search</span></a>
260
+ <a><i>share</i><span>Share</span></a>
261
+ </nav>
262
+ <main class="responsive">
263
+ <h3>Expanded</h3>
264
+ </main>
265
+ ```
85
266
 
86
- ### Listing available themes
267
+ ### Multi-pane
87
268
 
88
- ```js
89
- ui("themes"); // → ["stackific", "hello-pumpkin", "coral-bloom", ...]
269
+ ```html
270
+ <nav class="left">…</nav>
271
+ <main class="responsive">
272
+ <div class="grid">
273
+ <div class="s12 m6 l6"><h3>Pane 1</h3></div>
274
+ <div class="s12 m6 l6"><h3>Pane 2</h3></div>
275
+ </div>
276
+ </main>
90
277
  ```
91
278
 
92
- ### Adding a brand theme
279
+ ### Empty state
93
280
 
94
- If you've cloned the source, `pnpm theme add acme '#ff5722'` bakes a new theme from a single hex color, then `pnpm build` makes it reachable as `<html data-theme="acme">`. See [Local development](#local-development) below for the full workflow.
281
+ ```html
282
+ <div class="fill medium-height middle-align center-align">
283
+ <div class="center-align">
284
+ <i class="extra">mail</i>
285
+ <h5>You have no new messages</h5>
286
+ <p>Click the button to start a conversation</p>
287
+ <div class="space"></div>
288
+ <nav class="center-align">
289
+ <button class="round">Send a message</button>
290
+ </nav>
291
+ </div>
292
+ </div>
293
+ ```
95
294
 
96
295
  ---
97
296
 
98
- ## Customize tokens
297
+ ## Components
99
298
 
100
- All M3 design tokens are CSS custom properties. Override at any scope — no rebuild required.
299
+ ### Badge
101
300
 
102
- ```css
103
- /* whole site */
104
- :root {
105
- --primary: #ff5722;
106
- --on-primary: #ffffff;
107
- --primary-container: #ffe0d6;
108
- }
301
+ Corner indicators placed inside another element. Default error-colored.
109
302
 
110
- /* one card */
111
- article.alert {
112
- --surface-container-low: #fff3e0;
113
- }
303
+ ```html
304
+ <button class="circle">
305
+ <i>mail</i>
306
+ <div class="badge">3</div>
307
+ </button>
308
+
309
+ <button class="chip circle">
310
+ <i>mail</i>
311
+ <div class="badge">3</div>
312
+ </button>
114
313
  ```
115
314
 
116
- Common tweaks:
315
+ | Class | Result |
316
+ | -------------------------------------------------------------------- | ------------------------------------- |
317
+ | _(none)_ | Top-right of parent, `error` colors. |
318
+ | `.top` / `.bottom` / `.left` / `.right` | Corner override. |
319
+ | `.fill` / `.primary` / `.secondary` / `.tertiary` | Role-colored background. |
320
+ | `.border` | Outlined on surface background. |
321
+ | `.circle` / `.square` / `.round` / `.no-round` / corner-round classes | Shape variants. |
322
+ | `.min` | Dot only (no text). |
323
+ | `.none` | Inline (not absolutely positioned). |
117
324
 
118
- ```css
119
- :root {
120
- --size: 1.1rem; /* 10% larger everywhere */
121
- --font: "Inter Variable", system-ui; /* body font */
122
- --font-icon: "Material Symbols Rounded"; /* see icon styles below */
123
- }
325
+ ### Button
326
+
327
+ ```html
328
+ <button>Button</button>
329
+ <a class="button">Link button</a>
330
+
331
+ <button>
332
+ <i>home</i>
333
+ <span>Button</span>
334
+ </button>
124
335
  ```
125
336
 
126
- ### Full token list
337
+ `<button>` and `<a class="button">` are interchangeable. Variants combine on the same element.
338
+
339
+ | Class | Result |
340
+ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
341
+ | _(none)_ | Filled with `primary`, 2.5 rem high, fully-rounded. |
342
+ | `.small` / `.large` / `.extra` | 2 / 3 / 3.5 rem high. |
343
+ | `.border` | Outlined; `primary-text` on transparent background. |
344
+ | `.fill` | `secondary-container` background. |
345
+ | `.primary` / `.secondary` / `.tertiary` | Role-colored background. |
346
+ | `.transparent` | Drops background and box-shadow; for icon buttons. |
347
+ | `.square` / `.circle` | No padding; equal block/inline size. |
348
+ | `.round` / `.no-round` / `.small-round` / `.large-round` | Corner-radius scale. |
349
+ | `.left-round` / `.right-round` / `.top-round` / `.bottom-round` | Per-corner radius. |
350
+ | `.responsive` | Fills the container's inline-size. |
351
+ | `.horizontal` / `.vertical` | Row vs column flow of children. |
352
+ | `.extend` | Collapses to icon; expands label on hover or `.active`. |
353
+ | `.active` | `primary-container` background. |
354
+ | `[disabled]` | 50% opacity, no pointer-events. |
355
+
356
+ #### Responsive button
357
+
358
+ Stretches to fill its container.
127
359
 
128
- | Group | Tokens |
129
- |---|---|
130
- | Primary | `--primary`, `--on-primary`, `--primary-container`, `--on-primary-container` |
131
- | Secondary | `--secondary`, `--on-secondary`, `--secondary-container`, `--on-secondary-container` |
132
- | Tertiary | `--tertiary`, `--on-tertiary`, `--tertiary-container`, `--on-tertiary-container` |
133
- | Error | `--error`, `--on-error`, `--error-container`, `--on-error-container` |
134
- | Neutral | `--background`, `--on-background`, `--surface`, `--on-surface`, `--surface-variant`, `--on-surface-variant`, `--outline`, `--outline-variant`, `--shadow`, `--scrim` |
135
- | Surface tonal | `--surface-dim`, `--surface-bright`, `--surface-container-lowest`, `--surface-container-low`, `--surface-container`, `--surface-container-high`, `--surface-container-highest` |
136
- | Inverse | `--inverse-surface`, `--inverse-on-surface`, `--inverse-primary` |
137
- | Structural | `--size`, `--font`, `--font-icon`, `--speed1`..`--speed4`, `--active`, `--overlay`, `--elevate1`..`--elevate3`, `--top`, `--bottom`, `--left`, `--right`, `--image` |
360
+ ```html
361
+ <button class="responsive">Button</button>
138
362
 
139
- ### Icon styles
363
+ <button class="responsive">
364
+ <i>home</i>
365
+ <span>Button</span>
366
+ </button>
367
+ ```
140
368
 
141
- Four Material Symbols variants are pre-bundled in `dist/fonts/`. Switch by changing one variable:
369
+ #### FAB
142
370
 
143
- ```css
144
- :root {
145
- --font-icon: "Material Symbols Rounded";
146
- /* "Material Symbols Outlined" (default)
147
- "Material Symbols Rounded"
148
- "Material Symbols Sharp"
149
- "Material Symbols Subset" (smaller; common icons only) */
150
- }
371
+ Floating Action Button — the primary action of a screen.
372
+
373
+ ```html
374
+ <button class="circle extra">
375
+ <i>add</i>
376
+ </button>
377
+
378
+ <button class="square extra">
379
+ <i>add</i>
380
+ </button>
381
+
382
+ <button class="circle extra left-round top-round">
383
+ <i>add</i>
384
+ </button>
151
385
  ```
152
386
 
153
- ---
387
+ #### Extended FAB
154
388
 
155
- ## Runtime API
389
+ Wider FAB with a text label.
390
+
391
+ ```html
392
+ <button class="extend circle">
393
+ <i>add</i>
394
+ <span>Create</span>
395
+ </button>
396
+
397
+ <button class="extend square">
398
+ <i>add</i>
399
+ <span>Create</span>
400
+ </button>
401
+ ```
402
+
403
+ #### FAB menu
404
+
405
+ A FAB that opens a menu of related actions.
406
+
407
+ ```html
408
+ <div>
409
+ <button class="circle extra">
410
+ <i>more_horiz</i>
411
+ </button>
412
+ <menu class="group no-wrap small-space top">
413
+ <li><button class="fill"><i>search</i><span>Search</span></button></li>
414
+ <li><button class="fill"><i>home</i><span>Home</span></button></li>
415
+ <li><button class="fill"><i>more_vert</i><span>About</span></button></li>
416
+ </menu>
417
+ </div>
418
+ ```
419
+
420
+ #### Icon button
421
+
422
+ A transparent button shaped only on hover/press.
423
+
424
+ ```html
425
+ <button class="transparent circle">
426
+ <i>search</i>
427
+ </button>
428
+
429
+ <button class="transparent circle">
430
+ <img class="responsive" src="/avatar.png">
431
+ </button>
432
+ ```
433
+
434
+ #### Button groups
435
+
436
+ Standard groups, connected groups, and split buttons. Children should use directional corner rounds.
437
+
438
+ ```html
439
+ <nav class="group">
440
+ <button class="left-round">Left</button>
441
+ <button class="no-round">Center</button>
442
+ <button class="right-round">Right</button>
443
+ </nav>
444
+
445
+ <nav class="group connected">
446
+ <button class="left-round">Left</button>
447
+ <button class="no-round">Center</button>
448
+ <button class="right-round">Right</button>
449
+ </nav>
450
+
451
+ <nav class="group split">
452
+ <button class="left-round">
453
+ <i>add_circle</i>
454
+ <span>Action</span>
455
+ </button>
456
+ <button class="right-round square">
457
+ <i>keyboard_arrow_down</i>
458
+ </button>
459
+ </nav>
460
+ ```
461
+
462
+ Add `.secondary` / `.tertiary` on `nav.group.split` to colorize the group.
463
+
464
+ ### Card
465
+
466
+ ```html
467
+ <article>
468
+ <h5>Title</h5>
469
+ <p>Description</p>
470
+ <nav>
471
+ <button>Action</button>
472
+ </nav>
473
+ </article>
474
+ ```
475
+
476
+ | Class | Result |
477
+ | ------------------------------------------------------------------------------------------- | ----------------------------------- |
478
+ | _(none)_ | Elevated, `surface-container-low`. |
479
+ | `.border` | No shadow, outline border. |
480
+ | `.fill` / `.primary-container` / `.secondary-container` / `.tertiary-container` | Role-colored background. |
481
+ | `.small` / `.medium` / `.large` | 12 / 20 / 32 rem block-size. |
482
+ | `.round` / `.no-round` / `.left-round` / `.right-round` / `.top-round` / `.bottom-round` | Corner radius. |
483
+ | `.padding` / `.no-padding` / `.tiny-padding` / `.small-padding` / `.medium-padding` / `.large-padding` | Inner padding override. |
484
+ | `.elevate` / `.no-elevate` / `.small-elevate` / `.medium-elevate` / `.large-elevate` | Shadow override. |
485
+
486
+ #### Card with leading media
487
+
488
+ ```html
489
+ <article>
490
+ <div class="row">
491
+ <img class="circle large" src="/avatar.png">
492
+ <div class="max">
493
+ <h5>Title</h5>
494
+ <div>Supporting text</div>
495
+ </div>
496
+ </div>
497
+ <nav>
498
+ <button>Action</button>
499
+ </nav>
500
+ </article>
501
+ ```
502
+
503
+ #### Card with hero media
504
+
505
+ ```html
506
+ <article class="no-padding">
507
+ <img class="responsive medium" src="/hero.jpg">
508
+ <div class="row absolute bottom left right padding bottom-shadow">
509
+ <h5>Title</h5>
510
+ <div class="max"></div>
511
+ <button class="circle transparent"><i>more_vert</i></button>
512
+ </div>
513
+ </article>
514
+ ```
515
+
516
+ #### Side-by-side card
517
+
518
+ ```html
519
+ <article class="no-padding">
520
+ <div class="grid no-space">
521
+ <div class="s6"><img class="responsive" src="/photo.jpg"></div>
522
+ <div class="s6">
523
+ <div class="padding">
524
+ <h5>Title</h5>
525
+ <p>Description</p>
526
+ <nav><button class="border round">Action</button></nav>
527
+ </div>
528
+ </div>
529
+ </div>
530
+ </article>
531
+ ```
532
+
533
+ ### Checkbox
534
+
535
+ ```html
536
+ <label class="checkbox">
537
+ <input type="checkbox">
538
+ <span></span>
539
+ </label>
540
+
541
+ <label class="checkbox">
542
+ <input type="checkbox">
543
+ <span>Click here</span>
544
+ </label>
545
+ ```
546
+
547
+ Sizes: `.small`, _(default)_, `.large`, `.extra`.
548
+
549
+ #### With icons
156
550
 
157
- A single global function `ui()` (or named import `ui`) handles everything.
551
+ ```html
552
+ <label class="checkbox icon">
553
+ <input type="checkbox">
554
+ <span>
555
+ <i>close</i>
556
+ <i>done</i>
557
+ </span>
558
+ </label>
559
+ ```
560
+
561
+ #### Inside a field
562
+
563
+ ```html
564
+ <div class="field middle-align">
565
+ <nav>
566
+ <label class="checkbox"><input type="checkbox"><span>Item 1</span></label>
567
+ <label class="checkbox"><input type="checkbox"><span>Item 2</span></label>
568
+ <label class="checkbox"><input type="checkbox"><span>Item 3</span></label>
569
+ </nav>
570
+ <output>Helper text</output>
571
+ </div>
572
+ ```
158
573
 
159
- ### Mode + theme
574
+ #### Indeterminate state
575
+
576
+ Set via JavaScript — there is no HTML attribute for this:
160
577
 
161
578
  ```js
162
- ui("mode"); // "auto" | "light" | "dark"
163
- ui("mode", "dark"); // set mode; persisted to localStorage
164
- ui("themes"); // → ["stackific", ...] list of baked themes
165
- ui("theme", "stackific"); // swap to a baked theme (no extra deps)
166
- await ui("theme", "#1447E6"); // generate a theme from a hex — needs MDC
579
+ document.getElementById("my-checkbox").indeterminate = true;
580
+ ```
581
+
582
+ ### Chip
583
+
584
+ ```html
585
+ <button class="chip">Chip</button>
586
+
587
+ <button class="chip">
588
+ <i>home</i>
589
+ <span>Chip</span>
590
+ </button>
591
+
592
+ <a class="chip">Link chip</a>
593
+ ```
594
+
595
+ `<button class="chip">` and `<a class="chip">` are interchangeable.
596
+
597
+ | Class | Result |
598
+ | ------------------------------------------------------------------------ | ------------------------------- |
599
+ | _(none)_ | 2 rem, outlined. |
600
+ | `.small` / `.medium` / `.large` | 2 / 2.5 / 3 rem. |
601
+ | `.fill` | Filled (no border). |
602
+ | `.primary` / `.secondary` / `.tertiary` | Role-colored background. |
603
+ | `.border` / `.no-border` | Border control. |
604
+ | `.circle` / `.square` | Equal block/inline size. |
605
+ | `.round` / `.no-round` / corner-round classes | Corner radius. |
606
+ | `.horizontal` / `.vertical` | Child flow. |
607
+
608
+ Canonical chip patterns:
609
+
610
+ ```html
611
+ <!-- Suggestion -->
612
+ <button class="chip">Suggestion</button>
613
+
614
+ <!-- Input (removable) -->
615
+ <button class="chip">
616
+ <span>Input</span>
617
+ <i>close</i>
618
+ </button>
619
+
620
+ <!-- Filter -->
621
+ <button class="chip">
622
+ <i>done</i>
623
+ <span>Filter</span>
624
+ </button>
625
+
626
+ <!-- Assist with leading icon -->
627
+ <button class="chip">
628
+ <i class="primary-text">today</i>
629
+ <span>Assist</span>
630
+ </button>
631
+
632
+ <!-- With image -->
633
+ <button class="chip">
634
+ <img src="/favicon.png">
635
+ <span>Image</span>
636
+ </button>
637
+ ```
638
+
639
+ ### Container
640
+
641
+ The main content of a page.
642
+
643
+ ```html
644
+ <main class="responsive">…</main>
167
645
  ```
168
646
 
169
- `ui("theme", source)` accepts a baked theme name, hex string, image URL, `File`, or a pre-computed `{ light, dark }` object. **Only the hex/URL/File forms require [`material-dynamic-colors`](https://www.npmjs.com/package/material-dynamic-colors)** to be loaded in the page:
647
+ `.responsive` caps inline-size at 75 rem and centers; `.max` removes the cap.
648
+
649
+ ### Dialog
650
+
651
+ ```html
652
+ <dialog>
653
+ <h5>Title</h5>
654
+ <p>Content of dialog</p>
655
+ <nav class="right-align no-space">
656
+ <button class="transparent link">Cancel</button>
657
+ <button class="transparent link">Confirm</button>
658
+ </nav>
659
+ </dialog>
660
+ ```
661
+
662
+ | Class | Result |
663
+ | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
664
+ | _(none)_ | Centered modal, 80% max block-size. |
665
+ | `.small` / `.medium` / `.large` | 25% / 50% / 75%. |
666
+ | `.left` / `.right` | Slides in from the side, full height. With `.small` 20 rem / `.medium` 32 rem / `.large` 44 rem. |
667
+ | `.top` / `.bottom` | Slides in vertically, full width. With `.small` 16 rem / `.medium` 24 rem / `.large` 32 rem. |
668
+ | `.max` | Full-screen. |
669
+ | `.modal` | Opens via native `showModal()` (native backdrop, Escape, scroll lock). |
670
+ | `.fill` / `.primary-container` / `.secondary-container` / `.tertiary-container` | Role-colored background. |
671
+ | `.border` / `.round` / `.no-round` / corner-round classes | Border and corner radius. |
672
+ | `.padding` / `.no-padding` / `.tiny-padding` / `.small-padding` / `.medium-padding` / `.large-padding` | Padding override. |
673
+ | `.elevate` / `.no-elevate` / `.small-elevate` / `.medium-elevate` / `.large-elevate` | Shadow override. |
674
+ | `.active` | Open state (also `[open]` from native API). |
675
+
676
+ #### Opening and closing — 5 methods
677
+
678
+ **1. Add/remove `active` class**
679
+
680
+ ```html
681
+ <dialog class="active">…</dialog>
682
+ ```
683
+
684
+ **2. Native HTML `<dialog>` API**
170
685
 
171
686
  ```js
172
- import "material-dynamic-colors"; // once, at app entry
173
- await ui("theme", "#1447E6"); // now works
687
+ document.querySelector("#dialog").show(); // non-modal
688
+ document.querySelector("#dialog").showModal(); // modal
689
+ document.querySelector("#dialog").close();
174
690
  ```
175
691
 
176
- The framework itself does not bundle MDC — install and import it yourself when you need dynamic generation.
692
+ **3. `data-ui` attribute**
693
+
694
+ ```html
695
+ <button data-ui="#dialog">Open</button>
696
+
697
+ <dialog id="dialog">
698
+ <h5>Title</h5>
699
+ <nav class="right-align no-space">
700
+ <button data-ui="#dialog">Cancel</button>
701
+ <button data-ui="#dialog">Confirm</button>
702
+ </nav>
703
+ </dialog>
704
+ ```
177
705
 
178
- ### Component triggers
706
+ **4. `ui()` call**
179
707
 
180
708
  ```js
181
- ui("#my-dialog"); // toggle a <dialog>
182
- ui("#my-snackbar"); // show snackbar for 6s
183
- ui("#my-snackbar", 3000); // show for 3s
184
- ui("#my-snackbar", -1); // show until clicked
185
- ui("#my-page"); // activate a .page (tab-style content)
709
+ ui("#dialog");
186
710
  ```
187
711
 
188
- These are equivalent to clicking an element with `[data-ui="#id"]`.
712
+ **5. Popover API**
713
+
714
+ ```html
715
+ <button popovertarget="dialog">Open</button>
189
716
 
190
- ### Utilities
717
+ <dialog id="dialog" popover>
718
+ <h5>Title</h5>
719
+ </dialog>
720
+ ```
191
721
 
192
722
  ```js
193
- ui(); // re-scan the DOM (call after dynamically injecting markup)
194
- ui("guid"); // → UUID-v4 string, handy for unique input ids
723
+ document.querySelector("#dialog").showPopover();
724
+ document.querySelector("#dialog").hidePopover();
725
+ document.querySelector("#dialog").togglePopover();
195
726
  ```
196
727
 
197
- ---
728
+ #### Custom overlay
198
729
 
199
- ## Local development
730
+ Place an `.overlay` sibling immediately before the dialog; modifier classes on the overlay are honored:
731
+
732
+ ```html
733
+ <div class="overlay blur"></div>
734
+ <dialog id="dialog">…</dialog>
735
+ ```
200
736
 
201
- These commands are for hacking on the framework itself, not for consumers of the published package.
737
+ #### Navigation drawer
202
738
 
203
- ```sh
204
- git clone https://github.com/stackific/md3
205
- cd md3
206
- pnpm install
207
- pnpm build # → dist/md3.css + dist/md3.js
208
- pnpm demo # build + start the demo Vue app at http://localhost:5001
739
+ ```html
740
+ <div class="overlay"></div>
741
+ <dialog id="drawer" class="left">
742
+ <header>
743
+ <nav>
744
+ <img class="circle large" src="/avatar.png">
745
+ <h6 class="max">Title</h6>
746
+ <button class="transparent circle large" data-ui="#drawer">
747
+ <i>close</i>
748
+ </button>
749
+ </nav>
750
+ </header>
751
+ <ul class="list">
752
+ <li class="wave round"><i>inbox</i><span class="max">Inbox</span><b>24</b></li>
753
+ <li class="wave round"><i>send</i><span>Outbox</span></li>
754
+ <li class="wave round"><i>favorite</i><span>Favorites</span></li>
755
+ <li class="wave round"><i>delete</i><span>Trash</span></li>
756
+ </ul>
757
+ </dialog>
209
758
  ```
210
759
 
211
- ### Adding or removing a brand theme
760
+ ### Divider
212
761
 
213
- ```sh
214
- pnpm theme add acme '#ff5722' # quote the hex
215
- pnpm theme remove acme
216
- pnpm build # re-emit dist/
762
+ ```html
763
+ <hr>
764
+ <hr class="small">
765
+ <hr class="medium">
766
+ <hr class="large">
767
+
768
+ <div class="divider"></div>
769
+ <div class="divider vertical"></div>
770
+ ```
771
+
772
+ `.small` / `.medium` / `.large` control vertical margin (0.5 / 1 / 1.5 rem). `.vertical` produces a vertical rule.
773
+
774
+ ### Expansion
775
+
776
+ Use native `<details>` / `<summary>`.
777
+
778
+ ```html
779
+ <details>
780
+ <summary>Header</summary>
781
+ <p>Body content</p>
782
+ </details>
217
783
  ```
218
784
 
219
- `add` derives the M3 light + dark token sets and a 10-step tonal palette from the source color, then writes both:
785
+ Multi-level:
220
786
 
221
- - `src/styles/_config.scss` → upserts the entry in `$material-palette` so `.acme`, `.acme1`..`.acme10`, `.acme-text`, `.acme-border` utility classes become available.
222
- - `src/styles/settings/_themes.scss` → upserts a named block in `$themes` so `<html data-theme="acme">` activates it.
787
+ ```html
788
+ <details>
789
+ <summary>Level 1</summary>
790
+ <details>
791
+ <summary>Level 2</summary>
792
+ <details>
793
+ <summary>Level 3</summary>
794
+ <p>Body</p>
795
+ </details>
796
+ </details>
797
+ </details>
798
+ ```
799
+
800
+ Custom summary (hides the marker):
801
+
802
+ ```html
803
+ <details>
804
+ <summary class="none">
805
+ <button>Custom trigger</button>
806
+ </summary>
807
+ <p>Body</p>
808
+ </details>
809
+ ```
810
+
811
+ ### Field
812
+
813
+ Wrapper for inputs, selects, textareas, and composite controls.
814
+
815
+ ```html
816
+ <div class="field border">
817
+ <input type="text">
818
+ </div>
819
+ ```
820
+
821
+ `.label` is optional; with it, the label floats above the input when focused or filled.
822
+
823
+ ```html
824
+ <div class="field label border">
825
+ <input type="text" placeholder=" ">
826
+ <label>Label</label>
827
+ </div>
828
+ ```
829
+
830
+ | Class | Result |
831
+ | ------------------------------------------- | ------------------------------------------------- |
832
+ | `.label` | Adds a floating label child. |
833
+ | `.border` | Outlined. |
834
+ | `.fill` | Filled (`surface-variant`). |
835
+ | `.round` | Pill shape. |
836
+ | `.small` / _(default)_ / `.large` / `.extra` | 2.5 / 3 / 3.5 / 4 rem input height. |
837
+ | `.prefix` | Extra start-padding for a leading icon child. |
838
+ | `.suffix` | Extra end-padding for a trailing icon child. |
839
+ | `.invalid` | Error coloring; shows `<output class="invalid">`. |
840
+
841
+ #### Input
842
+
843
+ ```html
844
+ <div class="field border">
845
+ <input type="text">
846
+ </div>
847
+
848
+ <div class="field border">
849
+ <input type="text">
850
+ <output>Helper text</output>
851
+ </div>
852
+
853
+ <div class="field invalid border">
854
+ <input type="text">
855
+ <output class="invalid">Error text</output>
856
+ </div>
857
+
858
+ <div class="field label border">
859
+ <input type="text" placeholder=" ">
860
+ <label>Label</label>
861
+ </div>
862
+
863
+ <div class="field label prefix border">
864
+ <i>search</i>
865
+ <input type="text">
866
+ <label>Label</label>
867
+ </div>
868
+
869
+ <div class="field label suffix border">
870
+ <input type="text">
871
+ <label>Label</label>
872
+ <i>visibility</i>
873
+ </div>
874
+
875
+ <div class="field label prefix suffix border">
876
+ <i>search</i>
877
+ <input type="text">
878
+ <label>Label</label>
879
+ <i>tune</i>
880
+ </div>
881
+ ```
882
+
883
+ Clickable prefix/suffix icons — wrap in `<a>` or use `<i class="front">`:
884
+
885
+ ```html
886
+ <div class="field label prefix border">
887
+ <a><i>search</i></a>
888
+ <input type="text">
889
+ <label>Label</label>
890
+ </div>
891
+
892
+ <div class="field label prefix border">
893
+ <i class="front">search</i>
894
+ <input type="text">
895
+ <label>Label</label>
896
+ </div>
897
+ ```
898
+
899
+ Floating-label triggers — either add `placeholder=" "` (pure CSS) or let the runtime toggle `.active`:
900
+
901
+ ```html
902
+ <!-- CSS-only -->
903
+ <div class="field label border">
904
+ <input type="text" placeholder=" ">
905
+ <label>Label</label>
906
+ </div>
907
+
908
+ <!-- Manual control -->
909
+ <div class="field label border">
910
+ <input type="text" class="active">
911
+ <label class="active">Label</label>
912
+ </div>
913
+ ```
914
+
915
+ Password fields auto-wire a visibility toggle when a child `<i>visibility</i>` is present:
916
+
917
+ ```html
918
+ <div class="field label suffix border">
919
+ <input type="password">
920
+ <label>Password</label>
921
+ <i>visibility</i>
922
+ </div>
923
+ ```
223
924
 
224
- `remove` strips both.
925
+ #### Select
225
926
 
226
- ### Editing tokens directly
927
+ ```html
928
+ <div class="field suffix border">
929
+ <select>
930
+ <option>Item 1</option>
931
+ <option>Item 2</option>
932
+ <option>Item 3</option>
933
+ </select>
934
+ <i>arrow_drop_down</i>
935
+ </div>
936
+
937
+ <div class="field label suffix border">
938
+ <select>
939
+ <option>Item 1</option>
940
+ <option>Item 2</option>
941
+ </select>
942
+ <label>Label</label>
943
+ <i>arrow_drop_down</i>
944
+ <output>Helper text</output>
945
+ </div>
946
+ ```
947
+
948
+ #### Textarea
949
+
950
+ Auto-resizes unless `[rows]` is set.
951
+
952
+ ```html
953
+ <div class="field border">
954
+ <textarea></textarea>
955
+ </div>
956
+
957
+ <div class="field border">
958
+ <textarea rows="10"></textarea>
959
+ </div>
960
+
961
+ <div class="field label border">
962
+ <textarea placeholder=" "></textarea>
963
+ <label>Label</label>
964
+ </div>
965
+ ```
966
+
967
+ #### Search
968
+
969
+ Search field with auto-suggestion menu.
970
+
971
+ ```html
972
+ <div class="field large prefix round fill">
973
+ <i class="front">search</i>
974
+ <input>
975
+ <menu class="min">
976
+ <li class="transparent">
977
+ <div class="field large prefix">
978
+ <i class="front">arrow_back</i>
979
+ <input>
980
+ </div>
981
+ </li>
982
+ <li><i>history</i><div>Recent 1</div></li>
983
+ <li><i>history</i><div>Recent 2</div></li>
984
+ <li><i>history</i><div>Recent 3</div></li>
985
+ </menu>
986
+ </div>
987
+ ```
988
+
989
+ FAB-anchored search:
990
+
991
+ ```html
992
+ <div>
993
+ <button class="extra circle fill">
994
+ <i>search</i>
995
+ </button>
996
+ <menu class="no-wrap left min">
997
+ <li class="transparent">
998
+ <div class="field large prefix">
999
+ <i class="front">arrow_back</i>
1000
+ <input>
1001
+ </div>
1002
+ </li>
1003
+ <li><i>history</i><div>Recent 1</div></li>
1004
+ </menu>
1005
+ </div>
1006
+ ```
1007
+
1008
+ #### Custom inputs (file, color, date, time)
1009
+
1010
+ Wrap a hidden input in a button trigger; the runtime mirrors values back into a sibling text input.
1011
+
1012
+ ```html
1013
+ <div>
1014
+ <button class="circle"><i>attach_file</i></button>
1015
+ <input type="file">
1016
+ </div>
1017
+
1018
+ <div>
1019
+ <button class="circle"><i>palette</i></button>
1020
+ <input type="color">
1021
+ </div>
1022
+
1023
+ <div>
1024
+ <button class="circle"><i>today</i></button>
1025
+ <input type="date">
1026
+ </div>
1027
+
1028
+ <div>
1029
+ <button class="circle"><i>schedule</i></button>
1030
+ <input type="time">
1031
+ </div>
1032
+
1033
+ <!-- With label -->
1034
+ <div>
1035
+ <button><i>attach_file</i><span>File</span></button>
1036
+ <input type="file">
1037
+ </div>
1038
+ ```
1039
+
1040
+ ### Grid
227
1041
 
228
- If you'd rather edit token values by hand instead of running `pnpm theme add`, the shape of `$themes` is:
1042
+ 12-column grid with 1 rem gap. Cell wrappers live immediately inside `.grid` don't put `.grid` and `.s1`–`.s12` on the same element.
229
1043
 
230
- ```scss
231
- $themes: (
232
- "stackific": (
233
- "light": ( "primary": #1c4bea, "on-primary": #ffffff, /* ... */ ),
234
- "dark": ( "primary": #b9c3ff, /* ... */ ),
235
- ),
236
- );
1044
+ ```html
1045
+ <div class="grid">
1046
+ <div class="s12 m6 l3"><h5>First</h5></div>
1047
+ <div class="s12 m6 l3"><h5>Second</h5></div>
1048
+ <div class="s12 m6 l3"><h5>Third</h5></div>
1049
+ <div class="s12 m6 l3"><h5>Fourth</h5></div>
1050
+ </div>
237
1051
  ```
238
1052
 
239
- Token keys are kebab-cased and match the CSS custom-property names verbatim.
1053
+ | Class | Active |
1054
+ | ---------------- | ------------------ |
1055
+ | `.s1` … `.s12` | Always. |
1056
+ | `.m1` … `.m12` | ≥ 601 px. |
1057
+ | `.l1` … `.l12` | ≥ 993 px. |
240
1058
 
241
- ### Configure which roles get utility classes
1059
+ Gap modifiers on `.grid`: `.no-space`, `.space`, `.small-space`, `.medium-space` (1.5 rem), `.large-space` (2 rem).
242
1060
 
243
- `src/styles/_config.scss` controls which M3 roles generate `.role`, `.role-text`, `.role-border`, `.role-container`, and `nav.role`/`menu.role` active-state recoloring:
1061
+ **Do** put component elements inside the cell:
244
1062
 
245
- ```scss
246
- $theme-roles-paired: ("primary", "secondary", "tertiary", "error", "background");
247
- $theme-roles-with-edges: ("primary", "secondary", "tertiary", "error");
248
- $theme-roles-container: ("primary", "secondary", "tertiary", "error");
249
- $theme-roles-nav-active: ("primary", "secondary", "tertiary");
1063
+ ```html
1064
+ <div class="grid">
1065
+ <div class="s12 m6 l3"><article>…</article></div>
1066
+ <div class="s12 m6 l3"><div class="field">…</div></div>
1067
+ </div>
250
1068
  ```
251
1069
 
252
- ### Other things you can edit in SCSS
1070
+ 🚫 **Don't** put grid classes directly on the element:
253
1071
 
254
- | File | What's there |
255
- |---|---|
256
- | `src/styles/_config.scss` | `$material-palette`, role lists, `$breakpoints`, mixins |
257
- | `src/styles/settings/_themes.scss` | `$themes` (light/dark tokens per brand) |
258
- | `src/styles/settings/_fonts.scss` | Material Symbols `@font-face` declarations |
259
- | `src/styles/elements/_*.scss` | Per-component scales (`$-button-sizes`, `$-chip-sizes`, etc.) |
260
- | `src/styles/elements/_shapes.scss` | `$-shapes` list of SVG-backed shape utilities |
1072
+ ```html
1073
+ <div class="grid">
1074
+ <article class="s12 m6 l3">…</article> <!-- breaks layout -->
1075
+ </div>
1076
+ ```
261
1077
 
262
- ## Publish
1078
+ ### Header & Footer
263
1079
 
264
- ```bash
265
- pnpm prepublishOnly
266
- npm publish --access public --dry-run
267
- npm publish --access public
1080
+ ```html
1081
+ <header>
1082
+ <nav>
1083
+ <button class="circle transparent"><i>arrow_back</i></button>
1084
+ <h5 class="max">Title</h5>
1085
+ <button class="circle transparent"><i>more_vert</i></button>
1086
+ </nav>
1087
+ </header>
1088
+
1089
+ <footer>
1090
+ <nav>
1091
+ <button class="circle transparent"><i>check_box</i></button>
1092
+ <button class="circle transparent"><i>brush</i></button>
1093
+ <div class="max"></div>
1094
+ <button class="square round extra primary"><i>add</i></button>
1095
+ </nav>
1096
+ </footer>
268
1097
  ```
269
1098
 
1099
+ | Class | Result |
1100
+ | ---------------------------------- | ------------------------------------------------------------- |
1101
+ | _(none)_ | 4 rem min (header), 5 rem min (footer). |
1102
+ | `.fixed` | Sticky inside the container. |
1103
+ | `.responsive` | Caps content at 75 rem and centers it. |
1104
+ | `.max` | Removes the 75 rem cap. |
1105
+
1106
+ When `.fixed` and a scrollable parent are used together, header/footer stick to the parent's top/bottom edge:
1107
+
1108
+ ```html
1109
+ <article class="small-width small-height scroll">
1110
+ <header class="fixed bold">Fixed header</header>
1111
+ <p>Long body content…</p>
1112
+ <p>…that scrolls underneath.</p>
1113
+ <footer class="fixed bold">Fixed footer</footer>
1114
+ </article>
1115
+ ```
1116
+
1117
+ ### Icon
1118
+
1119
+ Material Symbols ligature. Four font variants ship inside the CSS bundle.
1120
+
1121
+ ```html
1122
+ <i>home</i>
1123
+
1124
+ <i>
1125
+ <svg viewBox="0 0 24 24">
1126
+ <path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"></path>
1127
+ </svg>
1128
+ </i>
1129
+
1130
+ <i>
1131
+ <img src="/icon.png">
1132
+ </i>
1133
+ ```
1134
+
1135
+ | Class | Size |
1136
+ | -------- | -------- |
1137
+ | `.tiny` | 1 rem |
1138
+ | `.small` | 1.25 rem |
1139
+ | _(none)_ | 1.5 rem |
1140
+ | `.large` | 1.75 rem |
1141
+ | `.extra` | 2 rem |
1142
+ | `.fill` | Filled (`FILL=1`) variation. Also auto-applied inside `<a class="active">` / `<button class="active">`. |
1143
+
1144
+ Switch font variant via `--font-icon`:
1145
+
1146
+ ```css
1147
+ :root { --font-icon: "Material Symbols Rounded"; }
1148
+ /* "Material Symbols Outlined" (default), "Material Symbols Rounded",
1149
+ "Material Symbols Sharp", "Material Symbols Subset" (smaller bundle), or none */
1150
+ ```
1151
+
1152
+ Sharing one SVG sprite across multiple icons:
1153
+
1154
+ ```html
1155
+ <svg viewBox="0 0 24 24" style="display: none">
1156
+ <g id="home"><path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"/></g>
1157
+ <g id="star"><path d="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"/></g>
1158
+ </svg>
1159
+
1160
+ <i><svg viewBox="0 0 24 24"><use href="#home"/></svg></i>
1161
+ <i><svg viewBox="0 0 24 24"><use href="#star"/></svg></i>
1162
+ ```
1163
+
1164
+ Third-party icon fonts work too (Font Awesome, MDI, …) — load them yourself:
1165
+
1166
+ ```html
1167
+ <i class="fa-regular fa-clock"></i>
1168
+ <i class="mdi mdi-clock-outline"></i>
1169
+ ```
1170
+
1171
+ ### Layout
1172
+
1173
+ Positioning containers — absolute (relative to parent) or fixed (relative to viewport).
1174
+
1175
+ ```html
1176
+ <article class="small">
1177
+ <div class="absolute left bottom right">
1178
+ <h5>Pinned to container bottom</h5>
1179
+ </div>
1180
+ </article>
1181
+
1182
+ <div class="fixed left bottom right">
1183
+ <h5>Pinned to viewport bottom</h5>
1184
+ </div>
1185
+ ```
1186
+
1187
+ | Class | Effect |
1188
+ | -------------------------------------------------------------- | ----------------------------------------------- |
1189
+ | `.absolute` / `.fixed` | Positioning context. |
1190
+ | `.left` / `.right` / `.top` / `.bottom` | Pin to that edge. |
1191
+ | `.front` / `.back` | z-index +10 / −10. |
1192
+ | `.center` / `.middle` | Horizontal / vertical centering. |
1193
+ | `.small` / `.medium` / `.large` | With `.left.right`: 20 / 28 / 44 rem block-size. With `.top.bottom`: same inline-size. |
1194
+
1195
+ ```html
1196
+ <article class="small">
1197
+ <span class="absolute center middle">Centered</span>
1198
+ </article>
1199
+
1200
+ <article class="small center-align middle-align">
1201
+ <span>Centered via alignment</span>
1202
+ </article>
1203
+ ```
1204
+
1205
+ ### List
1206
+
1207
+ ```html
1208
+ <ul class="list">
1209
+ <li>Item</li>
1210
+ <li>Item</li>
1211
+ </ul>
1212
+
1213
+ <ol class="list">
1214
+ <li>Item</li>
1215
+ </ol>
1216
+ ```
1217
+
1218
+ | Class | Result |
1219
+ | --------------------------------------------------------- | ---------------------------- |
1220
+ | _(none)_ | 3.5 rem rows, no dividers. |
1221
+ | `.border` | Bottom divider between rows. |
1222
+ | `.no-space` / `.space` / `.small-space` | 2.5 / 3 / 3.5 rem rows. |
1223
+ | `.medium-space` / `.large-space` | 4.5 / 5.5 rem rows. |
1224
+
1225
+ #### Nested
1226
+
1227
+ ```html
1228
+ <ul class="list">
1229
+ <li>Item</li>
1230
+ <li>
1231
+ <ul class="list">
1232
+ <li>Nested</li>
1233
+ <li>Nested</li>
1234
+ </ul>
1235
+ </li>
1236
+ </ul>
1237
+ ```
1238
+
1239
+ #### Expansion list
1240
+
1241
+ ```html
1242
+ <ul class="list">
1243
+ <li>Item</li>
1244
+ <li>
1245
+ <details>
1246
+ <summary>Header</summary>
1247
+ <ul class="list">
1248
+ <li>Inner</li>
1249
+ </ul>
1250
+ </details>
1251
+ </li>
1252
+ </ul>
1253
+ ```
1254
+
1255
+ #### Headline and supporting text
1256
+
1257
+ ```html
1258
+ <ul class="list border">
1259
+ <li>
1260
+ <button class="circle">A</button>
1261
+ <div class="max">
1262
+ <h6 class="small">Headline</h6>
1263
+ <div>Supporting text</div>
1264
+ </div>
1265
+ <label>+15 min</label>
1266
+ </li>
1267
+ </ul>
1268
+ ```
1269
+
1270
+ With leading icon or avatar:
1271
+
1272
+ ```html
1273
+ <ul class="list">
1274
+ <li>
1275
+ <i>home</i>
1276
+ <div class="max">
1277
+ <h6 class="small">Headline</h6>
1278
+ <div>Supporting text</div>
1279
+ </div>
1280
+ <label>+15 min</label>
1281
+ </li>
1282
+ <li>
1283
+ <img class="round" src="/avatar.png">
1284
+ <div class="max">
1285
+ <h6 class="small">Headline</h6>
1286
+ <div>Supporting text</div>
1287
+ </div>
1288
+ </li>
1289
+ </ul>
1290
+ ```
1291
+
1292
+ ### Media
1293
+
1294
+ ```html
1295
+ <img src="/image.png" class="circle extra">
1296
+
1297
+ <video class="circle extra">
1298
+ <source src="/video.mp4" type="video/mp4">
1299
+ </video>
1300
+
1301
+ <svg class="circle extra" viewBox="0 0 24 24">
1302
+ <path d="…"/>
1303
+ </svg>
1304
+ ```
1305
+
1306
+ | Class | Result |
1307
+ | ------------------------------------------------------------------ | --------------------------------------------------- |
1308
+ | `.tiny` / `.small` / _(default)_ / `.large` / `.extra` | 2 / 2.5 / 3 / 3.5 / 4 rem square. |
1309
+ | `.circle` / `.round` / `.square` | Border-radius style. |
1310
+ | `.no-round` / `.left-round` / `.right-round` / `.top-round` / `.bottom-round` | Per-corner radius. |
1311
+ | `.responsive` | 100% inline-size. |
1312
+ | `.responsive.tiny` / `.small` / `.medium` / `.large` / `.extra` | Fixed block-size 4 / 8 / 12 / 16 / 20 rem. |
1313
+ | `.empty-state` | Max 24 rem wide; for empty-state illustrations. |
1314
+
1315
+ ### Menu
1316
+
1317
+ ```html
1318
+ <div>
1319
+ <button>
1320
+ <span>Menu</span>
1321
+ <i>arrow_drop_down</i>
1322
+ </button>
1323
+ <menu>
1324
+ <li>Item 1</li>
1325
+ <li>Item 2</li>
1326
+ <li>Item 3</li>
1327
+ </menu>
1328
+ </div>
1329
+ ```
1330
+
1331
+ With links:
1332
+
1333
+ ```html
1334
+ <div>
1335
+ <button>
1336
+ <span>Links</span>
1337
+ <i>arrow_drop_down</i>
1338
+ </button>
1339
+ <menu>
1340
+ <li><a href="#">Item 1</a></li>
1341
+ <li><a href="#">Item 2</a></li>
1342
+ </menu>
1343
+ </div>
1344
+ ```
1345
+
1346
+ With divider:
1347
+
1348
+ ```html
1349
+ <menu>
1350
+ <li>Item 1</li>
1351
+ <li><hr></li>
1352
+ <li>Item 2</li>
1353
+ </menu>
1354
+ ```
1355
+
1356
+ Selected state:
1357
+
1358
+ ```html
1359
+ <menu>
1360
+ <li>Item 1</li>
1361
+ <li class="active">Item 2</li>
1362
+ <li>Item 3</li>
1363
+ </menu>
1364
+ ```
1365
+
1366
+ | Class | Result |
1367
+ | --------------------------------------------------------------------------------------- | ----------------------------------------------- |
1368
+ | _(none)_ | Anchored below trigger, full width. |
1369
+ | `.no-wrap` | Width fits content; never wraps. |
1370
+ | `.wrap` | Allow item wrap. |
1371
+ | `.min` | Compact anchored popover. |
1372
+ | `.max` | Full-screen. |
1373
+ | `.top` / `.bottom` / `.left` / `.right` | Open direction / alignment. |
1374
+ | `.border` | Outlined. |
1375
+ | `.group` | Visually merges with parent (no shadow / bg). |
1376
+ | `.no-space` / `.space` / `.tiny-space` / `.small-space` / `.medium-space` / `.large-space` / `.extra-space` | Gap scale. |
1377
+
1378
+ #### Submenu
1379
+
1380
+ ```html
1381
+ <div>
1382
+ <button>
1383
+ <span>Menu</span>
1384
+ <i>arrow_drop_down</i>
1385
+ </button>
1386
+ <menu>
1387
+ <li>
1388
+ <span>Submenu</span>
1389
+ <menu>
1390
+ <li>Item</li>
1391
+ <li>Item</li>
1392
+ </menu>
1393
+ </li>
1394
+ </menu>
1395
+ </div>
1396
+ ```
1397
+
1398
+ #### Grouped menu
1399
+
1400
+ `.group` makes the menu transparent and stacks groups visually.
1401
+
1402
+ ```html
1403
+ <div>
1404
+ <button>
1405
+ <span>Grouped</span>
1406
+ <i>arrow_drop_down</i>
1407
+ </button>
1408
+ <menu class="group">
1409
+ <li>
1410
+ <menu>
1411
+ <li>Item</li>
1412
+ <li>Item</li>
1413
+ </menu>
1414
+ </li>
1415
+ <li>
1416
+ <menu>
1417
+ <li>Item</li>
1418
+ <li>Item</li>
1419
+ </menu>
1420
+ </li>
1421
+ </menu>
1422
+ </div>
1423
+ ```
1424
+
1425
+ #### Opening and closing
1426
+
1427
+ Three patterns:
1428
+
1429
+ 1. **Active class:** `<menu class="active">…</menu>`
1430
+ 2. **`data-ui` attribute:** `<div data-ui="#menu"><menu id="menu">…</menu></div>`
1431
+ 3. **`ui()` call:** `ui("#menu")`
1432
+
1433
+ Clicking outside an open menu closes it automatically.
1434
+
1435
+ ### Navigation
1436
+
1437
+ `<nav>` and `.row` are interchangeable containers — `<nav>` is semantic, `.row` is not.
1438
+
1439
+ ```html
1440
+ <nav>
1441
+ <button>Button</button>
1442
+ <a class="chip">Chip</a>
1443
+ <a><img class="circle" src="/avatar.png"></a>
1444
+ <label class="checkbox"><input type="checkbox"></label>
1445
+ </nav>
1446
+
1447
+ <div class="row">
1448
+ <div>min</div>
1449
+ <div class="max">max</div>
1450
+ <div>min</div>
1451
+ </div>
1452
+ ```
1453
+
1454
+ | Class on `<nav>` / `.row` | Result |
1455
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------- |
1456
+ | `.no-space` / `.tiny-space` / `.small-space` / `.medium-space` / `.large-space` | Gap: 0 / 0.5 / 1 / 1.5 / 2 rem. |
1457
+ | `.left-align` / `.center-align` / `.right-align` | Justify-content (also `text-align`). |
1458
+ | `.top-align` / `.middle-align` / `.bottom-align` | Align-items. |
1459
+ | `.horizontal` / `.vertical` | Flow direction. |
1460
+ | `.wrap` / `.no-wrap` | Flex-wrap behavior. |
1461
+ | `.min` | Inline-flex; shrinks to content. |
1462
+ | `.max` | `flex: 1` (children with `.max` grow). |
1463
+ | `.border` / `.round` / `.no-round` / corner-round classes | Border and corner radius. |
1464
+ | `.margin` / `.no-margin` / `.tiny-margin` / `.small-margin` / `.medium-margin` / `.large-margin` | Outer margin. |
1465
+ | `.fill` / `.primary-container` / `.secondary-container` / `.tertiary-container` | Role-colored background. |
1466
+ | `.elevate` / `.no-elevate` / `.small-elevate` / `.medium-elevate` / `.large-elevate` | Shadow. |
1467
+
1468
+ #### Navigation rail (vertical edge)
1469
+
1470
+ ```html
1471
+ <nav class="left">
1472
+ <a><i>home</i><div>Home</div></a>
1473
+ <a><i>search</i><div>Search</div></a>
1474
+ <a><i>more_vert</i><div>More</div></a>
1475
+ </nav>
1476
+
1477
+ <nav class="left max">
1478
+ <a><i>home</i><div>Home</div></a>
1479
+ <a><i>search</i><div>Search</div></a>
1480
+ </nav>
1481
+ ```
1482
+
1483
+ #### Navigation bar (horizontal edge)
1484
+
1485
+ ```html
1486
+ <nav class="bottom">
1487
+ <a><i>home</i><div>Home</div></a>
1488
+ <a><i>search</i><div>Search</div></a>
1489
+ <a><i>more_vert</i><div>More</div></a>
1490
+ </nav>
1491
+
1492
+ <nav class="top max">
1493
+ <a><i>home</i><div>Home</div></a>
1494
+ </nav>
1495
+ ```
1496
+
1497
+ #### Tabbed nav
1498
+
1499
+ ```html
1500
+ <nav class="tabbed">
1501
+ <a class="active"><i>info</i><span>Overview</span></a>
1502
+ <a><i>style</i><span>Specs</span></a>
1503
+ <a><i>design_services</i><span>Guidelines</span></a>
1504
+ </nav>
1505
+ ```
1506
+
1507
+ `nav.tabbed`: default 4 rem height, `.small` 3 rem, `.large` 5 rem.
1508
+
1509
+ #### Toolbar
1510
+
1511
+ ```html
1512
+ <nav class="toolbar">
1513
+ <a><i>videocam_off</i></a>
1514
+ <a><i>mic</i></a>
1515
+ <a class="active"><i>front_hand</i></a>
1516
+ <a><i>more_vert</i></a>
1517
+ </nav>
1518
+ ```
1519
+
1520
+ `.fill` (primary-container), `.vertical`, `.max` (full width) all work on `nav.toolbar`.
1521
+
1522
+ #### Group / connected / split
1523
+
1524
+ ```html
1525
+ <nav class="group">
1526
+ <button class="left-round">Left</button>
1527
+ <button class="no-round">Center</button>
1528
+ <button class="right-round">Right</button>
1529
+ </nav>
1530
+
1531
+ <nav class="group connected">
1532
+ <button class="left-round">Left</button>
1533
+ <button class="no-round">Center</button>
1534
+ <button class="right-round">Right</button>
1535
+ </nav>
1536
+
1537
+ <nav class="group split">
1538
+ <button class="left-round"><i>add_circle</i><span>Action</span></button>
1539
+ <button class="right-round square"><i>keyboard_arrow_down</i></button>
1540
+ </nav>
1541
+ ```
1542
+
1543
+ #### List form
1544
+
1545
+ ```html
1546
+ <nav>
1547
+ <ul>
1548
+ <li><button>Button</button></li>
1549
+ <li><a class="chip">Chip</a></li>
1550
+ </ul>
1551
+ </nav>
1552
+ ```
1553
+
1554
+ ### Overlay
1555
+
1556
+ Scrim that blocks the screen. Used with dialogs or as a standalone loading curtain.
1557
+
1558
+ ```html
1559
+ <div class="overlay center-align middle-align">
1560
+ <progress class="circle"></progress>
1561
+ </div>
1562
+ ```
1563
+
1564
+ | Class | Result |
1565
+ | --------------------------------------------------------------------------- | ------------------------------- |
1566
+ | `.active` | Shown. |
1567
+ | `.blur` / `.small-blur` / `.medium-blur` / `.large-blur` | Backdrop-blur effect. |
1568
+ | `.left-align` / `.right-align` / `.center-align` / `.top-align` / `.bottom-align` / `.middle-align` | Align inner content. |
1569
+
1570
+ Opening and closing — same three patterns:
1571
+
1572
+ 1. **Active class:** `<div class="overlay active">…</div>`
1573
+ 2. **`data-ui`:** `<button data-ui="#overlay">Show</button>` + `<div class="overlay" id="overlay">…</div>`
1574
+ 3. **`ui()`:** `ui("#overlay")`
1575
+
1576
+ ### Page
1577
+
1578
+ ```html
1579
+ <div class="page active">
1580
+ <h5>Title</h5>
1581
+ </div>
1582
+ ```
1583
+
1584
+ | Class | Result |
1585
+ | ---------------------------------------------- | ------------------------------------- |
1586
+ | `.active` | Visible. |
1587
+ | `.left` / `.right` / `.top` / `.bottom` | Entry transform direction. |
1588
+
1589
+ Activating a page — three patterns:
1590
+
1591
+ 1. **Active class:** add `.active` to one, remove from siblings.
1592
+ 2. **`data-ui`:** `<a data-ui="#page1">Open</a>` — siblings at the same level deactivate.
1593
+ 3. **`ui()`:** `ui("#page1")`.
1594
+
1595
+ ### Progress
1596
+
1597
+ ```html
1598
+ <progress></progress> <!-- indeterminate -->
1599
+ <progress value="25" max="100"></progress> <!-- linear -->
1600
+
1601
+ <progress class="wavy"></progress>
1602
+ <progress class="wavy" value="25" max="100"></progress>
1603
+
1604
+ <progress class="circle"></progress> <!-- circular indeterminate -->
1605
+ <progress class="circle" value="25" max="100"></progress>
1606
+
1607
+ <progress class="circle wavy"></progress>
1608
+ <progress class="circle wavy" value="25" max="100"></progress>
1609
+ ```
1610
+
1611
+ | Class | Result |
1612
+ | ---------------- | -------------------------------------------- |
1613
+ | _(none)_ | Linear, 0.25 rem thick. |
1614
+ | `.small` / `.medium` / `.large` | 0.25 / 0.35 / 0.45 rem thick. |
1615
+ | `.indeterminate` | Forces animated indeterminate state. |
1616
+ | `.wavy` | Wavy SVG track (linear or circle). |
1617
+ | `.circle` | Circular; `.small` 1.5 rem / _(default)_ 2.5 / `.large` 3.5. |
1618
+ | `.max` | Absolutely fills the parent (use inside `<article>`, `<button>`, …). |
1619
+
1620
+ A bare `<progress></progress>` is auto-promoted to indeterminate at boot.
1621
+
1622
+ ### Radio
1623
+
1624
+ ```html
1625
+ <label class="radio">
1626
+ <input type="radio">
1627
+ <span></span>
1628
+ </label>
1629
+
1630
+ <label class="radio">
1631
+ <input type="radio">
1632
+ <span>Click here</span>
1633
+ </label>
1634
+ ```
1635
+
1636
+ Sizes: `.small`, _(default)_, `.large`, `.extra`.
1637
+
1638
+ #### With icons
1639
+
1640
+ ```html
1641
+ <label class="radio icon">
1642
+ <input type="radio">
1643
+ <span>
1644
+ <i>close</i>
1645
+ <i>done</i>
1646
+ </span>
1647
+ </label>
1648
+ ```
1649
+
1650
+ #### Group inside a field
1651
+
1652
+ ```html
1653
+ <div class="field middle-align">
1654
+ <nav>
1655
+ <label class="radio"><input type="radio" name="g"><span>Item 1</span></label>
1656
+ <label class="radio"><input type="radio" name="g"><span>Item 2</span></label>
1657
+ <label class="radio"><input type="radio" name="g"><span>Item 3</span></label>
1658
+ </nav>
1659
+ <output>Helper text</output>
1660
+ </div>
1661
+ ```
1662
+
1663
+ ### Shape
1664
+
1665
+ SVG-masked decorative element. Place inside `<button>`, `<div>`, or a sized container.
1666
+
1667
+ ```html
1668
+ <div class="shape sunny"></div>
1669
+
1670
+ <div class="shape sunny">
1671
+ <img class="responsive" src="/favicon.png">
1672
+ </div>
1673
+
1674
+ <div class="small-width small-height">
1675
+ <div class="shape sunny max"></div>
1676
+ </div>
1677
+ ```
1678
+
1679
+ | Class | Result |
1680
+ | ------------------------------------------------------------------------------ | ------------------------------- |
1681
+ | `.tiny` / `.small` / _(default)_ / `.medium` / `.large` / `.extra` | 2.5 / 3 / 3.5 / 4.5 / 5.5 / 6.5 rem. |
1682
+ | `.max` | Fills the parent. |
1683
+ | `.space` / `.no-space` / `.tiny-space` / `.small-space` / `.medium-space` / `.large-space` / `.extra-space` | Inner mask padding. |
1684
+ | `.rotate` / `.slow-rotate` / `.fast-rotate` | Rotation animation (12 s / 24 s / 6 s). |
1685
+
1686
+ **Shape names** (one per class):
1687
+
1688
+ `arch`, `arrow`, `boom`, `bun`, `burst`, `circle`, `clamshell`, `diamond`, `fan`, `flower`, `gem`, `ghost-ish`, `heart`, `leaf-clover4`, `leaft-clover8`, `loading-indicator`, `oval`, `pentagon`, `pill`, `pixel-circle`, `pixel-triangle`, `puffy`, `puffy-diamond`, `semicircle`, `sided-cookie4`, `sided-cookie6`, `sided-cookie7`, `sided-cookie9`, `sided-cookie12`, `slanted`, `soft-boom`, `soft-burst`, `square`, `sunny`, `triangle`, `very-sunny`, `wavy`, `wavy-circle`.
1689
+
1690
+ Inside a button:
1691
+
1692
+ ```html
1693
+ <button class="circle extra transparent">
1694
+ <span class="shape sided-cookie12 max medium-space"></span>
1695
+ </button>
1696
+ ```
1697
+
1698
+ Spinner:
1699
+
1700
+ ```html
1701
+ <div class="shape sided-cookie12 transparent rotate">
1702
+ <button class="responsive">
1703
+ <i>search</i>
1704
+ </button>
1705
+ </div>
1706
+ ```
1707
+
1708
+ ### Slider
1709
+
1710
+ Default range is 0–100.
1711
+
1712
+ ```html
1713
+ <div class="slider">
1714
+ <input type="range">
1715
+ <span></span>
1716
+ </div>
1717
+
1718
+ <div class="slider">
1719
+ <input type="range" min="4" max="8">
1720
+ <span></span>
1721
+ </div>
1722
+ ```
1723
+
1724
+ | Class | Track size |
1725
+ | ------------------------------------------------------------------ | ---------- |
1726
+ | `.tiny` | 1 rem |
1727
+ | `.small` | 1.5 rem |
1728
+ | `.medium` | 2.5 rem |
1729
+ | `.large` | 3.5 rem |
1730
+ | `.extra` | 6 rem |
1731
+ | `.vertical` | Rotates 90°. |
1732
+ | `.max` | Fills the parent (container slider). |
1733
+
1734
+ #### Dual thumb
1735
+
1736
+ ```html
1737
+ <div class="slider">
1738
+ <input type="range" value="25">
1739
+ <input type="range" value="50">
1740
+ <span></span>
1741
+ </div>
1742
+ ```
1743
+
1744
+ #### Value tooltip
1745
+
1746
+ ```html
1747
+ <div class="slider">
1748
+ <input type="range">
1749
+ <span></span>
1750
+ <div class="tooltip"></div>
1751
+ </div>
1752
+ ```
1753
+
1754
+ #### Inset icon
1755
+
1756
+ The icon appears only with `.medium`, `.large`, or `.extra`.
1757
+
1758
+ ```html
1759
+ <div class="slider medium">
1760
+ <input type="range">
1761
+ <span><i>sunny</i></span>
1762
+ </div>
1763
+ ```
1764
+
1765
+ #### Inside a field
1766
+
1767
+ ```html
1768
+ <div class="field middle-align">
1769
+ <div class="slider">
1770
+ <input type="range">
1771
+ <span></span>
1772
+ </div>
1773
+ <output>Helper</output>
1774
+ </div>
1775
+ ```
1776
+
1777
+ #### Container slider
1778
+
1779
+ ```html
1780
+ <article>
1781
+ <div class="slider max">
1782
+ <input type="range">
1783
+ <span></span>
1784
+ </div>
1785
+ </article>
1786
+ ```
1787
+
1788
+ ### Snackbar
1789
+
1790
+ ```html
1791
+ <div class="snackbar">
1792
+ <i>warning</i>
1793
+ <span>I'm a snackbar</span>
1794
+ </div>
1795
+ ```
1796
+
1797
+ | Class | Result |
1798
+ | ----------------------------------------------------------- | --------------------------------------- |
1799
+ | _(none)_ | Bottom-centered; auto-hides after 6 s. |
1800
+ | `.top` / `.bottom` | Anchor position. |
1801
+ | `.error` / `.primary` / `.secondary` / `.tertiary` | Role-colored background. |
1802
+ | `.active` | Shown. |
1803
+
1804
+ #### With action
1805
+
1806
+ ```html
1807
+ <div class="snackbar">
1808
+ <div class="max">Item moved to trash</div>
1809
+ <a class="inverse-primary-text">Undo</a>
1810
+ </div>
1811
+ ```
1812
+
1813
+ Opening — four patterns:
1814
+
1815
+ 1. **Active class:** add `.active`.
1816
+ 2. **`data-ui`:** `<button data-ui="#snack">Show</button>`.
1817
+ 3. **`ui()`:** `ui("#snack")` — default 6000 ms; `ui("#snack", 3000)` for a custom timeout; `ui("#snack", -1)` to keep open until click.
1818
+ 4. **Popover API:**
1819
+
1820
+ ```html
1821
+ <button popovertarget="snack">Show</button>
1822
+ <div class="snackbar" id="snack" popover>I'm a snackbar</div>
1823
+ ```
1824
+
1825
+ ```js
1826
+ document.querySelector("#snack").showPopover();
1827
+ document.querySelector("#snack").hidePopover();
1828
+ document.querySelector("#snack").togglePopover();
1829
+ ```
1830
+
1831
+ Only one snackbar shows at a time — opening one dismisses any open siblings.
1832
+
1833
+ ### Switch
1834
+
1835
+ ```html
1836
+ <label class="switch">
1837
+ <input type="checkbox">
1838
+ <span></span>
1839
+ </label>
1840
+
1841
+ <nav>
1842
+ <div class="max">
1843
+ <h6>Title</h6>
1844
+ <div>Complementary text</div>
1845
+ </div>
1846
+ <label class="switch">
1847
+ <input type="checkbox">
1848
+ <span></span>
1849
+ </label>
1850
+ </nav>
1851
+ ```
1852
+
1853
+ Sizes: `.small`, _(default)_, `.large`, `.extra`.
1854
+
1855
+ #### With icons
1856
+
1857
+ ```html
1858
+ <label class="switch icon">
1859
+ <input type="checkbox">
1860
+ <span><i>wifi</i></span>
1861
+ </label>
1862
+
1863
+ <label class="switch icon">
1864
+ <input type="checkbox">
1865
+ <span>
1866
+ <i>close</i>
1867
+ <i>done</i>
1868
+ </span>
1869
+ </label>
1870
+ ```
1871
+
1872
+ #### Inside a field
1873
+
1874
+ ```html
1875
+ <div class="field middle-align">
1876
+ <nav>
1877
+ <div class="max">
1878
+ <h6>Title</h6>
1879
+ <div>Complementary text</div>
1880
+ </div>
1881
+ <label class="switch">
1882
+ <input type="checkbox">
1883
+ <span></span>
1884
+ </label>
1885
+ </nav>
1886
+ </div>
1887
+ ```
1888
+
1889
+ ### Table
1890
+
1891
+ ```html
1892
+ <table>
1893
+ <thead>
1894
+ <tr><th>Header</th><th>Header</th></tr>
1895
+ </thead>
1896
+ <tbody>
1897
+ <tr><td>Cell</td><td>Cell</td></tr>
1898
+ <tr><td>Cell</td><td>Cell</td></tr>
1899
+ </tbody>
1900
+ <tfoot>
1901
+ <tr><th>Footer</th><th>Footer</th></tr>
1902
+ </tfoot>
1903
+ </table>
1904
+ ```
1905
+
1906
+ | Class on `<table>` | Result |
1907
+ | ---------------------------------------------------------------------------------------- | ----------------- |
1908
+ | `.border` | Row dividers. |
1909
+ | `.stripes` | Zebra rows. |
1910
+ | `.no-space` / `.space` / `.small-space` / `.medium-space` / `.large-space` | Cell padding. |
1911
+ | `.left-align` / `.center-align` / `.right-align` | Text alignment. |
1912
+
1913
+ `<th class="fixed">`, `<thead class="fixed">`, `<tfoot class="fixed">` for sticky headers/footers inside a scrolling container. `<td class="min">` / `<th class="min">` shrinks the cell to its content.
1914
+
1915
+ #### Scroll
1916
+
1917
+ ```html
1918
+ <div class="scroll small-height">
1919
+ <table>
1920
+ <thead class="fixed">
1921
+ <tr><th>Header</th><th>Header</th></tr>
1922
+ </thead>
1923
+ <tbody>
1924
+ <tr><td>Cell</td><td>Cell</td></tr>
1925
+ </tbody>
1926
+ </table>
1927
+ </div>
1928
+ ```
1929
+
1930
+ ### Tabs
1931
+
1932
+ ```html
1933
+ <div class="tabs">
1934
+ <a class="active">Tab 1</a>
1935
+ <a>Tab 2</a>
1936
+ <a>Tab 3</a>
1937
+ </div>
1938
+
1939
+ <div class="page padding active"><h5>Tab 1</h5></div>
1940
+ <div class="page padding"><h5>Tab 2</h5></div>
1941
+ <div class="page padding"><h5>Tab 3</h5></div>
1942
+ ```
1943
+
1944
+ | Class on `.tabs` | Result |
1945
+ | --------------------------------------------------------- | --------------------- |
1946
+ | `.small` / `.large` | 2 / 4 rem min height. |
1947
+ | `.min` | Shorter active underline. |
1948
+ | `.max` | Tabs grow to fill. |
1949
+ | `.left-align` / `.center-align` / `.right-align` | Justify-content. |
1950
+ | `.horizontal` / `.vertical` | Flow direction. |
1951
+
1952
+ Activating tabs — two patterns:
1953
+
1954
+ 1. **Active class:** add `.active` to one tab `<a>` and one matching `.page`, remove from siblings.
1955
+ 2. **`data-ui`:** `<a data-ui="#page1">Tab 1</a>` — the matching `.page` activates, siblings hide.
1956
+
1957
+ ### Tooltip
1958
+
1959
+ A child of the trigger element.
1960
+
1961
+ ```html
1962
+ <button>
1963
+ <span>Button</span>
1964
+ <span class="tooltip">I'm a tooltip</span>
1965
+ </button>
1966
+
1967
+ <div>
1968
+ <button class="chip round">Hover me</button>
1969
+ <div class="tooltip">I'm a tooltip</div>
1970
+ </div>
1971
+ ```
1972
+
1973
+ | Class | Result |
1974
+ | -------------------------------------------------- | ----------------------------------- |
1975
+ | _(none)_ | Above the parent. |
1976
+ | `.left` / `.right` / `.bottom` | Position override. |
1977
+ | `.small` / `.medium` / `.large` | 8 / 12 / 16 rem wide, multi-line. |
1978
+ | `.max` | Block-level 20 rem rich tooltip. |
1979
+ | `.no-space` / `.medium-space` / `.large-space` | Gap from anchor: 0 / −1 / −1.5 rem. |
1980
+
1981
+ #### Rich tooltip
1982
+
1983
+ ```html
1984
+ <div>
1985
+ <button class="chip round">Rich tooltip</button>
1986
+ <div class="tooltip max">
1987
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
1988
+ <nav>
1989
+ <button>Action</button>
1990
+ </nav>
1991
+ </div>
1992
+ </div>
1993
+ ```
1994
+
1995
+ ### Typography
1996
+
1997
+ ```html
1998
+ <h1>Display</h1>
1999
+ <h2>Display</h2>
2000
+ <h3>Display</h3>
2001
+ <h4>Headline</h4>
2002
+ <h5>Headline</h5>
2003
+ <h6>Headline</h6>
2004
+ ```
2005
+
2006
+ Heading size modifiers: `.small`, `.large`.
2007
+
2008
+ #### Formatting
2009
+
2010
+ ```html
2011
+ <a class="link">link</a>
2012
+ <a class="inverse-link">inverse-link</a>
2013
+ <p class="italic">italic</p>
2014
+ <p class="bold">bold</p>
2015
+ <p class="underline">underline</p>
2016
+ <p class="overline">overline</p>
2017
+ <p class="upper">upper</p>
2018
+ <p class="lower">lower</p>
2019
+ <p class="capitalize">capitalize</p>
2020
+ <p class="small-text">small (0.75 rem)</p>
2021
+ <p class="medium-text">medium (0.875 rem)</p>
2022
+ <p class="large-text">large (1 rem)</p>
2023
+ <p class="truncate">long single-line text…</p>
2024
+ ```
2025
+
2026
+ #### Line spacing
2027
+
2028
+ `.no-line`, `.tiny-line` (1.25 rem), `.small-line` (1.5), `.medium-line` (1.75), `.large-line` (2), `.extra-line` (2.25).
2029
+
2030
+ #### Blockquote, pre, code
2031
+
2032
+ ```html
2033
+ <blockquote>Quoted text.</blockquote>
2034
+
2035
+ <pre>Preformatted.</pre>
2036
+
2037
+ <pre>
2038
+ <code>console.log("hello");</code>
2039
+ </pre>
2040
+
2041
+ <p>
2042
+ The function <code>console.log()</code> prints a log message.
2043
+ </p>
2044
+ ```
2045
+
2046
+ `<pre>` and `<blockquote>` modifiers: `.border`, `.no-border`, `.scroll`.
2047
+
2048
+ ---
2049
+
2050
+ ## Helpers
2051
+
2052
+ ### Alignments
2053
+
2054
+ ```html
2055
+ <div class="center-align middle-align">…</div>
2056
+ ```
2057
+
2058
+ `.left-align`, `.right-align`, `.center-align` (justify + text), `.top-align`, `.middle-align`, `.bottom-align` (align-items).
2059
+
2060
+ ### Blurs
2061
+
2062
+ Backdrop-blur with a translucent background.
2063
+
2064
+ ```html
2065
+ <header class="blur">…</header>
2066
+ <article class="blur">…</article>
2067
+ <button class="blur">Blurred</button>
2068
+ <button class="chip blur">Chip</button>
2069
+ ```
2070
+
2071
+ `.blur` (1 rem), `.small-blur` (0.5), `.large-blur` (1.5). Add `.light` or `.dark` to force a light/dark surface tint.
2072
+
2073
+ ### Color roles
2074
+
2075
+ The only color tokens are the M3 role-based ones. Every role yields a paired surface utility (`.<role>` paints background + on-role text), a `-text` utility, a `-border` utility, and where applicable a `-container` utility.
2076
+
2077
+ ```html
2078
+ <div class="primary">primary surface + on-primary text</div>
2079
+ <div class="primary-container">primary container surface + on-primary-container text</div>
2080
+ <span class="primary-text">primary text only</span>
2081
+ <div class="primary-border border">primary border only</div>
2082
+ ```
2083
+
2084
+ | Role group | Surface | Text | Border | Container |
2085
+ | -------------- | ------------------- | --------------------- | ----------------------- | ---------------------- |
2086
+ | **Primary** | `.primary` | `.primary-text` | `.primary-border` | `.primary-container` |
2087
+ | **Secondary** | `.secondary` | `.secondary-text` | `.secondary-border` | `.secondary-container` |
2088
+ | **Tertiary** | `.tertiary` | `.tertiary-text` | `.tertiary-border` | `.tertiary-container` |
2089
+ | **Error** | `.error` | `.error-text` | `.error-border` | `.error-container` |
2090
+
2091
+ **Neutral surface family** (background only — `on-surface` text is inherited):
2092
+
2093
+ `.background`, `.surface`, `.surface-variant`, `.surface-dim`, `.surface-bright`, `.surface-container-lowest`, `.surface-container-low`, `.surface-container`, `.surface-container-high`, `.surface-container-highest`, `.inverse-surface`.
2094
+
2095
+ **Inverse primary:**
2096
+
2097
+ `.inverse-primary`, `.inverse-primary-text`, `.inverse-primary-border`.
2098
+
2099
+ **Transparent (structural):**
2100
+
2101
+ `.transparent`, `.transparent-text`, `.transparent-border`.
2102
+
2103
+ ### Directions
2104
+
2105
+ `.horizontal` (row flex), `.vertical` (column flex). Both work on `<a>`, `<button>`, `.chip`, `nav`, `.row`, `.tabs`.
2106
+
2107
+ ### Elevations
2108
+
2109
+ `.elevate`, `.no-elevate`, `.small-elevate`, `.medium-elevate`, `.large-elevate`.
2110
+
2111
+ ### Forms (helpers)
2112
+
2113
+ Shape and surface helpers.
2114
+
2115
+ | Class | Effect |
2116
+ | ---------------------------------------------------------------------- | --------------------------------- |
2117
+ | `.border` / `.no-border` | 1 px outline / no border. |
2118
+ | `.round` / `.no-round` / `.small-round` / `.large-round` | Border-radius scale (0.5 / 2 / 3.5 rem). |
2119
+ | `.left-round` / `.right-round` / `.top-round` / `.bottom-round` | Per-side radius. |
2120
+ | `.circle` / `.square` | Round / no border-radius equal-size shape. |
2121
+ | `.fill` | Filled surface (`surface-variant`). |
2122
+ | `.extend` | Extendable label (buttons). |
2123
+
2124
+ ### Margins
2125
+
2126
+ `.margin` (1 rem default). Scale modifiers: `.no-margin`, `.auto-margin`, `.tiny-margin` (0.25), `.small-margin` (0.5), `.large-margin` (1.5). Directional: `.left-margin`, `.right-margin`, `.top-margin`, `.bottom-margin`, `.horizontal-margin`, `.vertical-margin`.
2127
+
2128
+ Combine: `<div class="margin large-margin top-margin">`.
2129
+
2130
+ ### Opacities
2131
+
2132
+ `.opacity` (1), `.no-opacity` (0), `.small-opacity` (0.25), `.medium-opacity` (0.5), `.large-opacity` (0.75).
2133
+
2134
+ ### Paddings
2135
+
2136
+ Identical scale and directional set as margins: `.padding`, `.no-padding`, `.tiny-padding`, `.small-padding`, `.large-padding`, `.left-padding`, `.right-padding`, `.top-padding`, `.bottom-padding`, `.horizontal-padding`, `.vertical-padding`.
2137
+
2138
+ ### Positions
2139
+
2140
+ | Class | Effect |
2141
+ | -------------------------------------- | --------------------------------------------- |
2142
+ | `.left` / `.right` / `.top` / `.bottom` | Pin to the edge (inset-inline / inset-block). |
2143
+ | `.center` | Horizontally centered (`translateX(-50%)`). |
2144
+ | `.middle` | Vertically centered (`translateY(-50%)`). |
2145
+ | `.middle.center` | Fully centered. |
2146
+ | `.front` | `z-index: 10`. |
2147
+ | `.back` | `z-index: -10`. |
2148
+
2149
+ ### Responsive
2150
+
2151
+ `.responsive` fills available inline-size. `.s` (visible < 601 px), `.m` (601 – 992 px), `.l` (≥ 993 px). Combine to span ranges (e.g. `.m.l` hides on small only).
2152
+
2153
+ ### Ripples
2154
+
2155
+ Pointer-driven hover/focus tint plus speed scale.
2156
+
2157
+ ```html
2158
+ <button class="ripple">Default</button>
2159
+ <button class="fast-ripple">Fast (200 ms)</button>
2160
+ <button class="slow-ripple">Slow (1800 ms)</button>
2161
+
2162
+ <button class="chip ripple">Chip with ripple</button>
2163
+ ```
2164
+
2165
+ ### Scrolls
2166
+
2167
+ `.scroll` (`overflow: auto`), `.no-scroll` (`overflow: hidden`).
2168
+
2169
+ ### Shadows
2170
+
2171
+ Inset / directional gradient shadows. Useful behind text overlaid on media.
2172
+
2173
+ ```html
2174
+ <header class="left-shadow">…</header>
2175
+ <article class="bottom-shadow">…</article>
2176
+ <button class="left-shadow">Button</button>
2177
+ <button class="chip left-shadow">Chip</button>
2178
+ ```
2179
+
2180
+ `.shadow`, `.left-shadow`, `.right-shadow`, `.top-shadow`, `.bottom-shadow`.
2181
+
2182
+ ### Sizes
2183
+
2184
+ | Class | Effect |
2185
+ | --------------------------------------------------------------------- | ------------------------------------- |
2186
+ | `.tiny` / `.small` / `.medium` / `.large` / `.extra` | Component-scoped size scale. |
2187
+ | `.wrap` / `.no-wrap` | Block + wrap / flex no-wrap. |
2188
+ | `.max` | `flex: 1` inside flex containers. |
2189
+ | `.small-width` / `.medium-width` / `.large-width` / `.auto-width` | 12 / 24 / 36 rem / `auto` inline-size. |
2190
+ | `.small-height` / `.medium-height` / `.large-height` / `.auto-height` | Same scale for block-size. |
2191
+
2192
+ ### Spaces
2193
+
2194
+ Vertical spacer blocks (suppressed inside `nav`, `.row`, `.grid`, `table`, `.tooltip`, `.list`, `menu`, `.shape`).
2195
+
2196
+ | Class | Height |
2197
+ | --------------------------- | -------- |
2198
+ | `.tiny-space` | 0.5 rem |
2199
+ | `.space` / `.small-space` | 1 rem |
2200
+ | `.medium-space` | 2 rem |
2201
+ | `.large-space` | 3 rem |
2202
+ | `.extra-space` | 4 rem |
2203
+
2204
+ On `<nav>` / `.row` / `.grid` / `menu`, the same class names control gap instead of block-size.
2205
+
2206
+ ### Waves
2207
+
2208
+ Material radial-gradient wave on hover / focus (auto-applied to `.button`, `button`, `.chip`, `.tabs > a`, `nav.tabbed > a`, `nav.toolbar > a`, and rail-nav items). Add `.wave` to opt arbitrary elements in; `.no-wave` to opt out.
2209
+
2210
+ ```html
2211
+ <div class="wave padding round">Custom wave target</div>
2212
+ <button class="no-wave">No wave</button>
2213
+ ```
2214
+
2215
+ ### Zoom
2216
+
2217
+ CSS `zoom` scaling. `.zoom` (2×), `.tiny-zoom` (2), `.small-zoom` (3), `.medium-zoom` (4), `.large-zoom` (5), `.extra-zoom` (6).
2218
+
2219
+ ---
2220
+
2221
+ ## Design tokens
2222
+
2223
+ Override any CSS custom property at any scope — no rebuild required.
2224
+
2225
+ ```css
2226
+ :root {
2227
+ --primary: #ff5722;
2228
+ --on-primary: #fff;
2229
+ --primary-container: #ffe0d6;
2230
+ --on-primary-container: #410001;
2231
+ }
2232
+
2233
+ article.alert {
2234
+ --surface-container-low: #fff3e0;
2235
+ }
2236
+ ```
2237
+
2238
+ **Color roles** — same quartet for `secondary`, `tertiary`, `error`:
2239
+
2240
+ `--primary`, `--on-primary`, `--primary-container`, `--on-primary-container`
2241
+
2242
+ **Neutrals:** `--background`, `--on-background`, `--surface`, `--on-surface`, `--surface-variant`, `--on-surface-variant`, `--outline`, `--outline-variant`, `--shadow`, `--scrim`.
2243
+
2244
+ **Surface tonal:** `--surface-dim`, `--surface-bright`, `--surface-container-lowest`, `--surface-container-low`, `--surface-container`, `--surface-container-high`, `--surface-container-highest`.
2245
+
2246
+ **Inverse:** `--inverse-surface`, `--inverse-on-surface`, `--inverse-primary`.
2247
+
2248
+ **Structural:**
2249
+
2250
+ | Token | Default |
2251
+ | ---------------------------------------- | --------------------------------------------------------------- |
2252
+ | `--size` | `1rem` (scale all sizing) |
2253
+ | `--font` | `Inter, Roboto, "Helvetica Neue", "Arial Nova", "Nimbus Sans", "Noto Sans", Arial, sans-serif` |
2254
+ | `--font-icon` | `"Material Symbols Outlined"` |
2255
+ | `--speed1` … `--speed4` | `0.1s` / `0.2s` / `0.3s` / `0.4s` |
2256
+ | `--active` | `rgb(128 128 128 / 0.192)` (hover / pressed overlay) |
2257
+ | `--overlay` | `rgb(0 0 0 / 0.5)` (modal scrim) |
2258
+ | `--elevate1` / `--elevate2` / `--elevate3` | Shadow tiers. |
2259
+ | `--top`, `--bottom`, `--left`, `--right` | `env(safe-area-inset-*)` for notched devices. |
2260
+
2261
+ Switch icon variant:
2262
+
2263
+ ```css
2264
+ :root { --font-icon: "Material Symbols Rounded"; }
2265
+ /* "Material Symbols Outlined" (default), "Material Symbols Rounded",
2266
+ "Material Symbols Sharp", "Material Symbols Subset" (smaller bundle, used by
2267
+ checkbox/radio/switch), or `none` to disable icons entirely. */
2268
+ ```
2269
+
2270
+ Use a custom body font:
2271
+
2272
+ ```css
2273
+ @import url("https://fonts.googleapis.com/css2?family=Roboto+Flex:wght@400;500;700&display=swap");
2274
+ :root { --font: "Roboto Flex"; }
2275
+ ```
2276
+
2277
+ Ship a narrower Symbols subset (import only the glyphs you use) to shrink the bundle:
2278
+
2279
+ ```css
2280
+ @import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:FILL@0..1&icon_names=check,check_box,check_box_outline_blank,indeterminate_check_box,radio_button_checked&display=swap");
2281
+ :root { --font-icon: "Material Symbols Outlined"; }
2282
+ ```
2283
+
2284
+ ### Full token override template
2285
+
2286
+ To replace the default theme entirely, override every role token at `:root` and provide a `[data-mode="dark"]` variant:
2287
+
2288
+ ```css
2289
+ :root,
2290
+ :root[data-mode="light"] {
2291
+ --primary: #6750a4;
2292
+ --on-primary: #ffffff;
2293
+ --primary-container: #e9ddff;
2294
+ --on-primary-container: #22005d;
2295
+ --secondary: #625b71;
2296
+ --on-secondary: #ffffff;
2297
+ --secondary-container: #e8def8;
2298
+ --on-secondary-container: #1e192b;
2299
+ --tertiary: #7e5260;
2300
+ --on-tertiary: #ffffff;
2301
+ --tertiary-container: #ffd9e3;
2302
+ --on-tertiary-container: #31101d;
2303
+ --error: #ba1a1a;
2304
+ --on-error: #ffffff;
2305
+ --error-container: #ffdad6;
2306
+ --on-error-container: #410002;
2307
+ --background: #fffbff;
2308
+ --on-background: #1c1b1e;
2309
+ --surface: #fdf8fd;
2310
+ --on-surface: #1c1b1e;
2311
+ --surface-variant: #e7e0eb;
2312
+ --on-surface-variant: #49454e;
2313
+ --outline: #7a757f;
2314
+ --outline-variant: #cac4cf;
2315
+ --shadow: #000000;
2316
+ --scrim: #000000;
2317
+ --inverse-surface: #313033;
2318
+ --inverse-on-surface: #f4eff4;
2319
+ --inverse-primary: #cfbcff;
2320
+ --surface-dim: #ddd8dd;
2321
+ --surface-bright: #fdf8fd;
2322
+ --surface-container-lowest: #ffffff;
2323
+ --surface-container-low: #f7f2f7;
2324
+ --surface-container: #f2ecf1;
2325
+ --surface-container-high: #ece7eb;
2326
+ --surface-container-highest: #e6e1e6;
2327
+ }
2328
+
2329
+ :root[data-mode="dark"] {
2330
+ --primary: #cfbcff;
2331
+ --on-primary: #381e72;
2332
+ --primary-container: #4f378a;
2333
+ --on-primary-container: #e9ddff;
2334
+ --secondary: #cbc2db;
2335
+ --on-secondary: #332d41;
2336
+ --secondary-container: #4a4458;
2337
+ --on-secondary-container: #e8def8;
2338
+ --tertiary: #efb8c8;
2339
+ --on-tertiary: #4a2532;
2340
+ --tertiary-container: #633b48;
2341
+ --on-tertiary-container: #ffd9e3;
2342
+ --error: #ffb4ab;
2343
+ --on-error: #690005;
2344
+ --error-container: #93000a;
2345
+ --on-error-container: #ffb4ab;
2346
+ --background: #1c1b1e;
2347
+ --on-background: #e6e1e6;
2348
+ --surface: #141316;
2349
+ --on-surface: #e6e1e6;
2350
+ --surface-variant: #49454e;
2351
+ --on-surface-variant: #cac4cf;
2352
+ --outline: #948f99;
2353
+ --outline-variant: #49454e;
2354
+ --shadow: #000000;
2355
+ --scrim: #000000;
2356
+ --inverse-surface: #e6e1e6;
2357
+ --inverse-on-surface: #313033;
2358
+ --inverse-primary: #6750a4;
2359
+ --surface-dim: #141316;
2360
+ --surface-bright: #3a383c;
2361
+ --surface-container-lowest: #0f0e11;
2362
+ --surface-container-low: #1c1b1e;
2363
+ --surface-container: #201f22;
2364
+ --surface-container-high: #2b292d;
2365
+ --surface-container-highest: #363438;
2366
+ }
2367
+ ```
2368
+
2369
+ ---
2370
+
2371
+ ## Themes
2372
+
2373
+ Baked themes ship in the bundle and swap via `<html data-theme="<name>">`:
2374
+
2375
+ `stackific` _(default)_, `hello-pumpkin`, `sea-lettuce`, `olive`, `nord`, `vega-violet`, `wild-strawberry`, `heliotrope-magenta`, `voodoo-violet`, `red-orchid`, `green-brown`, `shakshuka`, `purple-honeycreeper`, `maldives`, `verditer`, `fennel`, `gold`, `burtuqali`.
2376
+
2377
+ Each theme sets the full M3 role-token set (`--primary`, `--secondary`, `--tertiary`, `--surface-*`, etc.) for both light and dark modes.
2378
+
2379
+ ```html
2380
+ <html data-theme="nord" data-mode="dark">
2381
+ ```
2382
+
2383
+ ```js
2384
+ ui("theme", "vega-violet"); // swap to a baked theme
2385
+ await ui("theme", "#1447E6"); // generate from hex (needs material-dynamic-colors)
2386
+ ```
2387
+
2388
+ ---
2389
+
2390
+ ## Theme and mode selector
2391
+
2392
+ If your page anchors the theme menu inside another element with `overflow: hidden` or `overflow: auto`, also add this so the popover can position itself:
2393
+
2394
+ ```html
2395
+ <style>
2396
+ /* Positioning context for md3's <menu> popover relative to its trigger. */
2397
+ *:has(> menu[data-theme-picker]) {
2398
+ position: relative;
2399
+ }
2400
+ </style>
2401
+ ```
2402
+
2403
+ ### The two triggers (anywhere in `<body>`)
2404
+
2405
+ The theme picker is a `<button data-ui="#…">` paired with an empty `<menu data-theme-picker>` — md3 toggles the menu's `active` class when the button is clicked; the init script fills the menu with the available theme slugs. The appearance toggle is any element with `data-theme-toggle`;
2406
+ the init script cycles `auto → light → dark` on click and keeps the inner `<i>` in sync with the current mode.
2407
+
2408
+ ```html
2409
+ <!-- Theme picker -->
2410
+ <div>
2411
+ <button data-ui="#theme-picker" aria-label="Pick theme">
2412
+ <i aria-hidden="true">style</i>
2413
+ </button>
2414
+ <menu id="theme-picker" class="no-wrap" data-theme-picker></menu>
2415
+ </div>
2416
+
2417
+ <!-- Appearance (auto / light / dark) -->
2418
+ <button
2419
+ data-theme-toggle
2420
+ aria-label="Cycle appearance (auto, light, dark)">
2421
+ <i aria-hidden="true">brightness_auto</i>
2422
+ </button>
2423
+ ```
2424
+
2425
+ Notes:
2426
+
2427
+ - The `<menu>`'s `id` must match the trigger's `data-ui` value.
2428
+ - Add `top` / `left` / `right` classes to the `<menu>` to anchor the popover side (e.g. `class="no-wrap top"` if the trigger sits at the bottom of the page).
2429
+ - You can attach `data-theme-toggle` to multiple buttons (e.g. desktop + mobile) — the script wires all of them and keeps their icons in sync.
2430
+
2431
+ ### Init script (before `</body>`)
2432
+
2433
+ Pure, no dependencies beyond md3's global `ui()`. Cycles the mode, populates the picker from `ui("themes")`, and writes selections back through `ui("mode", …)` / `ui("theme", …)`. md3 persists both choices to `localStorage` (`md3:mode`, `md3:theme`) so the page reopens in the same state.
2434
+
2435
+ ```html
2436
+ <script type="module">
2437
+ const MODE_CYCLE = ["auto", "light", "dark"];
2438
+
2439
+ const modeIcon = (pref) =>
2440
+ pref === "light" ? "light_mode"
2441
+ : pref === "dark" ? "dark_mode"
2442
+ : "brightness_auto";
2443
+
2444
+ function currentMode() {
2445
+ const v = window.ui("mode");
2446
+ return v === "light" || v === "dark" || v === "auto" ? v : "auto";
2447
+ }
2448
+
2449
+ function syncIcons() {
2450
+ const icon = modeIcon(currentMode());
2451
+ for (const el of document.querySelectorAll("[data-theme-toggle] i")) {
2452
+ el.textContent = icon;
2453
+ }
2454
+ }
2455
+
2456
+ function cycleMode() {
2457
+ const current = currentMode();
2458
+ const next =
2459
+ MODE_CYCLE[(MODE_CYCLE.indexOf(current) + 1) % MODE_CYCLE.length];
2460
+ window.ui("mode", next);
2461
+ syncIcons();
2462
+ }
2463
+
2464
+ const unslugify = (slug) =>
2465
+ slug
2466
+ .split("-")
2467
+ .map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w))
2468
+ .join(" ");
2469
+
2470
+ function fillThemePickers() {
2471
+ const themes = (window.ui("themes") ?? []).slice().sort();
2472
+ for (const menu of document.querySelectorAll("[data-theme-picker]")) {
2473
+ menu.replaceChildren();
2474
+ for (const slug of themes) {
2475
+ const li = document.createElement("li");
2476
+ li.className = "wave round";
2477
+ li.textContent = unslugify(slug);
2478
+ li.dataset.themeSlug = slug;
2479
+ li.addEventListener("click", (e) => {
2480
+ window.ui("theme", slug);
2481
+ e.currentTarget.closest("menu")?.classList.remove("active");
2482
+ });
2483
+ menu.appendChild(li);
2484
+ }
2485
+ }
2486
+ }
2487
+
2488
+ function start() {
2489
+ for (const btn of document.querySelectorAll("[data-theme-toggle]")) {
2490
+ btn.addEventListener("click", cycleMode);
2491
+ }
2492
+ syncIcons();
2493
+ fillThemePickers();
2494
+ }
2495
+
2496
+ // md3.js loads as a module — wait until window.ui is registered.
2497
+ function whenUiReady(fn) {
2498
+ if (typeof window.ui === "function") return fn();
2499
+ const id = setInterval(() => {
2500
+ if (typeof window.ui === "function") {
2501
+ clearInterval(id);
2502
+ fn();
2503
+ }
2504
+ }, 50);
2505
+ }
2506
+
2507
+ if (document.readyState === "loading") {
2508
+ document.addEventListener("DOMContentLoaded", () => whenUiReady(start));
2509
+ } else {
2510
+ whenUiReady(start);
2511
+ }
2512
+ </script>
2513
+ ```
2514
+
2515
+ ---
2516
+
2517
+ ## Breakpoints
2518
+
2519
+ | Name | Min width |
2520
+ | ---- | --------- |
2521
+ | `s` | 600 px |
2522
+ | `m` | 993 px |
2523
+ | `l` | 1240 px |
2524
+
2525
+ Used by `.s` / `.m` / `.l` show-hide and the `.s1`–`.s12` / `.m1`–`.m12` / `.l1`–`.l12` grid spans.
2526
+
2527
+ ---
2528
+
2529
+ ## Authoring conventions
2530
+
2531
+ **Don't fight the framework.** md3 is opinionated by design: semantic HTML + a single component class + composable helpers + design-token overrides. If you find yourself reaching for BEM-style sub-element classes (`.card-header`, `.card-body`), wrapping every tag in extra divs, or writing custom CSS that re-implements a helper, stop — there's almost always a built-in helper for that. The framework is small (≈100 classes) precisely so you can hold all of it in your head and compose instead of layering.
2532
+
2533
+ If a helper doesn't exist for what you need, override a [design token](#design-tokens) at the scope you need it. That's the escape hatch — not a parallel class system.
2534
+
2535
+ Three rules keep markup predictable and styles composable.
2536
+
2537
+ ### ✅ Do
2538
+
2539
+ ```html
2540
+ <!-- One element + N helpers on the same tag -->
2541
+ <button class="border round large">…</button>
2542
+ <div class="field label border">…</div>
2543
+
2544
+ <!-- One <main> per document -->
2545
+ <body>
2546
+ <main class="responsive">…</main>
2547
+ </body>
2548
+
2549
+ <!-- Inline/block elements inside block elements -->
2550
+ <div>
2551
+ <div>block</div>
2552
+ <span>inline</span>
2553
+ </div>
2554
+ ```
2555
+
2556
+ ```css
2557
+ /* Helper composition */
2558
+ .button.border { … }
2559
+
2560
+ /* Direct-child component nesting */
2561
+ .field > input { … }
2562
+ .field > .max { … }
2563
+ ```
2564
+
2565
+ ### 🚫 Don't
2566
+
2567
+ ```html
2568
+ <!-- N elements on one tag -->
2569
+ <div class="field button border">…</div>
2570
+ <button class="chip">…</button> <!-- NO, this collides;
2571
+ use .chip OR <button>, not both as elements -->
2572
+
2573
+ <!-- Custom sub-element class trees -->
2574
+ <div class="card">
2575
+ <div class="card-header">…</div>
2576
+ <div class="card-content">…</div>
2577
+ <div class="card-footer">…</div>
2578
+ </div>
2579
+
2580
+ <!-- Multiple <main> -->
2581
+ <body>
2582
+ <main>…</main>
2583
+ <main>…</main>
2584
+ </body>
2585
+
2586
+ <!-- Block inside inline -->
2587
+ <span>
2588
+ <div>…</div>
2589
+ </span>
2590
+ ```
2591
+
2592
+ ```css
2593
+ /* Don't double-class the same element type */
2594
+ .button.button { … }
2595
+
2596
+ /* Don't descend through unrelated elements */
2597
+ .button .chip { … }
2598
+ ```
2599
+
2600
+ ### Tips
2601
+
2602
+ 1. Reach for [Helpers](#helpers) before writing custom CSS.
2603
+ 2. To customize the theme, override the [Design tokens](#design-tokens).
2604
+ 3. Quick-learn by scanning the per-component class tables.
2605
+ 4. Slider value rendering, textarea auto-resize, and the file/color/password field wiring require the JS runtime. Theme/mode/dialog/menu/snackbar/page work via CSS + `data-ui`.
2606
+ 5. For Vite users, build with `assetsInlineLimit: 0` so the bundled Material Symbols fonts aren't base64-inlined.
2607
+
270
2608
  ---
271
2609
 
272
- ## License
2610
+ ## Repository
273
2611
 
274
- Apache-2.0. See `LICENSE` and `NOTICE`. The published source includes patterns and SCSS structure derived from [beercss](https://github.com/beercss/beercss) (MIT) full attribution in `THIRD-PARTY-NOTICES`.
2612
+ https://github.com/stackific/md3Apache-2.0.