@mlola-ui/engine 1.0.2 → 1.0.4

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/build.mjs CHANGED
@@ -7,6 +7,7 @@ import { renderCatalogCss } from "./src/library-recipes.mjs";
7
7
  import { renderSpecSchema } from "./src/spec.mjs";
8
8
  import { renderThemeCss, renderThemeManifest } from "./src/theme.mjs";
9
9
  import {
10
+ renderAccessibilityCss,
10
11
  renderDtcg,
11
12
  renderFoundationsCss,
12
13
  renderMaterialsCss,
@@ -28,6 +29,7 @@ const layers = {
28
29
  "materials.css": renderMaterialsCss(),
29
30
  "recipes.css": renderRecipesCss(),
30
31
  "motion.css": renderMotionCss(),
32
+ "accessibility.css": renderAccessibilityCss(),
31
33
  };
32
34
 
33
35
  const layerOrder = "mlola.tokens, mlola.foundations, mlola.materials, mlola.recipes, mlola.motion, mlola.accessibility";
@@ -41,6 +43,7 @@ const aggregate = `/* Generated by @mlola-ui/engine. React and Tailwind are not
41
43
  @import "./materials.css" layer(mlola.materials);
42
44
  @import "./recipes.css" layer(mlola.recipes);
43
45
  @import "./motion.css" layer(mlola.motion);
46
+ @import "./accessibility.css" layer(mlola.accessibility);
44
47
  `;
45
48
 
46
49
  const dtcg = `${JSON.stringify(renderDtcg(), null, 2)}\n`;
@@ -0,0 +1,9 @@
1
+ /* Accessibility layer: last in the cascade, so these guarantees hold over any recipe. */
2
+ /* On a touch screen, a text field smaller than 16px makes iOS zoom the page
3
+ when it takes focus, and the page stays zoomed after. Every field that
4
+ takes typing is at least 16px there; everything else keeps its size. */
5
+ @media (pointer: coarse) {
6
+ :is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="submit"], [type="button"], [type="reset"], [type="image"], [type="hidden"]), textarea, select, [contenteditable]:not([contenteditable="false"])) {
7
+ font-size: max(1rem, 1em);
8
+ }
9
+ }
@@ -0,0 +1,419 @@
1
+ {
2
+ "version": 1,
3
+ "description": "The Mlola UI design language as data: tokens by purpose, the rules for new UI, and the composition primitives. Generated with agents.md.",
4
+ "themes": [
5
+ {
6
+ "id": "graphite",
7
+ "name": "Graphite"
8
+ },
9
+ {
10
+ "id": "atelier",
11
+ "name": "Atelier Umami"
12
+ },
13
+ {
14
+ "id": "machined",
15
+ "name": "Machined Titanium"
16
+ },
17
+ {
18
+ "id": "aerogel",
19
+ "name": "Aerogel Glass"
20
+ },
21
+ {
22
+ "id": "nordic",
23
+ "name": "Nordic Earth"
24
+ }
25
+ ],
26
+ "tokens": [
27
+ {
28
+ "group": "Planes and ink",
29
+ "purpose": "Backgrounds, surfaces, the three levels of text, borders.",
30
+ "names": [
31
+ "--ml-background",
32
+ "--ml-background-subtle",
33
+ "--ml-surface",
34
+ "--ml-surface-elevated",
35
+ "--ml-text",
36
+ "--ml-text-muted",
37
+ "--ml-text-faint",
38
+ "--ml-border",
39
+ "--ml-border-subtle"
40
+ ]
41
+ },
42
+ {
43
+ "group": "Color roles",
44
+ "purpose": "Each role is a fill with its `-foreground`, a `-text` for text and marks on the page, and (primary) a `-subtle` tint.",
45
+ "names": [
46
+ "--ml-primary",
47
+ "--ml-primary-foreground",
48
+ "--ml-primary-text",
49
+ "--ml-primary-subtle",
50
+ "--ml-success",
51
+ "--ml-success-foreground",
52
+ "--ml-success-text",
53
+ "--ml-warning",
54
+ "--ml-warning-foreground",
55
+ "--ml-warning-text",
56
+ "--ml-danger",
57
+ "--ml-danger-foreground",
58
+ "--ml-danger-text",
59
+ "--ml-info",
60
+ "--ml-info-foreground",
61
+ "--ml-info-text"
62
+ ]
63
+ },
64
+ {
65
+ "group": "Charts",
66
+ "purpose": "A categorical palette for series, 3:1 on the surface. Never a status.",
67
+ "names": [
68
+ "--ml-chart-1",
69
+ "--ml-chart-2",
70
+ "--ml-chart-3",
71
+ "--ml-chart-4",
72
+ "--ml-chart-5",
73
+ "--ml-chart-6"
74
+ ]
75
+ },
76
+ {
77
+ "group": "Interaction and light",
78
+ "purpose": "Focus, hover and pressed fills, tracks, the veil behind overlays, light and knobs.",
79
+ "names": [
80
+ "--ml-focus",
81
+ "--ml-primary-hover",
82
+ "--ml-fill-hover",
83
+ "--ml-fill-active",
84
+ "--ml-track",
85
+ "--ml-control-border",
86
+ "--ml-ring",
87
+ "--ml-scrim",
88
+ "--ml-highlight",
89
+ "--ml-knob",
90
+ "--ml-knob-shadow",
91
+ "--ml-sheen"
92
+ ]
93
+ },
94
+ {
95
+ "group": "Spacing",
96
+ "purpose": "The only spacing: gaps, padding, margins, offsets.",
97
+ "names": [
98
+ "--ml-space-px",
99
+ "--ml-space-0-5",
100
+ "--ml-space-1",
101
+ "--ml-space-1-5",
102
+ "--ml-space-2",
103
+ "--ml-space-2-5",
104
+ "--ml-space-3",
105
+ "--ml-space-3-5",
106
+ "--ml-space-4",
107
+ "--ml-space-4-5",
108
+ "--ml-space-5",
109
+ "--ml-space-6",
110
+ "--ml-space-7",
111
+ "--ml-space-8",
112
+ "--ml-space-9",
113
+ "--ml-space-10",
114
+ "--ml-space-12",
115
+ "--ml-space-14",
116
+ "--ml-space-16"
117
+ ]
118
+ },
119
+ {
120
+ "group": "Type",
121
+ "purpose": "The only type sizes, line heights, families and weights.",
122
+ "names": [
123
+ "--ml-type-2xs",
124
+ "--ml-type-xs",
125
+ "--ml-type-sm",
126
+ "--ml-type-base",
127
+ "--ml-type-md",
128
+ "--ml-type-lg",
129
+ "--ml-type-xl",
130
+ "--ml-type-2xl",
131
+ "--ml-leading-tight",
132
+ "--ml-leading-snug",
133
+ "--ml-leading-normal",
134
+ "--ml-font-sans",
135
+ "--ml-font-display",
136
+ "--ml-font-mono",
137
+ "--ml-display-weight",
138
+ "--ml-body-leading",
139
+ "--ml-tracking"
140
+ ]
141
+ },
142
+ {
143
+ "group": "Density",
144
+ "purpose": "Control heights, panel padding, the touch target.",
145
+ "names": [
146
+ "--ml-control-sm",
147
+ "--ml-control-md",
148
+ "--ml-control-lg",
149
+ "--ml-panel-padding",
150
+ "--ml-target-min"
151
+ ]
152
+ },
153
+ {
154
+ "group": "Shape",
155
+ "purpose": "Corner radii by the size of the thing, and the border weight.",
156
+ "names": [
157
+ "--ml-radius-xs",
158
+ "--ml-radius-sm",
159
+ "--ml-radius-md",
160
+ "--ml-radius-lg",
161
+ "--ml-radius-pill",
162
+ "--ml-border-width"
163
+ ]
164
+ },
165
+ {
166
+ "group": "Depth and material",
167
+ "purpose": "Elevation, and the material a floating surface is made of.",
168
+ "names": [
169
+ "--ml-texture-opacity",
170
+ "--ml-material",
171
+ "--ml-surface-alpha",
172
+ "--ml-surface-blur",
173
+ "--ml-surface-grain",
174
+ "--ml-surface-highlight",
175
+ "--ml-shadow-xs",
176
+ "--ml-shadow-sm",
177
+ "--ml-shadow-md",
178
+ "--ml-shadow-lg",
179
+ "--ml-shadow-xl",
180
+ "--ml-shadow-tint"
181
+ ]
182
+ },
183
+ {
184
+ "group": "Motion",
185
+ "purpose": "Every transition and entrance.",
186
+ "names": [
187
+ "--ml-duration-fast",
188
+ "--ml-duration-normal",
189
+ "--ml-duration-slow",
190
+ "--ml-duration-reveal",
191
+ "--ml-ease-standard",
192
+ "--ml-ease-spring",
193
+ "--ml-ease-bounce"
194
+ ]
195
+ },
196
+ {
197
+ "group": "Layers",
198
+ "purpose": "The one stacking order.",
199
+ "names": [
200
+ "--ml-layer-raised",
201
+ "--ml-layer-sticky",
202
+ "--ml-layer-header",
203
+ "--ml-layer-dropdown",
204
+ "--ml-layer-overlay",
205
+ "--ml-layer-modal",
206
+ "--ml-layer-popover",
207
+ "--ml-layer-toast",
208
+ "--ml-layer-tooltip",
209
+ "--ml-layer-top"
210
+ ]
211
+ },
212
+ {
213
+ "group": "Icons",
214
+ "purpose": "The theme's icon channel.",
215
+ "names": [
216
+ "--ml-icon-stroke"
217
+ ]
218
+ }
219
+ ],
220
+ "rules": "## Designing new UI in the Mlola language\n\nWhen the elements above do not cover what you need, build it the way they are\nbuilt, and it will look like it belongs.\n\n1. **Compose first.** Reach for a component, then a layout primitive, and\n write CSS only for what neither covers. Put it in a layer of your own,\n declared before `mlola.accessibility` so the accessibility guarantees\n still win:\n `@layer mlola.tokens, mlola.foundations, mlola.materials, mlola.recipes, mlola.motion, app, mlola.accessibility;`\n2. **Name what it is, not how it looks.** One class per element role, with\n your own prefix (not `ml-`, so a later Mlola element never collides).\n State and variant go in `data-*` and `aria-*`, never in a second class.\n Reuse the shared words: `data-tone` is `neutral primary info success\n warning danger`; `data-size` is `xs sm md lg xl`; work that went\n wrong is `error`.\n3. **Color by role, never by value.** Planes are `background`,\n `background-subtle`, `surface`, `surface-elevated`. Ink is `text`,\n `text-muted`, `text-faint`. A colored fill (`primary`, `danger`, …)\n always carries its `-foreground`. Colored text, icons, lines and status\n marks on the page use the `-text` role: fills are only kept 1.5:1 from the\n page, enough for an area, not for meaning. Series use `chart-1`…`chart-6`.\n4. **Measure with the scales.** Spacing from `--ml-space-*`, type from\n `--ml-type-*` with `--ml-leading-*`, control heights from\n `--ml-control-*`, panel padding from `--ml-panel-padding`. A `clamp()`\n between two steps is fine; a value invented between them is not.\n5. **Shape and depth come from the theme.** Radii by the size of the thing\n (`xs` a tag, `sm` a small control, `md` a control or card, `lg` a panel\n or dialog, `pill`). Elevation from `--ml-shadow-*`; a floating surface\n also takes the material: `--ml-surface-alpha`, `--ml-surface-blur`,\n `--ml-surface-highlight`.\n6. **Move with the theme.** Durations from `--ml-duration-*`, easing from\n `--ml-ease-*`. Reduced motion is handled by the engine.\n7. **Stack with the layers.** `--ml-layer-*`, never a raw z-index above 9.\n8. **Keep it usable by hand.** Never remove an outline without a\n `:focus-visible` style in its place. Targets are at least 24px; a smaller\n control adds `data-hit=\"expand\"` for touch. Text is never under\n `--ml-type-2xs`.\n9. **Leave the theme alone.** `data-theme` and `data-mode` belong to the\n engine; never set them for a component's own meaning, and never style a\n theme or mode by name. If something must differ by theme, it is a token.\n",
221
+ "primitives": [
222
+ {
223
+ "name": "ml-actions",
224
+ "note": ""
225
+ },
226
+ {
227
+ "name": "ml-brand",
228
+ "note": ""
229
+ },
230
+ {
231
+ "name": "ml-brand-mark",
232
+ "note": ""
233
+ },
234
+ {
235
+ "name": "ml-brand-name",
236
+ "note": ""
237
+ },
238
+ {
239
+ "name": "ml-chart",
240
+ "note": ""
241
+ },
242
+ {
243
+ "name": "ml-chart-bar",
244
+ "note": ""
245
+ },
246
+ {
247
+ "name": "ml-chart-bars",
248
+ "note": ""
249
+ },
250
+ {
251
+ "name": "ml-chart-heading",
252
+ "note": ""
253
+ },
254
+ {
255
+ "name": "ml-cluster",
256
+ "note": ""
257
+ },
258
+ {
259
+ "name": "ml-definition-list",
260
+ "note": ""
261
+ },
262
+ {
263
+ "name": "ml-display",
264
+ "note": ""
265
+ },
266
+ {
267
+ "name": "ml-divider",
268
+ "note": ""
269
+ },
270
+ {
271
+ "name": "ml-empty-state",
272
+ "note": ""
273
+ },
274
+ {
275
+ "name": "ml-eyebrow",
276
+ "note": ""
277
+ },
278
+ {
279
+ "name": "ml-filter-chip",
280
+ "note": ""
281
+ },
282
+ {
283
+ "name": "ml-filter-group",
284
+ "note": ""
285
+ },
286
+ {
287
+ "name": "ml-fine-print",
288
+ "note": ""
289
+ },
290
+ {
291
+ "name": "ml-form",
292
+ "note": ""
293
+ },
294
+ {
295
+ "name": "ml-form-message",
296
+ "note": ""
297
+ },
298
+ {
299
+ "name": "ml-form-options",
300
+ "note": ""
301
+ },
302
+ {
303
+ "name": "ml-grid",
304
+ "note": ""
305
+ },
306
+ {
307
+ "name": "ml-heading",
308
+ "note": ""
309
+ },
310
+ {
311
+ "name": "ml-icon-chip",
312
+ "note": ""
313
+ },
314
+ {
315
+ "name": "ml-inline-form",
316
+ "note": ""
317
+ },
318
+ {
319
+ "name": "ml-inline-form-field",
320
+ "note": ""
321
+ },
322
+ {
323
+ "name": "ml-label",
324
+ "note": ""
325
+ },
326
+ {
327
+ "name": "ml-lede",
328
+ "note": ""
329
+ },
330
+ {
331
+ "name": "ml-link",
332
+ "note": "A glyph inside a link flows with the text instead of breaking the line."
333
+ },
334
+ {
335
+ "name": "ml-page-shell",
336
+ "note": "A full page: header, main and footer stacked on the page background."
337
+ },
338
+ {
339
+ "name": "ml-person",
340
+ "note": ""
341
+ },
342
+ {
343
+ "name": "ml-person-copy",
344
+ "note": ""
345
+ },
346
+ {
347
+ "name": "ml-positive",
348
+ "note": ""
349
+ },
350
+ {
351
+ "name": "ml-price",
352
+ "note": ""
353
+ },
354
+ {
355
+ "name": "ml-required-mark",
356
+ "note": ""
357
+ },
358
+ {
359
+ "name": "ml-section",
360
+ "note": ""
361
+ },
362
+ {
363
+ "name": "ml-section-description",
364
+ "note": ""
365
+ },
366
+ {
367
+ "name": "ml-section-header",
368
+ "note": ""
369
+ },
370
+ {
371
+ "name": "ml-section-header-centered",
372
+ "note": ""
373
+ },
374
+ {
375
+ "name": "ml-section-muted",
376
+ "note": ""
377
+ },
378
+ {
379
+ "name": "ml-section-shell",
380
+ "note": ""
381
+ },
382
+ {
383
+ "name": "ml-stack",
384
+ "note": ""
385
+ },
386
+ {
387
+ "name": "ml-stat",
388
+ "note": ""
389
+ },
390
+ {
391
+ "name": "ml-stat-card",
392
+ "note": ""
393
+ },
394
+ {
395
+ "name": "ml-stat-grid",
396
+ "note": ""
397
+ },
398
+ {
399
+ "name": "ml-stat-list",
400
+ "note": ""
401
+ },
402
+ {
403
+ "name": "ml-stat-meta",
404
+ "note": ""
405
+ },
406
+ {
407
+ "name": "ml-stat-value",
408
+ "note": ""
409
+ },
410
+ {
411
+ "name": "ml-text-primary",
412
+ "note": ""
413
+ },
414
+ {
415
+ "name": "ml-value",
416
+ "note": ""
417
+ }
418
+ ]
419
+ }
@@ -9,7 +9,7 @@ Generated from the source of truth. Do not edit by hand.
9
9
  - **Style through the classes below, never through invented ones.** A class
10
10
  that is not in this document does not exist. (Mlola Pro's classes are in the
11
11
  guide that comes with Pro source.)
12
- - **Behaviour is optional and framework-free.** `@mlola-ui/behavior` attaches
12
+ - **Behavior is optional and framework-free.** `@mlola-ui/behavior` attaches
13
13
  to markup you already rendered. It never renders anything itself, so it works
14
14
  with React, Svelte, Vue, Rails, a Go template or a static file.
15
15
  - **Native controls stay native.** Checkbox and radio are real `<input>`
@@ -45,9 +45,93 @@ Set `data-theme` and `data-mode` on any ancestor. Nothing else changes.
45
45
  `data-mode` is `light` or `dark`.
46
46
 
47
47
  A project defines its own theme in one file, `mlola.theme.json`, which
48
- overrides fonts, colours, geometry, the spacing and type scale, and any
48
+ overrides fonts, colors, geometry, the spacing and type scale, and any
49
49
  custom property through `extend`. See `mlola.theme.example.json`.
50
50
 
51
+ ## Tokens
52
+
53
+ Every value a design needs is a token. Read them with `var()`; never write
54
+ a color, a size from outside the scales, a shadow or a duration by hand.
55
+ Values change with the theme and the mode, the names never do.
56
+
57
+ - **Planes and ink.** Backgrounds, surfaces, the three levels of text, borders.
58
+ `--ml-background` `--ml-background-subtle` `--ml-surface` `--ml-surface-elevated` `--ml-text` `--ml-text-muted` `--ml-text-faint` `--ml-border` `--ml-border-subtle`
59
+ - **Color roles.** Each role is a fill with its `-foreground`, a `-text` for text and marks on the page, and (primary) a `-subtle` tint.
60
+ `--ml-primary` `--ml-primary-foreground` `--ml-primary-text` `--ml-primary-subtle` `--ml-success` `--ml-success-foreground` `--ml-success-text` `--ml-warning` `--ml-warning-foreground` `--ml-warning-text` `--ml-danger` `--ml-danger-foreground` `--ml-danger-text` `--ml-info` `--ml-info-foreground` `--ml-info-text`
61
+ - **Charts.** A categorical palette for series, 3:1 on the surface. Never a status.
62
+ `--ml-chart-1` `--ml-chart-2` `--ml-chart-3` `--ml-chart-4` `--ml-chart-5` `--ml-chart-6`
63
+ - **Interaction and light.** Focus, hover and pressed fills, tracks, the veil behind overlays, light and knobs.
64
+ `--ml-focus` `--ml-primary-hover` `--ml-fill-hover` `--ml-fill-active` `--ml-track` `--ml-control-border` `--ml-ring` `--ml-scrim` `--ml-highlight` `--ml-knob` `--ml-knob-shadow` `--ml-sheen`
65
+ - **Spacing.** The only spacing: gaps, padding, margins, offsets.
66
+ `--ml-space-px` `--ml-space-0-5` `--ml-space-1` `--ml-space-1-5` `--ml-space-2` `--ml-space-2-5` `--ml-space-3` `--ml-space-3-5` `--ml-space-4` `--ml-space-4-5` `--ml-space-5` `--ml-space-6` `--ml-space-7` `--ml-space-8` `--ml-space-9` `--ml-space-10` `--ml-space-12` `--ml-space-14` `--ml-space-16`
67
+ - **Type.** The only type sizes, line heights, families and weights.
68
+ `--ml-type-2xs` `--ml-type-xs` `--ml-type-sm` `--ml-type-base` `--ml-type-md` `--ml-type-lg` `--ml-type-xl` `--ml-type-2xl` `--ml-leading-tight` `--ml-leading-snug` `--ml-leading-normal` `--ml-font-sans` `--ml-font-display` `--ml-font-mono` `--ml-display-weight` `--ml-body-leading` `--ml-tracking`
69
+ - **Density.** Control heights, panel padding, the touch target.
70
+ `--ml-control-sm` `--ml-control-md` `--ml-control-lg` `--ml-panel-padding` `--ml-target-min`
71
+ - **Shape.** Corner radii by the size of the thing, and the border weight.
72
+ `--ml-radius-xs` `--ml-radius-sm` `--ml-radius-md` `--ml-radius-lg` `--ml-radius-pill` `--ml-border-width`
73
+ - **Depth and material.** Elevation, and the material a floating surface is made of.
74
+ `--ml-texture-opacity` `--ml-material` `--ml-surface-alpha` `--ml-surface-blur` `--ml-surface-grain` `--ml-surface-highlight` `--ml-shadow-xs` `--ml-shadow-sm` `--ml-shadow-md` `--ml-shadow-lg` `--ml-shadow-xl` `--ml-shadow-tint`
75
+ - **Motion.** Every transition and entrance.
76
+ `--ml-duration-fast` `--ml-duration-normal` `--ml-duration-slow` `--ml-duration-reveal` `--ml-ease-standard` `--ml-ease-spring` `--ml-ease-bounce`
77
+ - **Layers.** The one stacking order.
78
+ `--ml-layer-raised` `--ml-layer-sticky` `--ml-layer-header` `--ml-layer-dropdown` `--ml-layer-overlay` `--ml-layer-modal` `--ml-layer-popover` `--ml-layer-toast` `--ml-layer-tooltip` `--ml-layer-top`
79
+ - **Icons.** The theme's icon channel.
80
+ `--ml-icon-stroke`
81
+
82
+ ## Designing new UI in the Mlola language
83
+
84
+ When the elements above do not cover what you need, build it the way they are
85
+ built, and it will look like it belongs.
86
+
87
+ 1. **Compose first.** Reach for a component, then a layout primitive, and
88
+ write CSS only for what neither covers. Put it in a layer of your own,
89
+ declared before `mlola.accessibility` so the accessibility guarantees
90
+ still win:
91
+ `@layer mlola.tokens, mlola.foundations, mlola.materials, mlola.recipes, mlola.motion, app, mlola.accessibility;`
92
+ 2. **Name what it is, not how it looks.** One class per element role, with
93
+ your own prefix (not `ml-`, so a later Mlola element never collides).
94
+ State and variant go in `data-*` and `aria-*`, never in a second class.
95
+ Reuse the shared words: `data-tone` is `neutral primary info success
96
+ warning danger`; `data-size` is `xs sm md lg xl`; work that went
97
+ wrong is `error`.
98
+ 3. **Color by role, never by value.** Planes are `background`,
99
+ `background-subtle`, `surface`, `surface-elevated`. Ink is `text`,
100
+ `text-muted`, `text-faint`. A colored fill (`primary`, `danger`, …)
101
+ always carries its `-foreground`. Colored text, icons, lines and status
102
+ marks on the page use the `-text` role: fills are only kept 1.5:1 from the
103
+ page, enough for an area, not for meaning. Series use `chart-1`…`chart-6`.
104
+ 4. **Measure with the scales.** Spacing from `--ml-space-*`, type from
105
+ `--ml-type-*` with `--ml-leading-*`, control heights from
106
+ `--ml-control-*`, panel padding from `--ml-panel-padding`. A `clamp()`
107
+ between two steps is fine; a value invented between them is not.
108
+ 5. **Shape and depth come from the theme.** Radii by the size of the thing
109
+ (`xs` a tag, `sm` a small control, `md` a control or card, `lg` a panel
110
+ or dialog, `pill`). Elevation from `--ml-shadow-*`; a floating surface
111
+ also takes the material: `--ml-surface-alpha`, `--ml-surface-blur`,
112
+ `--ml-surface-highlight`.
113
+ 6. **Move with the theme.** Durations from `--ml-duration-*`, easing from
114
+ `--ml-ease-*`. Reduced motion is handled by the engine.
115
+ 7. **Stack with the layers.** `--ml-layer-*`, never a raw z-index above 9.
116
+ 8. **Keep it usable by hand.** Never remove an outline without a
117
+ `:focus-visible` style in its place. Targets are at least 24px; a smaller
118
+ control adds `data-hit="expand"` for touch. Text is never under
119
+ `--ml-type-2xs`.
120
+ 9. **Leave the theme alone.** `data-theme` and `data-mode` belong to the
121
+ engine; never set them for a component's own meaning, and never style a
122
+ theme or mode by name. If something must differ by theme, it is a token.
123
+
124
+ ## Composition primitives
125
+
126
+ Compose pages from these before writing layout CSS: sections, stacks,
127
+ clusters, grids, headings, forms, stats. They read the same tokens as every
128
+ component, so a page built from them themes with the rest.
129
+
130
+ `.ml-actions` `.ml-brand` `.ml-brand-mark` `.ml-brand-name` `.ml-chart` `.ml-chart-bar` `.ml-chart-bars` `.ml-chart-heading` `.ml-cluster` `.ml-definition-list` `.ml-display` `.ml-divider` `.ml-empty-state` `.ml-eyebrow` `.ml-filter-chip` `.ml-filter-group` `.ml-fine-print` `.ml-form` `.ml-form-message` `.ml-form-options` `.ml-grid` `.ml-heading` `.ml-icon-chip` `.ml-inline-form` `.ml-inline-form-field` `.ml-label` `.ml-lede` `.ml-link` `.ml-page-shell` `.ml-person` `.ml-person-copy` `.ml-positive` `.ml-price` `.ml-required-mark` `.ml-section` `.ml-section-description` `.ml-section-header` `.ml-section-header-centered` `.ml-section-muted` `.ml-section-shell` `.ml-stack` `.ml-stat` `.ml-stat-card` `.ml-stat-grid` `.ml-stat-list` `.ml-stat-meta` `.ml-stat-value` `.ml-text-primary` `.ml-value`
131
+
132
+ - `.ml-link` — A glyph inside a link flows with the text instead of breaking the line.
133
+ - `.ml-page-shell` — A full page: header, main and footer stacked on the page background.
134
+
51
135
  ## Elements and their attributes
52
136
 
53
137
  Emit these classes and attributes from any language and the visuals are
@@ -198,11 +282,11 @@ correct. This table is read out of the stylesheet, so it is never stale.
198
282
  | `.ml-toggle-button` | `data-state` | `on` |
199
283
  | `.ml-tooltip` | `data-side` | `bottom`, `left`, `right`, `top` |
200
284
  | `.ml-tour-button` | `data-primary` | _presence only_ |
201
- | `.ml-tour-card` | `data-centred` | _presence only_ |
285
+ | `.ml-tour-card` | `data-centered` | _presence only_ |
202
286
  | `.ml-tour-dot` | `data-active` | _presence only_ |
203
287
  | `.ml-tour-scrim` | `data-spotlight` | _presence only_ |
204
288
 
205
- ## Behaviour
289
+ ## Behavior
206
290
 
207
291
  ### accordion
208
292
 
@@ -316,7 +400,7 @@ A short label shown on hover or focus.
316
400
  - <kbd>Escape</kbd>: Hide the tooltip.
317
401
  - State changes:
318
402
  - On pointer enter or focus the trigger, set visible to after the delay.
319
- - On pointer leave or blur, set hidden to immediately, cancelling any pending delay.
403
+ - On pointer leave or blur, set hidden to immediately, canceling any pending delay.
320
404
  - Note: Never put essential information or interactive content in a tooltip.
321
405
 
322
406
  ### toast
@@ -365,5 +449,5 @@ A single value chosen from a range.
365
449
 
366
450
  Prefer emitting less. A plain `<button class="ml-button">` is correct; a
367
451
  button with a variant this document does not list is not. When a component
368
- needs behaviour, mark its root with `data-ml="<behaviour name>"` and let the
452
+ needs behavior, mark its root with `data-ml="<behavior name>"` and let the
369
453
  runtime attach, rather than writing event handlers that guess at the contract.