@tenphi/tasty 3.4.0 → 3.6.0

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.
Files changed (83) hide show
  1. package/README.md +147 -129
  2. package/dist/astro-CzY4LCpr.js +437 -0
  3. package/dist/astro-CzY4LCpr.js.map +1 -0
  4. package/dist/{babel-BUQGeOXA.d.ts → babel-DWCsafYw.d.ts} +2 -2
  5. package/dist/{collector-DUaHCcTS.d.ts → collector-BTVXj2hS.d.ts} +21 -3
  6. package/dist/{collector-DTahQUiV.js → collector-C6TtL8HJ.js} +36 -13
  7. package/dist/collector-C6TtL8HJ.js.map +1 -0
  8. package/dist/{config-B5kHzuNz.js → config-B3gPdCqd.js} +305 -153
  9. package/dist/config-B3gPdCqd.js.map +1 -0
  10. package/dist/{config-YsxGv4tq.d.ts → config-LfIDmVHx.d.ts} +132 -32
  11. package/dist/core/index.d.ts +5 -5
  12. package/dist/core/index.js +6 -6
  13. package/dist/{core-Dr4u1NVD.js → core-Bq7w2kti.js} +215 -95
  14. package/dist/core-Bq7w2kti.js.map +1 -0
  15. package/dist/{css-writer-B-J87ncv.js → css-writer-Bgowobbx.js} +3 -3
  16. package/dist/{css-writer-B-J87ncv.js.map → css-writer-Bgowobbx.js.map} +1 -1
  17. package/dist/{format-rules-DKOA-6qu.js → format-rules-rCZ37rqY.js} +2 -2
  18. package/dist/format-rules-rCZ37rqY.js.map +1 -0
  19. package/dist/{hydrate-OeMX99We.js → hydrate-CNOmZprz.js} +3 -3
  20. package/dist/{hydrate-OeMX99We.js.map → hydrate-CNOmZprz.js.map} +1 -1
  21. package/dist/{index-PqN-DIpn.d.ts → index-Bm9ltvdH.d.ts} +38 -16
  22. package/dist/{index-Cd45t5NM.d.ts → index-BmogEzTo.d.ts} +69 -15
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +7 -7
  25. package/dist/index.js.map +1 -1
  26. package/dist/{keyframes-CV8azJf3.js → keyframes-DE-OE76F.js} +45 -3
  27. package/dist/keyframes-DE-OE76F.js.map +1 -0
  28. package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-BcLl1u_d.d.ts} +2 -2
  29. package/dist/{merge-styles-oklji0KB.js → merge-styles-DuoZEsm9.js} +2 -2
  30. package/dist/{merge-styles-oklji0KB.js.map → merge-styles-DuoZEsm9.js.map} +1 -1
  31. package/dist/{resolve-recipes-DTG81rzl.js → resolve-recipes-H9NqOQuP.js} +3 -3
  32. package/dist/{resolve-recipes-DTG81rzl.js.map → resolve-recipes-H9NqOQuP.js.map} +1 -1
  33. package/dist/ssr/astro-client.js +1 -1
  34. package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
  35. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  36. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  37. package/dist/ssr/astro-middleware-extract.d.ts +11 -0
  38. package/dist/ssr/astro-middleware-extract.js +9 -0
  39. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  40. package/dist/ssr/astro-middleware-static.d.ts +3 -1
  41. package/dist/ssr/astro-middleware-static.js +1 -1
  42. package/dist/ssr/astro-middleware.d.ts +3 -1
  43. package/dist/ssr/astro-middleware.js +1 -1
  44. package/dist/ssr/astro.d.ts +45 -3
  45. package/dist/ssr/astro.js +1 -156
  46. package/dist/ssr/index.d.ts +2 -2
  47. package/dist/ssr/index.js +3 -3
  48. package/dist/ssr/index.js.map +1 -1
  49. package/dist/ssr/next.d.ts +1 -1
  50. package/dist/ssr/next.js +4 -4
  51. package/dist/static/index.d.ts +2 -2
  52. package/dist/static/index.js +1 -1
  53. package/dist/zero/babel.d.ts +1 -1
  54. package/dist/zero/babel.js +4 -4
  55. package/dist/zero/babel.js.map +1 -1
  56. package/dist/zero/index.d.ts +1 -1
  57. package/dist/zero/index.js +1 -1
  58. package/dist/zero/next.d.ts +1 -1
  59. package/docs/README.md +13 -8
  60. package/docs/adoption.md +56 -31
  61. package/docs/ai-agents.md +78 -77
  62. package/docs/comparison.md +69 -40
  63. package/docs/configuration.md +97 -87
  64. package/docs/debug.md +4 -2
  65. package/docs/design-system.md +41 -22
  66. package/docs/dsl.md +225 -195
  67. package/docs/getting-started.md +60 -39
  68. package/docs/injector.md +63 -26
  69. package/docs/methodology.md +42 -36
  70. package/docs/migration-v3.md +49 -49
  71. package/docs/plugins.md +37 -33
  72. package/docs/react-api.md +65 -44
  73. package/docs/runtime-benchmarks.md +178 -0
  74. package/docs/ssr.md +124 -61
  75. package/docs/styles.md +1 -1
  76. package/docs/tasty-static.md +137 -104
  77. package/package.json +26 -8
  78. package/dist/collector-DTahQUiV.js.map +0 -1
  79. package/dist/config-B5kHzuNz.js.map +0 -1
  80. package/dist/core-Dr4u1NVD.js.map +0 -1
  81. package/dist/format-rules-DKOA-6qu.js.map +0 -1
  82. package/dist/keyframes-CV8azJf3.js.map +0 -1
  83. package/dist/ssr/astro.js.map +0 -1
package/docs/dsl.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Style DSL Reference
2
2
 
3
- This is the Tasty style language reference the value syntax, state mappings, tokens, units, extending semantics, and special declarations that apply to both runtime `tasty()` and build-time `tastyStatic()`.
3
+ This is the language behind Tasty’s predictable state resolution. Every property can use a state map; later branches declare higher priority, and Tasty compiles them so only one generated selector can match.
4
4
 
5
- For the runtime React API (`tasty()`, hooks, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
5
+ The same value syntax, state maps, tokens, units, extension semantics, and special declarations apply to React `tasty()`—whether rendered on the client or server—and build-time `tastyStatic()`.
6
+
7
+ For the React API (`tasty()`, style functions, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
6
8
 
7
9
  ---
8
10
 
@@ -18,17 +20,28 @@ fill: { '': '#white', hovered: '#gray.05', 'theme=danger': '#red' }
18
20
 
19
21
  #### State Key Types
20
22
 
21
- | Syntax | Example | Generated CSS |
22
- |--------|---------|---------------|
23
- | Boolean modifier | `hovered` | `[data-hovered]` |
24
- | Value modifier | `theme=danger` | `[data-theme="danger"]` |
25
- | Pseudo-class | `:hover` | `:hover` |
26
- | Class selector | `.active` | `.active` |
27
- | Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]` |
28
- | Combined (AND) | `hovered & .active` | `[data-hovered].active` |
29
- | Combined (OR) | `hovered \| focused` | `[data-hovered], [data-focused]` |
30
- | Negated (NOT) | `!disabled` | `:not([data-disabled])` |
31
- | Exclusive (XOR) | `hovered ^ focused` | `[data-hovered]:not([data-focused]), :not([data-hovered])[data-focused]` |
23
+ | Syntax | Example | Generated CSS |
24
+ | ------------------ | ------------------------ | ------------------------------------------------------------------------ |
25
+ | Boolean modifier | `hovered` | `[data-hovered]` |
26
+ | Value modifier | `theme=danger` | `[data-theme="danger"]` |
27
+ | Pseudo-class | `:hover` | `:hover` |
28
+ | Class selector | `.active` | `.active` |
29
+ | Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]` |
30
+ | Combined (AND) | `hovered & .active` | `[data-hovered].active` |
31
+ | Combined (OR) | `hovered \| focused` | `[data-hovered], [data-focused]` |
32
+ | Negated (NOT) | `!disabled` | `:not([data-disabled])` |
33
+ | Exclusive (XOR) | `hovered ^ focused` | `[data-hovered]:not([data-focused]), :not([data-hovered])[data-focused]` |
34
+
35
+ #### Automatic Native States
36
+
37
+ `disabled` and `checked` are built-in automatic states. On a Tasty component, they track the corresponding native prop or attribute, so these pairs express the same component state:
38
+
39
+ | Concise Tasty state | Explicit attribute selector |
40
+ | ------------------- | --------------------------- |
41
+ | `disabled` | `[disabled]` |
42
+ | `checked` | `[checked]` |
43
+
44
+ Prefer the concise form when authoring Tasty component styles. Explicit attribute selectors remain valid and can be useful when targeting arbitrary attributes or when CSS-shaped syntax makes an introductory example easier to recognize.
32
45
 
33
46
  Operator precedence (highest to lowest): `!` (NOT) > `^` (XOR) > `|` (OR) > `&` (AND). Use parentheses to override: `hovered & (pressed ^ focused)`.
34
47
 
@@ -53,14 +66,18 @@ color: { '': '#text', hovered: '#accent' }
53
66
  color: { hovered: '#accent', '': '#text' }
54
67
  ```
55
68
 
56
- The bare `''` default still **receives negation** (it is turned off when a higher-priority state matches). For a value that must always apply as a guaranteed floor — even where a query is *unknown* — use the [`_` fallback floor](#_--fallback-floor) instead. The two can coexist: `''` is the negated default, `_` is the always-on floor. If a map contains only `_` and `''` (no other states), the `''` default is redundant — Tasty keeps the `_` value and drops `''` with a `REDUNDANT_DEFAULT_STATE` warning.
69
+ The bare `''` default still **receives negation** (it is turned off when a higher-priority state matches). For a value that must always apply as a guaranteed floor — even where a query is _unknown_ — use the [`_` fallback floor](#_--fallback-floor) instead. The two can coexist: `''` is the negated default, `_` is the always-on floor. If a map contains only `_` and `''` (no other states), the `''` default is redundant — Tasty keeps the `_` value and drops `''` with a `REDUNDANT_DEFAULT_STATE` warning.
57
70
 
58
71
  ### Sub-element
59
72
 
60
73
  Element styled using a capitalized key. Identified by `data-element` attribute:
61
74
 
62
75
  ```jsx
63
- styles: { Title: { preset: 'h3' } }
76
+ styles: {
77
+ Title: {
78
+ preset: 'h3';
79
+ }
80
+ }
64
81
  // Targets: <div data-element="Title">
65
82
  ```
66
83
 
@@ -70,19 +87,19 @@ Control how a sub-element selector attaches to the root selector using the `$` p
70
87
 
71
88
  Examples below assume the sub-element key is **Cell** (i.e. `[data-element="Cell"]` in CSS):
72
89
 
73
- | Pattern | Result | Description |
74
- |---------|--------|-------------|
75
- | *(none)* | `[data-element="Cell"]` | Descendant (default) |
76
- | `>` | `> [data-element="Cell"]` | Direct child |
77
- | `>Body>` | `> [data-element="Body"] > [data-element="Cell"]` | Chained elements |
78
- | `> Cell` | `> [data-element="Cell"]` | Self-name shorthand — when the trailing element name matches the sub-element's own key, it acts as the placeholder (same as `@`); no duplication |
79
- | `h1` | `h1` | Tag selector (no key injection) |
80
- | `h1 >` | `h1 > [data-element="Cell"]` | Key is direct child of tag |
81
- | `h1 *` | `h1 *` | Any descendant of tag |
82
- | `*` | `*` | All descendants |
83
- | `&::before` | `::before` | Root pseudo (no key); `&` is required |
84
- | `@::before` | `[data-element="Cell"]::before` | Pseudo on the sub-element |
85
- | `>@.active` | `> [data-element="Cell"].active` | Class on the sub-element |
90
+ | Pattern | Result | Description |
91
+ | ----------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
92
+ | _(none)_ | `[data-element="Cell"]` | Descendant (default) |
93
+ | `>` | `> [data-element="Cell"]` | Direct child |
94
+ | `>Body>` | `> [data-element="Body"] > [data-element="Cell"]` | Chained elements |
95
+ | `> Cell` | `> [data-element="Cell"]` | Self-name shorthand — when the trailing element name matches the sub-element's own key, it acts as the placeholder (same as `@`); no duplication |
96
+ | `h1` | `h1` | Tag selector (no key injection) |
97
+ | `h1 >` | `h1 > [data-element="Cell"]` | Key is direct child of tag |
98
+ | `h1 *` | `h1 *` | Any descendant of tag |
99
+ | `*` | `*` | All descendants |
100
+ | `&::before` | `::before` | Root pseudo (no key); `&` is required |
101
+ | `@::before` | `[data-element="Cell"]::before` | Pseudo on the sub-element |
102
+ | `>@.active` | `> [data-element="Cell"].active` | Class on the sub-element |
86
103
 
87
104
  Rules for key injection (`[data-element="..."]`):
88
105
 
@@ -111,7 +128,7 @@ const List = tasty({
111
128
  Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
112
129
 
113
130
  ```jsx
114
- fill: '#purple.5' // → oklch(from var(--purple-color) l c h / .5)
131
+ fill: '#purple.5'; // → oklch(from var(--purple-color) l c h / .5)
115
132
  ```
116
133
 
117
134
  ### Modifier
@@ -146,7 +163,7 @@ fill: '#purple.5';
146
163
  ```
147
164
 
148
165
  The channels are copied over and the alpha slot is written, which asks nothing of
149
- the color beyond *being* a color. That means the suffix works on every one of
166
+ the color beyond _being_ a color. That means the suffix works on every one of
150
167
  these:
151
168
 
152
169
  - a token holding a `color-mix()`, a `light-dark()`, or a `color()` in a space
@@ -171,7 +188,7 @@ that expresses its disabled state once, in `color`, fades everything painted fro
171
188
  `#current` below it with no further declarations.
172
189
 
173
190
  The `color` style also publishes `--current-color` beside every color it sets,
174
- for anything that needs the inherited color *as a color* rather than as the
191
+ for anything that needs the inherited color _as a color_ rather than as the
175
192
  keyword — hand-authored CSS, or a place the keyword will not do. Read it as
176
193
  `$current-color`. It is registered with `initial-value: currentcolor`, so where
177
194
  nothing published it, it still resolves against each element's own color:
@@ -184,7 +201,7 @@ A color that already reads the color it inherits — `#current`, a `#current` fa
184
201
  — is not published into it: resolving such a value a second time at a descendant
185
202
  would fade it twice.
186
203
 
187
- One case needs the wider floor to be considered. A token *defined* as `#current`
204
+ One case needs the wider floor to be considered. A token _defined_ as `#current`
188
205
  and then faded gives relative color syntax a `currentcolor` origin, which Safari
189
206
  supports only from 18:
190
207
 
@@ -200,7 +217,7 @@ through `color-mix()`, which works from Safari 16.2 and composes like every othe
200
217
 
201
218
  ### `#current` composes instead
202
219
 
203
- `#current` is the one exception, and the difference is deliberate. A token *names*
220
+ `#current` is the one exception, and the difference is deliberate. A token _names_
204
221
  a color, so fading it sets its alpha. `currentcolor` is the color an element
205
222
  **inherits**, which an ancestor may already have faded — `#current.4` means "40%
206
223
  of what reaches me":
@@ -266,22 +283,22 @@ configure({
266
283
  },
267
284
  });
268
285
 
269
- fill: '#solid.5'; // → hsl(220 90% 50% / .5)
270
- fill: '#adaptive.5'; // → oklch(from light-dark(…) l c h / .5)
286
+ fill: '#solid.5'; // → hsl(220 90% 50% / .5)
287
+ fill: '#adaptive.5'; // → oklch(from light-dark(…) l c h / .5)
271
288
  ```
272
289
 
273
290
  ---
274
291
 
275
292
  ## Built-in Units
276
293
 
277
- | Unit | Description | Example | CSS Output |
278
- |------|-------------|---------|------------|
279
- | `x` | Gap multiplier | `2x` | `calc(var(--gap) * 2)` |
280
- | `r` | Border radius | `1r` | `var(--radius)` |
281
- | `cr` | Card border radius | `1cr` | `var(--card-radius)` |
282
- | `bw` | Border width | `2bw` | `calc(var(--border-width) * 2)` |
283
- | `ow` | Outline width | `1ow` | `var(--outline-width)` |
284
- | `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)` |
294
+ | Unit | Description | Example | CSS Output |
295
+ | ---- | ------------------ | ------- | ------------------------------- |
296
+ | `x` | Gap multiplier | `2x` | `calc(var(--gap) * 2)` |
297
+ | `r` | Border radius | `1r` | `var(--radius)` |
298
+ | `cr` | Card border radius | `1cr` | `var(--card-radius)` |
299
+ | `bw` | Border width | `2bw` | `calc(var(--border-width) * 2)` |
300
+ | `ow` | Outline width | `1ow` | `var(--outline-width)` |
301
+ | `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)` |
285
302
 
286
303
  You can register additional custom units via [`configure()`](configuration.md#options).
287
304
 
@@ -326,7 +343,7 @@ const ElevatedCard = tasty({
326
343
 
327
344
  **Post-merge recipes (`/` separator):**
328
345
 
329
- Recipes listed after `/` are applied *after* component styles using `mergeStyles`:
346
+ Recipes listed after `/` are applied _after_ component styles using `mergeStyles`:
330
347
 
331
348
  ```jsx
332
349
  const Input = tasty({
@@ -364,8 +381,8 @@ When a style property uses a state map, the merge behavior depends on whether th
364
381
  const MyButton = tasty(Button, {
365
382
  styles: {
366
383
  fill: {
367
- 'loading': '#yellow', // append new state
368
- 'disabled': '#gray.20', // override existing state in place
384
+ loading: '#yellow', // append new state
385
+ disabled: '#gray.20', // override existing state in place
369
386
  },
370
387
  },
371
388
  });
@@ -375,7 +392,7 @@ const MyButton = tasty(Button, {
375
392
  styles: {
376
393
  fill: {
377
394
  '': '#red',
378
- 'hovered': '#blue',
395
+ hovered: '#blue',
379
396
  },
380
397
  },
381
398
  });
@@ -400,8 +417,12 @@ fill: {
400
417
  Use `null` inside a state map to remove a state, or `false` to block it entirely (tombstone):
401
418
 
402
419
  ```jsx
403
- fill: { pressed: null } // removes pressed from the result
404
- fill: { disabled: false } // tombstone — no CSS for disabled, blocks recipe too
420
+ fill: {
421
+ pressed: null;
422
+ } // removes pressed from the result
423
+ fill: {
424
+ disabled: false;
425
+ } // tombstone — no CSS for disabled, blocks recipe too
405
426
  ```
406
427
 
407
428
  ### Resetting Properties with `null` and `false`
@@ -409,36 +430,36 @@ fill: { disabled: false } // tombstone — no CSS for disabled, blocks recipe to
409
430
  ```jsx
410
431
  const SimpleButton = tasty(Button, {
411
432
  styles: {
412
- fill: null, // discard parent's fill, let recipe fill in
433
+ fill: null, // discard parent's fill, let recipe fill in
413
434
  border: false, // no border at all (tombstone — blocks recipe too)
414
435
  },
415
436
  });
416
437
  ```
417
438
 
418
- | Value | Meaning | Recipe fills in? |
419
- |-------|---------|-----------------|
420
- | `undefined` | Not provided — parent preserved | N/A |
421
- | `null` | Intentional unset — parent discarded | Yes |
422
- | `false` | Tombstone — blocks everything | No |
439
+ | Value | Meaning | Recipe fills in? |
440
+ | ----------- | ------------------------------------ | ---------------- |
441
+ | `undefined` | Not provided — parent preserved | N/A |
442
+ | `null` | Intentional unset — parent discarded | Yes |
443
+ | `false` | Tombstone — blocks everything | No |
423
444
 
424
445
  ---
425
446
 
426
447
  ## Advanced States (`@` prefix)
427
448
 
428
- | Prefix | Purpose | Example |
429
- |--------|---------|---------|
430
- | `@media` | Media queries | `@media(w < 768px)` |
431
- | `@(...)` | Container queries | `@(panel, w >= 300px)` |
432
- | `@supports` | Feature/selector support | `@supports(display: grid)` |
433
- | `@root` | Root element states | `@root(schema=dark)` |
434
- | `@parent` | Parent/ancestor element states | `@parent(hovered)` |
435
- | `@own` | Sub-element's own state | `@own(hovered)` |
436
- | `@starting` | Entry animation | `@starting` |
437
- | `_` | Fallback floor (always-on, never negated) | `_` |
438
- | `:is()` | CSS `:is()` structural pseudo-class | `:is(fieldset > label)` |
439
- | `:has()` | CSS `:has()` relational pseudo-class | `:has(> Icon)` |
440
- | `:not()` | CSS `:not()` negation (prefer `!:is()`) | `:not(:first-child)` |
441
- | `:where()` | CSS `:where()` (zero specificity) | `:where(Section)` |
449
+ | Prefix | Purpose | Example |
450
+ | ----------- | ----------------------------------------- | -------------------------- |
451
+ | `@media` | Media queries | `@media(w < 768px)` |
452
+ | `@(...)` | Container queries | `@(panel, w >= 300px)` |
453
+ | `@supports` | Feature/selector support | `@supports(display: grid)` |
454
+ | `@root` | Root element states | `@root(schema=dark)` |
455
+ | `@parent` | Parent/ancestor element states | `@parent(hovered)` |
456
+ | `@own` | Sub-element's own state | `@own(hovered)` |
457
+ | `@starting` | Entry animation | `@starting` |
458
+ | `_` | Fallback floor (always-on, never negated) | `_` |
459
+ | `:is()` | CSS `:is()` structural pseudo-class | `:is(fieldset > label)` |
460
+ | `:has()` | CSS `:has()` relational pseudo-class | `:has(> Icon)` |
461
+ | `:not()` | CSS `:not()` negation (prefer `!:is()`) | `:not(:first-child)` |
462
+ | `:where()` | CSS `:where()` (zero specificity) | `:where(Section)` |
442
463
 
443
464
  > **Specificity.** All state selectors Tasty generates (modifiers, pseudo-classes,
444
465
  > `:is()`/`:not()` groups, and `@root` / `@parent` context) are wrapped in
@@ -454,9 +475,9 @@ const SimpleButton = tasty(Button, {
454
475
  Media queries support dimension shorthands and custom unit expansion:
455
476
 
456
477
  | Shorthand | Expands to |
457
- |-----------|------------|
458
- | `w` | `width` |
459
- | `h` | `height` |
478
+ | --------- | ---------- |
479
+ | `w` | `width` |
480
+ | `h` | `height` |
460
481
 
461
482
  ```jsx
462
483
  fill: {
@@ -467,14 +488,14 @@ fill: {
467
488
  }
468
489
  ```
469
490
 
470
- | Tasty syntax | CSS output |
471
- |--------------|------------|
472
- | `@media(w < 768px)` | `@media (width < 768px)` |
473
- | `@media(600px <= w < 1200px)` | `@media (600px <= width < 1200px)` |
474
- | `@media:print` | `@media print` |
475
- | `@media:screen` | `@media screen` |
491
+ | Tasty syntax | CSS output |
492
+ | ------------------------------------ | ------------------------------------- |
493
+ | `@media(w < 768px)` | `@media (width < 768px)` |
494
+ | `@media(600px <= w < 1200px)` | `@media (600px <= width < 1200px)` |
495
+ | `@media:print` | `@media print` |
496
+ | `@media:screen` | `@media screen` |
476
497
  | `@media(prefers-color-scheme: dark)` | `@media (prefers-color-scheme: dark)` |
477
- | `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
498
+ | `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
478
499
 
479
500
  Custom units work inside media queries: `@media(w < 40x)` → `@media (width < calc(var(--gap) * 40))`.
480
501
 
@@ -484,12 +505,12 @@ In practice, define state aliases via `configure({ states })` and use `@mobile`
484
505
 
485
506
  Container queries use the syntax `@(name, condition)` for named containers or `@(condition)` for the nearest ancestor container. Dimension shorthands (`w`, `h`, `is`, `bs`) are expanded the same way as `@media`.
486
507
 
487
- | Shorthand | Expands to |
488
- |-----------|------------|
489
- | `w` | `width` |
490
- | `h` | `height` |
491
- | `is` | `inline-size` |
492
- | `bs` | `block-size` |
508
+ | Shorthand | Expands to |
509
+ | --------- | ------------- |
510
+ | `w` | `width` |
511
+ | `h` | `height` |
512
+ | `is` | `inline-size` |
513
+ | `bs` | `block-size` |
493
514
 
494
515
  ```jsx
495
516
  const Panel = tasty({
@@ -502,14 +523,14 @@ const Panel = tasty({
502
523
  });
503
524
  ```
504
525
 
505
- | Tasty syntax | CSS output |
506
- |--------------|------------|
507
- | `@(layout, w < 600px)` | `@container layout (width < 600px)` |
508
- | `@(w < 600px)` | `@container (width < 600px)` |
509
- | `@(layout, $variant=danger)` | `@container layout style(--variant: "danger")` |
510
- | `@(layout, $compact)` | `@container layout style(--compact)` |
511
- | `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)` |
512
- | `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)` |
526
+ | Tasty syntax | CSS output |
527
+ | ---------------------------------- | ---------------------------------------------- |
528
+ | `@(layout, w < 600px)` | `@container layout (width < 600px)` |
529
+ | `@(w < 600px)` | `@container (width < 600px)` |
530
+ | `@(layout, $variant=danger)` | `@container layout style(--variant: "danger")` |
531
+ | `@(layout, $compact)` | `@container layout style(--compact)` |
532
+ | `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)` |
533
+ | `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)` |
513
534
 
514
535
  Container style queries use `$prop` (boolean) or `$prop=value` syntax, which maps to CSS `style(--prop)` or `style(--prop: "value")`.
515
536
 
@@ -517,10 +538,10 @@ Container style queries use `$prop` (boolean) or `$prop=value` syntax, which map
517
538
 
518
539
  Feature queries test CSS property support. Use `$` as the first argument to test selector support:
519
540
 
520
- | Tasty syntax | CSS output |
521
- |--------------|------------|
522
- | `@supports(display: grid)` | `@supports (display: grid)` |
523
- | `@supports($, :has(*))` | `@supports selector(:has(*))` |
541
+ | Tasty syntax | CSS output |
542
+ | --------------------------- | --------------------------------- |
543
+ | `@supports(display: grid)` | `@supports (display: grid)` |
544
+ | `@supports($, :has(*))` | `@supports selector(:has(*))` |
524
545
  | `!@supports(display: grid)` | `@supports (not (display: grid))` |
525
546
 
526
547
  ```jsx
@@ -539,7 +560,7 @@ that assumption: `@supports(...)` and `@(...)` queries can be **unknown** (not
539
560
  just true/false), and `not(unknown)` is also unknown — so a negated default
540
561
  branch silently never applies. The classic case is `scroll-state`: a browser can
541
562
  support `container-type: scroll-state` while a specific `scroll-state(...)` query
542
- is unknown, leaving *no* branch active.
563
+ is unknown, leaving _no_ branch active.
543
564
 
544
565
  The `_` fallback floor solves this. Use `_` as a **standalone key** and its value
545
566
  **always applies** as a guaranteed floor: it never receives negation, and
@@ -554,10 +575,14 @@ inset: {
554
575
  ```
555
576
 
556
577
  ```css
557
- .t0.t0 { inset: 0 ...; }
578
+ .t0.t0 {
579
+ inset: 0...;
580
+ }
558
581
  @container scroll-state(scrolled: block-end) {
559
582
  @supports (container-type: scroll-state) {
560
- .t0.t0 { inset: -80px ...; }
583
+ .t0.t0 {
584
+ inset: -80px...;
585
+ }
561
586
  }
562
587
  }
563
588
  ```
@@ -588,13 +613,13 @@ cause is always the same: **`''` is mutually exclusive (turned off by negation),
588
613
  while `_` is always on (layered underneath).**
589
614
 
590
615
  - **Unknown / invalid branches.** If a higher-priority branch sits behind a query
591
- that can be *unknown* (`@supports`, container, `scroll-state`) or uses a
616
+ that can be _unknown_ (`@supports`, container, `scroll-state`) or uses a
592
617
  selector the browser drops, the negated `''` default disappears along with it
593
618
  (`not(unknown) = unknown`), leaving the property with no rule. The `_` floor
594
619
  survives because it is an unconditional bare rule. This is the case `_` exists
595
620
  for.
596
621
 
597
- - **Empty / "unset" states.** A state can intentionally produce *no* output (an
622
+ - **Empty / "unset" states.** A state can intentionally produce _no_ output (an
598
623
  empty value) to leave a property unset while that state is active. With `''`
599
624
  this works — the default is negated away and nothing replaces it, so the
600
625
  property unsets. With `_` the floor can never be turned off, so its value keeps
@@ -606,14 +631,14 @@ while `_` is always on (layered underneath).**
606
631
  ```
607
632
 
608
633
  - **States with different output shapes.** Because `_` layers additively, when
609
- different states emit *different sets* of declarations, the floor's
634
+ different states emit _different sets_ of declarations, the floor's
610
635
  declarations bleed through wherever the winning state does not override the
611
636
  same property — which can produce inconsistent combinations. A `''` default is
612
637
  swapped out cleanly by its mutually-exclusive condition.
613
638
 
614
639
  Rule of thumb: **use `''` for the normal default** (clean mutual exclusivity,
615
640
  supports unsetting), and reach for `_` only when a value must survive an
616
- *unknown* higher-priority branch.
641
+ _unknown_ higher-priority branch.
617
642
 
618
643
  ### `@root(...)` — Root Element States
619
644
 
@@ -629,13 +654,13 @@ color: {
629
654
  }
630
655
  ```
631
656
 
632
- | Tasty syntax | CSS selector |
633
- |--------------|-------------|
634
- | `@root(schema=dark)` | `:root[data-schema="dark"]` |
635
- | `@root(hovered)` | `:root[data-hovered]` |
636
- | `@root(.premium-user)` | `:root.premium-user` |
637
- | `@root([lang="en"])` | `:root[lang="en"]` |
638
- | `!@root(schema=dark)` | `:root:not([data-schema="dark"])` |
657
+ | Tasty syntax | CSS selector |
658
+ | ---------------------- | --------------------------------- |
659
+ | `@root(schema=dark)` | `:root[data-schema="dark"]` |
660
+ | `@root(hovered)` | `:root[data-hovered]` |
661
+ | `@root(.premium-user)` | `:root.premium-user` |
662
+ | `@root([lang="en"])` | `:root[lang="en"]` |
663
+ | `!@root(schema=dark)` | `:root:not([data-schema="dark"])` |
639
664
 
640
665
  Root conditions are prepended to the component selector: `:root[data-schema="dark"] .t0.t0 { ... }`.
641
666
 
@@ -651,7 +676,7 @@ const Nav = tasty({
651
676
  '': '#text',
652
677
  '@own(:hover)': '#primary',
653
678
  '@own(:focus-visible)': '#primary',
654
- 'selected': '#primary', // root-level modifier
679
+ selected: '#primary', // root-level modifier
655
680
  },
656
681
  },
657
682
  },
@@ -659,11 +684,11 @@ const Nav = tasty({
659
684
  });
660
685
  ```
661
686
 
662
- | Tasty syntax (inside sub-element) | CSS output |
663
- |-----------------------------------|------------|
664
- | `@own(:hover)` | `:hover` on the sub-element selector |
665
- | `@own(hovered)` | `[data-hovered]` on the sub-element selector |
666
- | `@own(theme=dark)` | `[data-theme="dark"]` on the sub-element selector |
687
+ | Tasty syntax (inside sub-element) | CSS output |
688
+ | --------------------------------- | ------------------------------------------------- |
689
+ | `@own(:hover)` | `:hover` on the sub-element selector |
690
+ | `@own(hovered)` | `[data-hovered]` on the sub-element selector |
691
+ | `@own(theme=dark)` | `[data-theme="dark"]` on the sub-element selector |
667
692
 
668
693
  `@own()` is only valid inside sub-element styles. Using it on root styles emits a warning and is treated as a regular modifier.
669
694
 
@@ -681,9 +706,9 @@ const FadeIn = tasty({
681
706
  });
682
707
  ```
683
708
 
684
- | Tasty syntax | CSS output |
685
- |--------------|------------|
686
- | `@starting` | `@starting-style { .t0.t0 { ... } }` |
709
+ | Tasty syntax | CSS output |
710
+ | ------------ | ------------------------------------ |
711
+ | `@starting` | `@starting-style { .t0.t0 { ... } }` |
687
712
 
688
713
  ### `@parent(...)` — Parent Element States
689
714
 
@@ -694,22 +719,22 @@ const Highlight = tasty({
694
719
  styles: {
695
720
  fill: {
696
721
  '': '#white',
697
- '@parent(hovered)': '#gray.05', // Any ancestor has [data-hovered]
698
- '@parent(theme=dark, >)': '#dark-02', // Direct parent has [data-theme="dark"]
722
+ '@parent(hovered)': '#gray.05', // Any ancestor has [data-hovered]
723
+ '@parent(theme=dark, >)': '#dark-02', // Direct parent has [data-theme="dark"]
699
724
  },
700
725
  },
701
726
  });
702
727
  ```
703
728
 
704
- | Syntax | CSS Output |
705
- |--------|------------|
706
- | `@parent(hovered)` | `:is([data-hovered] *)` |
707
- | `!@parent(hovered)` | `:not([data-hovered] *)` |
708
- | `@parent(hovered, >)` | `:is([data-hovered] > *)` (direct parent) |
709
- | `@parent(.active)` | `:is(.active *)` |
710
- | `@parent(hovered & focused)` | `:is([data-hovered][data-focused] *)` (same ancestor) |
729
+ | Syntax | CSS Output |
730
+ | ------------------------------------- | -------------------------------------------------------------------- |
731
+ | `@parent(hovered)` | `:is([data-hovered] *)` |
732
+ | `!@parent(hovered)` | `:not([data-hovered] *)` |
733
+ | `@parent(hovered, >)` | `:is([data-hovered] > *)` (direct parent) |
734
+ | `@parent(.active)` | `:is(.active *)` |
735
+ | `@parent(hovered & focused)` | `:is([data-hovered][data-focused] *)` (same ancestor) |
711
736
  | `@parent(hovered) & @parent(focused)` | `:is([data-hovered] *):is([data-focused] *)` (independent ancestors) |
712
- | `@parent(hovered \| focused)` | `:is([data-hovered] *, [data-focused] *)` (OR inside single wrapper) |
737
+ | `@parent(hovered \| focused)` | `:is([data-hovered] *, [data-focused] *)` (OR inside single wrapper) |
713
738
 
714
739
  For sub-elements, the parent check applies to the root element's ancestors:
715
740
 
@@ -740,38 +765,38 @@ const Card = tasty({
740
765
  styles: {
741
766
  display: {
742
767
  '': 'block',
743
- ':has(> Icon)': 'grid', // has Icon as direct child
744
- ':has(+ Icon)': 'grid', // immediately followed by an Icon sibling
745
- ':has(~ Icon)': 'grid', // has an Icon sibling somewhere after
746
- ':has(Icon +)': 'grid', // immediately preceded by an Icon sibling (auto-completes to `Icon + *`)
747
- ':has(Icon ~)': 'grid', // has an Icon sibling somewhere before (auto-completes to `Icon ~ *`)
748
- ':is(fieldset > label)': 'inline', // is a label inside a fieldset (HTML tags)
749
- '!:has(> Icon)': 'flex', // negation: no Icon child
768
+ ':has(> Icon)': 'grid', // has Icon as direct child
769
+ ':has(+ Icon)': 'grid', // immediately followed by an Icon sibling
770
+ ':has(~ Icon)': 'grid', // has an Icon sibling somewhere after
771
+ ':has(Icon +)': 'grid', // immediately preceded by an Icon sibling (auto-completes to `Icon + *`)
772
+ ':has(Icon ~)': 'grid', // has an Icon sibling somewhere before (auto-completes to `Icon ~ *`)
773
+ ':is(fieldset > label)': 'inline', // is a label inside a fieldset (HTML tags)
774
+ '!:has(> Icon)': 'flex', // negation: no Icon child
750
775
  },
751
776
  },
752
777
  });
753
778
  ```
754
779
 
755
- | Syntax | CSS Output | Meaning |
756
- |--------|------------|---------|
757
- | `:has(> Icon)` | `:has(> [data-element="Icon"])` | Has Icon as direct child |
758
- | `:has(+ Icon)` | `:has(+ [data-element="Icon"])` | Immediately followed by an Icon sibling |
759
- | `:has(~ Icon)` | `:has(~ [data-element="Icon"])` | Has an Icon sibling somewhere after |
760
- | `:has(Icon +)` | `:has([data-element="Icon"] + *)` | Immediately preceded by an Icon sibling |
761
- | `:has(Icon ~)` | `:has([data-element="Icon"] ~ *)` | Has an Icon sibling somewhere before |
762
- | `:has(>)` | `:has(> *)` | Has any direct child |
763
- | `:is(> Field + input)` | `:is(> [data-element="Field"] + input)` | Structural match |
764
- | `:has(button)` | `:has(button)` | HTML tag (lowercase, unchanged) |
765
- | `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
766
- | `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
780
+ | Syntax | CSS Output | Meaning |
781
+ | ---------------------- | --------------------------------------- | --------------------------------------- |
782
+ | `:has(> Icon)` | `:has(> [data-element="Icon"])` | Has Icon as direct child |
783
+ | `:has(+ Icon)` | `:has(+ [data-element="Icon"])` | Immediately followed by an Icon sibling |
784
+ | `:has(~ Icon)` | `:has(~ [data-element="Icon"])` | Has an Icon sibling somewhere after |
785
+ | `:has(Icon +)` | `:has([data-element="Icon"] + *)` | Immediately preceded by an Icon sibling |
786
+ | `:has(Icon ~)` | `:has([data-element="Icon"] ~ *)` | Has an Icon sibling somewhere before |
787
+ | `:has(>)` | `:has(> *)` | Has any direct child |
788
+ | `:is(> Field + input)` | `:is(> [data-element="Field"] + input)` | Structural match |
789
+ | `:has(button)` | `:has(button)` | HTML tag (lowercase, unchanged) |
790
+ | `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
791
+ | `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
767
792
 
768
793
  Combine with other states using boolean logic (`&`, `|`, `!`, `^`):
769
794
 
770
795
  ```jsx
771
- ':has(> Icon) & hovered' // AND: structural + data attribute
772
- '@parent(hovered) & :has(> Icon)' // AND: parent check + structural
773
- ':has(> Icon) | :has(> Button)' // OR: either sub-element present
774
- ':has(> Icon) ^ :has(> Button)' // XOR: exactly one present
796
+ ':has(> Icon) & hovered'; // AND: structural + data attribute
797
+ '@parent(hovered) & :has(> Icon)'; // AND: parent check + structural
798
+ ':has(> Icon) | :has(> Button)'; // OR: either sub-element present
799
+ ':has(> Icon) ^ :has(> Button)'; // XOR: exactly one present
775
800
  ```
776
801
 
777
802
  > **Nesting limit:** The state key parser supports up to 2 levels of nested parentheses inside `:is()`, `:has()`, `:not()`, and `:where()` — e.g. `:has(Input:not(:disabled))` works, but 3+ levels like `:has(:is(:not(:hover)))` will not be tokenized correctly. This covers virtually all practical use cases.
@@ -807,7 +832,7 @@ const AnimatedGradient = tasty({
807
832
  styles: {
808
833
  '$gradient-angle': '0deg',
809
834
  '#theme': 'okhst(280 80% 50%)',
810
- background: 'linear-gradient($gradient-angle, #theme, transparent)',
835
+ image: 'linear-gradient($gradient-angle, #theme, #clear)',
811
836
  transition: '$$gradient-angle 0.3s, ##theme 0.3s',
812
837
  },
813
838
  });
@@ -838,7 +863,7 @@ const Heading = tasty({
838
863
  fontDisplay: 'swap',
839
864
  },
840
865
  },
841
- fontFamily: '"Brand Sans", sans-serif',
866
+ font: '"Brand Sans", sans-serif',
842
867
  },
843
868
  });
844
869
  ```
@@ -858,20 +883,20 @@ Supply an array to register several variants of the same family:
858
883
 
859
884
  ### Supported descriptors
860
885
 
861
- | Descriptor | CSS property | Type |
862
- |---|---|---|
863
- | `src` (required) | `src` | `string` |
864
- | `fontWeight` | `font-weight` | `string \| number` |
865
- | `fontStyle` | `font-style` | `string` |
866
- | `fontStretch` | `font-stretch` | `string` |
867
- | `fontDisplay` | `font-display` | `'auto' \| 'block' \| 'swap' \| 'fallback' \| 'optional'` |
868
- | `unicodeRange` | `unicode-range` | `string` |
869
- | `ascentOverride` | `ascent-override` | `string` |
870
- | `descentOverride` | `descent-override` | `string` |
871
- | `lineGapOverride` | `line-gap-override` | `string` |
872
- | `sizeAdjust` | `size-adjust` | `string` |
873
- | `fontFeatureSettings` | `font-feature-settings` | `string` |
874
- | `fontVariationSettings` | `font-variation-settings` | `string` |
886
+ | Descriptor | CSS property | Type |
887
+ | ----------------------- | ------------------------- | --------------------------------------------------------- |
888
+ | `src` (required) | `src` | `string` |
889
+ | `fontWeight` | `font-weight` | `string \| number` |
890
+ | `fontStyle` | `font-style` | `string` |
891
+ | `fontStretch` | `font-stretch` | `string` |
892
+ | `fontDisplay` | `font-display` | `'auto' \| 'block' \| 'swap' \| 'fallback' \| 'optional'` |
893
+ | `unicodeRange` | `unicode-range` | `string` |
894
+ | `ascentOverride` | `ascent-override` | `string` |
895
+ | `descentOverride` | `descent-override` | `string` |
896
+ | `lineGapOverride` | `line-gap-override` | `string` |
897
+ | `sizeAdjust` | `size-adjust` | `string` |
898
+ | `fontFeatureSettings` | `font-feature-settings` | `string` |
899
+ | `fontVariationSettings` | `font-variation-settings` | `string` |
875
900
 
876
901
  > Font-face rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@font-face`.
877
902
 
@@ -899,18 +924,18 @@ const EmojiList = tasty({
899
924
 
900
925
  ### Supported descriptors
901
926
 
902
- | Descriptor | CSS property | Type |
903
- |---|---|---|
904
- | `system` (required) | `system` | `'cyclic' \| 'numeric' \| 'alphabetic' \| 'symbolic' \| 'additive' \| 'fixed' \| string` |
905
- | `symbols` | `symbols` | `string` |
906
- | `additiveSymbols` | `additive-symbols` | `string` |
907
- | `prefix` | `prefix` | `string` |
908
- | `suffix` | `suffix` | `string` |
909
- | `negative` | `negative` | `string` |
910
- | `range` | `range` | `string` |
911
- | `pad` | `pad` | `string` |
912
- | `fallback` | `fallback` | `string` |
913
- | `speakAs` | `speak-as` | `string` |
927
+ | Descriptor | CSS property | Type |
928
+ | ------------------- | ------------------ | ---------------------------------------------------------------------------------------- |
929
+ | `system` (required) | `system` | `'cyclic' \| 'numeric' \| 'alphabetic' \| 'symbolic' \| 'additive' \| 'fixed' \| string` |
930
+ | `symbols` | `symbols` | `string` |
931
+ | `additiveSymbols` | `additive-symbols` | `string` |
932
+ | `prefix` | `prefix` | `string` |
933
+ | `suffix` | `suffix` | `string` |
934
+ | `negative` | `negative` | `string` |
935
+ | `range` | `range` | `string` |
936
+ | `pad` | `pad` | `string` |
937
+ | `fallback` | `fallback` | `string` |
938
+ | `speakAs` | `speak-as` | `string` |
914
939
 
915
940
  > Counter-style rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@counter-style`.
916
941
 
@@ -932,18 +957,18 @@ const Box = tasty({
932
957
  styles: {
933
958
  '@function': {
934
959
  // simplest: one bare param + result (auto-calc, no explicit calc())
935
- '$$negative': { args: ['$value'], result: '(-1 * $value)' },
960
+ $$negative: { args: ['$value'], result: '(-1 * $value)' },
936
961
 
937
962
  // typed param + default + return type + local variable
938
- '$$shadow': {
963
+ $$shadow: {
939
964
  args: { '$shadow-color': { syntax: '<color>', default: 'inherit' } },
940
965
  returns: '<color>',
941
- '$offset': '2px', // local variable
966
+ $offset: '2px', // local variable
942
967
  result: '$offset $offset ($shadow-color, black)',
943
968
  },
944
969
  },
945
- marginTop: '$$negative(10px)', // → margin-top: --negative(10px)
946
- boxShadow: '$$shadow(#accent)', // → box-shadow: --shadow(var(--accent-color))
970
+ margin: '$$negative(10px) top', // → margin: --negative(10px) 0 0 0
971
+ shadow: '$$shadow(#accent)', // → box-shadow: --shadow(var(--accent-color))
947
972
  },
948
973
  });
949
974
  ```
@@ -951,22 +976,27 @@ const Box = tasty({
951
976
  Generated CSS:
952
977
 
953
978
  ```css
954
- @function --negative(--value) { result: calc(-1 * var(--value)); }
979
+ @function --negative(--value) {
980
+ result: calc(-1 * var(--value));
981
+ }
955
982
  @function --shadow(--shadow-color <color>: inherit) returns <color> {
956
983
  --offset: 2px;
957
984
  result: var(--offset) var(--offset) var(--shadow-color, black);
958
985
  }
959
- .t0 { margin-top: --negative(10px); box-shadow: --shadow(var(--accent-color)); }
986
+ .t0 {
987
+ margin: --negative(10px) 0 0 0;
988
+ box-shadow: --shadow(var(--accent-color));
989
+ }
960
990
  ```
961
991
 
962
992
  ### Descriptor shape
963
993
 
964
- | Field | Type | Notes |
965
- |---|---|---|
966
- | `result` (required) | `string` | The `result:` value, parsed through the Tasty DSL. |
967
- | `args` | `string[]` \| `Record<string, FunctionParameter>` | Ordered parameters. Array form lists bare names; object form maps names to type/default. |
968
- | `returns` | `string` | Optional return type, e.g. `'<color>'`. |
969
- | `$name` keys | `string \| number` | Any `$name` key declares a local variable `--name` (value parsed). |
994
+ | Field | Type | Notes |
995
+ | ------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
996
+ | `result` (required) | `string` | The `result:` value, parsed through the Tasty DSL. |
997
+ | `args` | `string[]` \| `Record<string, FunctionParameter>` | Ordered parameters. Array form lists bare names; object form maps names to type/default. |
998
+ | `returns` | `string` | Optional return type, e.g. `'<color>'`. |
999
+ | `$name` keys | `string \| number` | Any `$name` key declares a local variable `--name` (value parsed). |
970
1000
 
971
1001
  A `FunctionParameter` is one of: `true` (bare param), a string CSS type shorthand (`'<length>'`), or `{ syntax?: string; default?: string | number }`.
972
1002
 
@@ -988,4 +1018,4 @@ For a complete reference of all enhanced style properties — syntax, values, mo
988
1018
  - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
989
1019
  - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
990
1020
  - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
991
- - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin
1021
+ - **[Build-Time Extraction (`tastyStatic`)](tasty-static.md)** — Static styling with the Babel plugin