@tenphi/tasty 3.10.0 → 3.11.1

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 (88) hide show
  1. package/README.md +403 -380
  2. package/dist/{babel-types-C_7kRP38.d.ts → babel-types-BKDTT4--.d.ts} +2 -2
  3. package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
  4. package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
  5. package/dist/chunks/{build-config-l2Ys-AJr.js → build-config-5d8WlQbq.js} +5 -5
  6. package/dist/chunks/{build-config-l2Ys-AJr.js.map → build-config-5d8WlQbq.js.map} +1 -1
  7. package/dist/chunks/{collector-CaNtsRtn.js → collector-CjCak4iE.js} +4 -4
  8. package/dist/chunks/{collector-CaNtsRtn.js.map → collector-CjCak4iE.js.map} +1 -1
  9. package/dist/chunks/{config-engine-CpExZO_T.js → config-engine-uOFYZidB.js} +4 -4
  10. package/dist/chunks/{config-engine-CpExZO_T.js.map → config-engine-uOFYZidB.js.map} +1 -1
  11. package/dist/chunks/config-file-DlZXJMj8.js +25 -0
  12. package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
  13. package/dist/chunks/{css-definitions-DAIJxzFY.js → css-definitions-ubrHP97m.js} +88 -77
  14. package/dist/chunks/css-definitions-ubrHP97m.js.map +1 -0
  15. package/dist/chunks/{debug-CKy49GEr.js → debug-CJ31U_WE.js} +6 -6
  16. package/dist/chunks/{debug-CKy49GEr.js.map → debug-CJ31U_WE.js.map} +1 -1
  17. package/dist/chunks/{dsl-DRhFWQzo.js → dsl-DYSJc1-T.js} +11 -6
  18. package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
  19. package/dist/chunks/{react-runtime-oi6i3ELR.js → react-runtime-CyyCI02N.js} +168 -61
  20. package/dist/chunks/react-runtime-CyyCI02N.js.map +1 -0
  21. package/dist/chunks/{runtime-engine-DoULyhPt.js → runtime-engine-BQmOu5-W.js} +53 -36
  22. package/dist/chunks/runtime-engine-BQmOu5-W.js.map +1 -0
  23. package/dist/chunks/{runtime-state-CZ2J5QiZ.js → runtime-state-DEsyUMU-.js} +14 -3
  24. package/dist/chunks/runtime-state-DEsyUMU-.js.map +1 -0
  25. package/dist/chunks/{style-engine-CeKsjtIr.js → style-engine-BX2LshDZ.js} +116 -56
  26. package/dist/chunks/style-engine-BX2LshDZ.js.map +1 -0
  27. package/dist/chunks/{zero-engine-fxKu8Jbn.js → zero-engine-Dw0P4yGM.js} +22 -7
  28. package/dist/chunks/zero-engine-Dw0P4yGM.js.map +1 -0
  29. package/dist/{collector-w4mBj5gd.d.ts → collector-CTU-nl0g.d.ts} +2 -2
  30. package/dist/{config-CrZOfNJ5.d.ts → config-CcxqSZI2.d.ts} +28 -9
  31. package/dist/core/index.d.ts +4 -4
  32. package/dist/core/index.js +7 -7
  33. package/dist/hydrate-CRyjjAyl.d.ts +22 -0
  34. package/dist/{index-CYJtAI9a.d.ts → index--GdqCPlp.d.ts} +58 -16
  35. package/dist/index.d.ts +4 -4
  36. package/dist/index.js +8 -8
  37. package/dist/{merge-styles-VXwJ0Frr.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -2
  38. package/dist/ssr/astro.js +4 -4
  39. package/dist/ssr/client.d.ts +2 -0
  40. package/dist/ssr/client.js +2 -0
  41. package/dist/ssr/index.d.ts +3 -22
  42. package/dist/ssr/index.js +4 -4
  43. package/dist/ssr/index.js.map +1 -1
  44. package/dist/ssr/next-config.d.ts +1 -1
  45. package/dist/ssr/next-config.js +6 -17
  46. package/dist/ssr/next-config.js.map +1 -1
  47. package/dist/ssr/next.d.ts +1 -1
  48. package/dist/ssr/next.js +3 -3
  49. package/dist/static/index.d.ts +2 -2
  50. package/dist/{types-JEY8PVYP.d.ts → types-DJEM_BOJ.d.ts} +6 -6
  51. package/dist/zero/babel.d.ts +1 -1
  52. package/dist/zero/babel.js +30 -19
  53. package/dist/zero/babel.js.map +1 -1
  54. package/dist/zero/index.d.ts +24 -6
  55. package/dist/zero/index.js +4 -4
  56. package/dist/zero/index.js.map +1 -1
  57. package/dist/zero/next.d.ts +1 -1
  58. package/dist/zero/next.js +3 -7
  59. package/dist/zero/next.js.map +1 -1
  60. package/docs/README.md +4 -1
  61. package/docs/adoption.md +25 -17
  62. package/docs/ai-agents.md +39 -34
  63. package/docs/comparison.md +9 -8
  64. package/docs/configuration.md +174 -58
  65. package/docs/debug.md +3 -2
  66. package/docs/design-system.md +117 -15
  67. package/docs/dsl.md +145 -81
  68. package/docs/getting-started.md +81 -24
  69. package/docs/injector.md +41 -40
  70. package/docs/methodology.md +46 -21
  71. package/docs/migration-v3.md +3 -3
  72. package/docs/pipeline.md +19 -16
  73. package/docs/plugins.md +21 -41
  74. package/docs/react-api.md +61 -9
  75. package/docs/runtime-benchmarks.md +4 -4
  76. package/docs/ssr.md +92 -66
  77. package/docs/styles.md +33 -17
  78. package/docs/tasty-static.md +129 -58
  79. package/package.json +23 -9
  80. package/dist/chunks/async-storage-CG5HnUT5.js +0 -20
  81. package/dist/chunks/async-storage-CG5HnUT5.js.map +0 -1
  82. package/dist/chunks/css-definitions-DAIJxzFY.js.map +0 -1
  83. package/dist/chunks/dsl-DRhFWQzo.js.map +0 -1
  84. package/dist/chunks/react-runtime-oi6i3ELR.js.map +0 -1
  85. package/dist/chunks/runtime-engine-DoULyhPt.js.map +0 -1
  86. package/dist/chunks/runtime-state-CZ2J5QiZ.js.map +0 -1
  87. package/dist/chunks/style-engine-CeKsjtIr.js.map +0 -1
  88. package/dist/chunks/zero-engine-fxKu8Jbn.js.map +0 -1
package/docs/ai-agents.md CHANGED
@@ -10,6 +10,8 @@ Notation: ❌ wrong → ✅ correct.
10
10
 
11
11
  Color tokens (`#name`), custom properties (`$name`), `preset` names, `recipe` names, state aliases (`@mobile`) and extra units are **project-defined** in `tasty.config.ts` or a `configure({ … })` call. Look them up before writing styles. Never invent a name — reuse an existing one, or add it to the config.
12
12
 
13
+ Only Tasty's built-ins exist without config: `#white`, `#black`, `#border`, `#outline`, `#current`, `#clear`, and `$gap`, `$radius`, `$border-width`, `$outline-width`, `$transition`, `$sharp-radius`, `$bold-font-weight`, `$font-sans-fallback`, `$font-mono-fallback`.
14
+
13
15
  ## 1. Where styles go
14
16
 
15
17
  ```jsx
@@ -64,19 +66,20 @@ The last row goes the other way — longhands over the shorthand — because `fl
64
66
 
65
67
  ### Tokens
66
68
 
67
- | Syntax | Meaning |
68
- | --------------------- | ------------------------------------------------------------------------- |
69
- | `#name` | color token → `var(--name-color)` |
70
- | `#name.50` | token at 50% opacity (`0`–`100`) |
71
- | `#name.$opacity` | opacity from a custom property |
72
- | `#clear` / `#current` | `transparent` / `currentcolor` |
73
- | `$name` | custom property → `var(--name)` |
74
- | `(#a, #b)` | fallback chain |
75
- | `$$name` / `##name` | the custom property _name_ (not its value) — inside `transition` |
76
- | `$$name(…)` | call a CSS `@function` declared via `@function`, `useFunction`, or config |
77
-
78
- - ❌ `#f5f5f5`, `rgb(0 0 0)`, `oklch(…)`, `okhsl(…)`, `red` → ✅ `#surface` (add the token to the config if it doesn't exist).
69
+ | Syntax | Meaning |
70
+ | --------------------- | -------------------------------------------------------------------------------------------------- |
71
+ | `#name` | color token → `var(--name-color)` |
72
+ | `#name.50` | token at 50% opacity — digits are a decimal fraction: `.5` = `.50` = 50%, `.05` = 5%, `.100` = 10% |
73
+ | `#name.$opacity` | opacity from a custom property |
74
+ | `#clear` / `#current` | `transparent` / `currentcolor` |
75
+ | `$name` | custom property → `var(--name)` |
76
+ | `(#a, #b)` | fallback chain |
77
+ | `$$name` / `##name` | the custom property _name_ (not its value) — inside `transition` |
78
+ | `$$name(…)` | call a CSS `@function` declared via `@function`, `useFunction`, or config |
79
+
80
+ - ❌ `#f5f5f5`, `rgb(0 0 0)`, `oklch(…)`, `okhsl(…)`, `okhst(…)`, `red` → ✅ `#surface` (add the token to the config if it doesn't exist).
79
81
  - ❌ `var(--gap)` → ✅ `$gap` · ❌ `$accent-color` → ✅ `#accent` · ❌ `transparent` → ✅ `#clear` · ❌ `currentColor` → ✅ `#current`.
82
+ - `#clear` is a default replace token for `transparent`, so `#clear.5` and `#clear.$opacity` stay transparent. It does not read `--clear-color`; override it through `replaceTokens` if needed.
80
83
  - A `$name` must be declared as a `'$name': value` key in the same styles object, or in the config.
81
84
  - Names are case-sensitive and start lowercase — `$myVar` → `--myVar`; a leading capital folds (`$Foo` → `--foo`). Prefer kebab-case.
82
85
  - A `$name` reference is untyped: it fills the first free slot in a shorthand (`border: '1bw $my-style'` → the line style). Write colors as `#name`; `$name-color` is a fallback for pointing at a raw CSS custom property that holds a color.
@@ -84,7 +87,7 @@ The last row goes the other way — longhands over the shorthand — because `fl
84
87
 
85
88
  ### Units
86
89
 
87
- Tasty units: `x` (gap multiple), `r` (radius), `cr` (card radius), `bw` (border width), `ow` (outline width), `sf` (`minmax(0, Nfr)`). All CSS units work too, plus anything in `units` in the config. Any other unit is an error.
90
+ Tasty units and their defaults: `x` 8px spacing step, `r` 6px radius, `cr` 10px card radius, `bw` 1px border width, `ow` 3px outline width, `sf` → `minmax(0, Nfr)`. They compile to literal values; change them through `units` in the config, not with `$gap`/`$radius` tokens. All CSS units work too, plus anything in `units` in the config. Any other unit is an error. Inside `@media(…)` / `@(…)` conditions units resolve to their literal length (`@media(w < 40x)` → `width < 320px`), unless the project points that unit at a custom property; then write query lengths in `px`, `em`, or `rem`.
88
91
 
89
92
  Prefer units over raw pixels: `8px` → `1x` … `64px` → `8x`; `radius: '6px'` → `'1r'`; `border: '1px …'` → `'1bw …'`.
90
93
 
@@ -94,7 +97,7 @@ Prefer units over raw pixels: `8px` → `1x` … `64px` → `8x`; `radius: '6px'
94
97
 
95
98
  ### `true`
96
99
 
97
- `true` means "the design-system default" and is accepted **only** by: `border`, `radius`, `outline`, `shadow`, `padding`, `margin`, `gap`, `inset`, `width`, `height`, `fill`, `color`, `fade`, `preset`, `font`, `scrollbar`, `scrollMargin`, `scrollPadding`, and the enhanced logical category styles listed above, plus `hide`. Anywhere else it is an error.
100
+ `true` means "the design-system default" and is accepted **only** by: `border`, `radius`, `outline`, `shadow`, `padding`, `margin`, `gap`, `inset`, `width`, `height`, `color`, `fade`, `preset`, `font`, `scrollbar`, `scrollMargin`, `scrollPadding`, and the enhanced logical category styles listed above, plus `hide`. Anywhere else it is an error.
98
101
 
99
102
  ### `!important`
100
103
 
@@ -104,21 +107,23 @@ Never use it. Tasty owns specificity through doubled selectors and state orderin
104
107
 
105
108
  A value is `[values…] [modifiers…]`, and several groups can be comma-separated (later groups override earlier ones). A group that names **direction** modifiers takes a **single** value, applied to every direction it names — per-side values come from comma groups. A group naming no direction keeps plain CSS shorthand order (1–4 values). (`inset` + `dock` is the one exception: a second value insets the spanned sides.) Only the modifiers a property knows are valid:
106
109
 
107
- | Property | Modifiers |
108
- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
109
- | `padding`, `margin`, `inset`, `fade` | `top` `right` `bottom` `left` |
110
- | `border` | the four directions + `solid` `dashed` `dotted` `double` `groove` `ridge` `inset` `outset` `none` `hidden` |
111
- | logical axis spacing/inset styles | `start` `end` |
112
- | `blockBorder`, `inlineBorder` | `start` `end` + the border style keywords above |
113
- | `outline` | the style keywords above |
114
- | `radius` | `top` `right` `bottom` `left` + shapes `round` `ellipse` `leaf` `backleaf` |
115
- | `width`, `height`, `blockSize`, `inlineSize` | `min` `max` `fixed` |
116
- | `flow` | `row` `column` `row-reverse` `column-reverse` `wrap` `nowrap` `dense` |
117
- | `overflow` | `visible` `hidden` `scroll` `clip` `auto` `overlay` |
118
- | `position` | `static` `relative` `absolute` `fixed` `sticky` |
119
- | `shadow` | `inset` |
120
- | `preset` | `name / strong` (or `bold`) `italic` `icon` `tight` |
121
- | box properties | `longhand` — emit CSS longhands instead of the shorthand |
110
+ | Property | Modifiers |
111
+ | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
112
+ | `padding`, `margin`, `fade` | `top` `right` `bottom` `left` |
113
+ | `inset` | the four directions + `dock` |
114
+ | `border` | the four directions + `solid` `dashed` `dotted` `double` `groove` `ridge` `inset` `outset` `none` `hidden` |
115
+ | logical axis spacing/inset styles | `start` `end` |
116
+ | `blockBorder`, `inlineBorder` | `start` `end` + the border style keywords above |
117
+ | `outline` | the style keywords above |
118
+ | `radius` | `top` `right` `bottom` `left`, corners `top-left` `top-right` `bottom-right` `bottom-left`, `inherit` + shapes `round` `ellipse` `leaf` `backleaf` |
119
+ | `width`, `height`, `blockSize`, `inlineSize` | `min` `max` `fixed` |
120
+ | `flow` | `row` `column` `row-reverse` `column-reverse` `wrap` `nowrap` `dense` |
121
+ | `overflow` | `visible` `hidden` `scroll` `clip` `auto` `overlay` |
122
+ | `position` | `static` `relative` `absolute` `fixed` `sticky` |
123
+ | `shadow` | `inset` |
124
+ | `preset` | `name / strong` (or `bold`) `italic` `icon` `tight` `normal` |
125
+ | `scrollbar` | `thin` `auto` `none` `stable` `both-edges` `always` |
126
+ | box properties | `longhand` — emit CSS longhands instead of the shorthand |
122
127
 
123
128
  Directional modifiers beat placeholder zeros:
124
129
 
@@ -128,11 +133,11 @@ Directional modifiers beat placeholder zeros:
128
133
  - ❌ `padding: '2x 4x top right'` — a directional group takes one value, so `4x` is dropped → ✅ `padding: '2x top, 4x right'`
129
134
  - ❌ `fade: '3x 1x top bottom'` → ✅ `fade: '3x top, 1x bottom'`
130
135
 
131
- Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `rowGap`, `opacity`, `zIndex`, `order`, `flexGrow`, `flexShrink`, `flexBasis`, `aspectRatio`, `lineClamp`, and `tabSize`. Logical axis category styles accept one or two values in start/end order and the `start`, `end`, and `longhand` modifiers. `fill` and `color` take a color (plus `none` / `transparent`); `caretColor` and `accentColor` take a color only.
136
+ Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `rowGap`, `opacity`, `zIndex`, `order`, `flexGrow`, `flexShrink`, `flexBasis`, `aspectRatio`, `lineClamp`, and `tabSize`. Logical axis category styles accept one or two values in start/end order and the `start`, `end`, and `longhand` modifiers. `fill`, `color`, `caretColor`, and `accentColor` take a color only (use `#clear` for transparent).
132
137
 
133
138
  ### `transition`
134
139
 
135
- Use semantic names, not CSS property names: `fade` `fill` `color` `theme` `border` `radius` `shadow` `outline` `preset` `text` `gap` `opacity` `translate` `rotate` `scale` `filter` `image` `background` `width` `height` `zIndex` `inset` `flow` `dimension`.
140
+ Use semantic names, not CSS property names: `fade` `fill` `color` `theme` `border` `radius` `shadow` `outline` `preset` `text` `gap` `opacity` `translate` `rotate` `scale` `filter` `image` `background` `width` `height` `zIndex` `inset`.
136
141
 
137
142
  ❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill'`
138
143
 
@@ -168,13 +173,13 @@ Precedence `!` > `^` > `|` > `&`; use parentheses to override.
168
173
 
169
174
  Rules:
170
175
 
171
- 1. **`''` comes first.** The bare default is the lowest-priority state; placing it later would override everything above it.
176
+ 1. **`''` comes first.** The bare default is the lowest-priority state. Written later, it triggers a `MISPLACED_DEFAULT_STATE` warning and Tasty moves it to the front.
172
177
  2. **Every state map needs `''` or `_`** — except when extending (`tasty(Parent, …)`), where omitting `''` merges into the parent's states and including `''` replaces them wholesale.
173
178
  3. **`_` is standalone-only** and always first (with `''` right after it, if present). `_` is a never-negated fallback floor for cases where a higher-priority branch may be _unknown_ (`@supports`, container queries). If a map contains only `_` and `''`, drop the `''`.
174
179
  4. **No nested maps:** ❌ `{ hovered: { pressed: 'x' } }` → ✅ `{ 'hovered & pressed': 'x' }`
175
180
  5. **State keys never sit at the top level** of a styles object — `:hover`, `.active`, `[open]` belong inside a property value.
176
181
  6. **Write keys Tasty can fully read.** Names start with a letter (a single letter is fine: `x` → `[data-x]`); join states with an operator (`hovered & focused`, not `hovered focused`); no space between `@media` and `(`. Tasty warns (`INVALID_STATE_KEY`) and skips what it cannot read.
177
- 7. **Prefer automatic native states:** use `disabled` and `checked` in Tasty component styles instead of `[disabled]` and `[checked]`. The concise states track the corresponding native prop or attribute automatically.
182
+ 7. **Prefer automatic native states:** use `disabled`, `checked`, and `hidden` in Tasty component styles instead of `[disabled]`, `[checked]`, and `[hidden]`. Tasty sets `data-disabled`, `data-checked`, and `data-hidden` from the native props automatically.
178
183
 
179
184
  ```jsx
180
185
  color: { '': '#text', hovered: '#accent', disabled: '#text.40' }
@@ -204,7 +209,7 @@ When extending a parent's state map: `'@inherit'` reuses the parent's value for
204
209
  A **capitalized** key targets `[data-element="Name"]`, and its value must be a style object.
205
210
 
206
211
  ```jsx
207
- styles: { Title: { preset: 'h3' }, Icon: { $: '>@:last-child', color: '#accent' } }
212
+ styles: { Title: { preset: 'h3' }, Icon: { $: '>', color: { '': '#text', '@own(:last-child)': '#accent' } } }
208
213
  ```
209
214
 
210
215
  ❌ nested-selector keys (`'& .title'`, `'&:hover'`) → ✅ sub-elements and state maps. Use the `$` affix property inside a sub-element to control how its selector attaches (`>` direct child, `@` placeholder for the element itself, `&::before` for a root pseudo-element).
@@ -6,7 +6,7 @@ Tasty is CSS-in-JS for React design systems. It is built for the point where reu
6
6
 
7
7
  Its distinctive guarantee is narrow and concrete: each property’s state map compiles into selectors that cannot compete, so one declared branch wins regardless of CSS source order.
8
8
 
9
- That does not require a big upfront configuration step. Tasty's built-in units and normal CSS color values work out of the box, and `okhsl(...)` is available immediately as the recommended path for color authoring. The extra setup comes later if a team wants shared tokens, aliases, recipes, or stricter conventions.
9
+ That does not require a big upfront configuration step. Tasty's built-in units and normal CSS color values work out of the box, and the built-in `okhsl(...)` and `okhst(...)` color functions are available immediately as the recommended path for color authoring. The extra setup comes later if a team wants shared tokens, aliases, recipes, or stricter conventions.
10
10
 
11
11
  Most styling tools focus on one of these layers:
12
12
 
@@ -95,11 +95,12 @@ That makes Tasty less of a "better way to write CSS objects" and more of a **sta
95
95
 
96
96
  Beyond state resolution, Tasty also provides several structural capabilities that reinforce the design-system layer:
97
97
 
98
- - **CSS properties as typed React props** — `styleProps` lets a component expose selected style properties as normal props (`<Button placeSelf="end">`), including state maps for responsive values. This keeps layout and composition controls inside a governed component API instead of pushing teams back to ad hoc styling escapes.
99
- - **Sub-element styling** — Compound components declare inner parts via capitalized keys in `styles` and `data-element` attributes. States, tokens, and recipes apply across the entire element tree from a single definition. See [React API — Sub-element Styling](react-api.md#sub-element-styling).
98
+ - **Typed component APIs** — `styleProps` exposes selected style properties as normal props (`<Button placeSelf="end">`, with state maps for responsive values), `modProps` turns modifiers into typed props (`size: ['sm', 'md', 'lg'] as const`), `tokenProps` passes per-instance values without generating new CSS, and `variants` gives a typed `variant` prop. Layout and composition controls stay inside a governed component API instead of pushing teams back to ad hoc styling escapes.
99
+ - **Sub-element styling** — Compound components declare inner parts via capitalized keys in `styles` and the `elements` option, which produces typed sub-components (`<Card.Title>`) that share the root's state context. States, tokens, and recipes apply across the entire element tree from a single definition. See [React API — Sub-element Styling](react-api.md#sub-element-styling).
100
+ - **Modern CSS as state keys** — Container queries, `@supports`, `:has()`, root and parent states, and `@starting-style` live in the same state maps as `:hover`, and `@keyframes`, `@property`, `@font-face`, `@counter-style`, and `@function` are built in.
100
101
  - **Auto-inferred `@property`** — When a custom property is assigned a concrete value, Tasty infers the CSS `@property` syntax and registers it automatically, enabling smooth transitions on custom properties without manual declarations.
101
102
  - **AI-friendly style definitions** — Style definitions are declarative, self-contained, and structurally consistent. AI tools can read, refactor, and generate Tasty styles as confidently as a human — no hidden cascade logic or implicit ordering to second-guess.
102
- - **Companion ecosystem** — An [ESLint plugin](https://github.com/tenphi/eslint-plugin-tasty) with 27 lint rules, a [VS Code extension](https://github.com/tenphi/tasty-vscode-extension) for syntax highlighting, and [Glaze](https://github.com/tenphi/glaze) for OKHSL color theme generation with automatic WCAG contrast solving.
103
+ - **Companion ecosystem** — An [ESLint and oxlint plugin](https://github.com/tenphi/eslint-plugin-tasty) with 40+ rules that validate styles against your project's `tasty.config.ts`, TypeScript augmentation for autocompleting your own token and preset names, a [VS Code extension](https://github.com/tenphi/tasty-vscode-extension) for syntax highlighting and snippets, and [Glaze](https://github.com/tenphi/glaze) for OKHST palette generation with WCAG and APCA contrast solving.
103
104
 
104
105
  ---
105
106
 
@@ -184,7 +185,7 @@ Every new state doubles the selector count. Miss one intersection and you ship a
184
185
  )}>
185
186
  ```
186
187
 
187
- The `theme` branch is runtime JS, not CSS. Intersections like `disabled + hover` need manual `!important` or extra utilities to override correctly.
188
+ The `theme` branch is runtime JS, not CSS. Tailwind resolves `hover:` against `disabled:` by its global variant order, not per component; when your priority differs from that order, or two base utilities conflict (`bg-primary` vs `bg-danger`), you need `tailwind-merge`, `!` modifiers, or more conditional class logic.
188
189
 
189
190
  **Tasty** — each property declares all its states in one map. The engine generates mutually exclusive selectors:
190
191
 
@@ -196,10 +197,10 @@ const Button = tasty({
196
197
  '': '#primary',
197
198
  ':hover': '#primary-hover',
198
199
  ':active': '#primary-pressed',
199
- disabled: '#surface',
200
200
  'theme=danger': '#danger',
201
201
  'theme=danger & :hover': '#danger-hover',
202
202
  'theme=danger & :active': '#danger-pressed',
203
+ disabled: '#surface',
203
204
  },
204
205
  color: {
205
206
  '': '#on-primary',
@@ -213,7 +214,7 @@ const Button = tasty({
213
214
  });
214
215
  ```
215
216
 
216
- `disabled` always wins over `:hover` because Tasty emits negation selectors — no source-order dependence, no manual intersection management, no `!important`.
217
+ `disabled` is declared last, so it wins over `:hover`, `:active`, and `theme=danger`: Tasty guards every earlier branch with `:not([data-disabled])` — no source-order dependence, no manual intersection management, no `!important`.
217
218
 
218
219
  ---
219
220
 
@@ -328,7 +329,7 @@ That makes it narrower in audience, but deeper in architectural ambition.
328
329
 
329
330
  There is also a fundamental architectural difference: Emotion and styled-components rely on React context and hooks internally, which means they require `'use client'` in modern React and cannot run as React Server Components. Tasty's style functions and `tasty()` components are hook-free, so they work as server components by default and ship no Tasty styling runtime in server-only contexts. This is not a minor compatibility detail — it means Tasty-based components stay as server components until _your_ code needs interactivity, while Emotion and styled-components force the client boundary at the styling layer.
330
331
 
331
- For teams evaluating runtime styling at scale, Tasty also documents its runtime benchmarks and caching model in the main [README](../README.md#performance). That matters, but it is still secondary to the core question of whether you want Tasty's deterministic selector model.
332
+ For teams evaluating runtime styling at scale, Tasty publishes reproducible [Runtime Benchmarks](runtime-benchmarks.md) and summarizes its caching model in the [README](../README.md#performance). That matters, but it is still secondary to the core question of whether you want Tasty's deterministic selector model.
332
333
 
333
334
  ---
334
335
 
@@ -23,9 +23,8 @@ configure({
23
23
 
24
24
  // Custom units (merged with built-in units)
25
25
  units: {
26
- vh: 'vh',
27
- vw: 'vw',
28
- custom: (n) => `${n * 10}px`, // Function-based unit
26
+ gu: '4px', // `2gu` → `8px`
27
+ cols: (n) => `calc(${n} * var(--col-width))`, // function-based unit
29
28
  },
30
29
 
31
30
  // Custom functions — a single map for both flavors, discriminated by value type:
@@ -47,35 +46,35 @@ These docs use `data-schema="dark"` in examples. If your app already standardize
47
46
 
48
47
  ## Options
49
48
 
50
- | Option | Type | Default | Description |
51
- | -------------------- | ------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | `nonce` | `string` | - | CSP nonce for style elements |
53
- | `maxRulesPerSheet` | `number` | `8192` | Maximum rules per injected stylesheet |
54
- | `forceTextInjection` | `boolean` | auto (`true` in test envs) | Force text-node CSS injection instead of constructable stylesheets |
55
- | `devMode` | `boolean` | auto | Enable development-mode features: performance metrics and debug info |
56
- | `states` | `Record<string, string>` | - | Global state aliases for advanced state mapping |
57
- | `parserCacheSize` | `number` | `1000` | Parser LRU cache size |
58
- | `units` | `Record<string, string \| UnitHandler>` | Built-in | Custom units (merged with built-in). See [built-in units](dsl.md#built-in-units) |
59
- | `functions` | `Record<string, FunctionDefinition \| Function>` | - | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
60
- | `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in | Custom style handlers (replace built-in). See [Custom Style Handlers](#custom-style-handlers) |
61
- | `propHandlers` | `Record<string, PropHandlerDefinition>` | - | Props middleware for every component — props in, props out. See [Props Middleware](#props-middleware) |
62
- | `baseStyleProps` | `readonly string[]` | - | Style names exposed as props on **every** component. See [Base Style Props](#base-style-props) |
63
- | `tokens` | `Record<string, value \| stateMap>` | - | Design tokens injected as `:root` CSS custom properties |
64
- | `replaceTokens` | `Record<string, string \| number \| boolean>` | - | Parse-time token substitution (inline replacement). `boolean` is allowed for `#` color tokens |
65
- | `keyframes` | `Record<string, KeyframesSteps>` | - | Global keyframes for animations |
66
- | `properties` | `Record<string, PropertyDefinition>` | - | Global CSS @property definitions |
67
- | `fontFaces` | `Record<string, FontFaceInput>` | - | Global @font-face definitions |
68
- | `counterStyles` | `Record<string, CounterStyleDescriptors>` | - | Global @counter-style definitions |
69
- | `polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills for not-yet-baseline features. `functions: true` inlines `@function` calls into plain CSS at parse time |
70
- | `autoPropertyTypes` | `boolean` | `true` | Auto-infer and register `@property` types from values |
71
- | `recipes` | `Record<string, RecipeStyles>` | - | Predefined style recipes (named style bundles) |
72
- | `presets` | `Record<string, TypographyPreset>` | - | Typography presets — shorthand for `generateTypographyTokens()` |
73
- | `globalStyles` | `Record<string, Styles>` | - | Global Tasty styles keyed by CSS selector |
74
- | `plugins` | `TastyPlugin[]` | - | Plugins that bundle any of the above (processed in order; later override earlier, and direct config wins over all). See [Plugins](plugins.md) |
75
- | `gc` | `GCConfig` | - | Garbage-collection tuning for unused styles (`{ touchInterval, capacity }`) |
76
- | `batchInjection` | `boolean \| 'always'` | `false` | Defer stylesheet writes and apply them in one batch. See [Batched injection](#batched-injection) |
77
- | `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | - | **Deprecated** — no longer has any effect. See [Color space](#color-space) |
78
- | `namePrefix` | `string` | `'t'` (`tasty`) / `'ts'` (build-time) | Prefix prepended to every generated identifier (class, keyframe, counter-style names). Must match `^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$`. See [Name prefix](#name-prefix). |
49
+ | Option | Type | Default | Description |
50
+ | -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `nonce` | `string` | - | CSP nonce for style elements |
52
+ | `maxRulesPerSheet` | `number` | `8192` | Maximum rules per injected stylesheet |
53
+ | `forceTextInjection` | `boolean` | `false` (`true` in test environments only while `configure()` has never been called) | Force text-node CSS injection instead of constructable stylesheets. Set it explicitly in test setup |
54
+ | `devMode` | `boolean` | auto | Enable development-mode features: performance metrics and debug info. Does not turn on dev warnings; those follow `NODE_ENV` (or `localStorage.TASTY_DEBUG = 'true'` in a browser) |
55
+ | `states` | `Record<string, string>` | - | Global state aliases for advanced state mapping |
56
+ | `parserCacheSize` | `number` | `1000` | Parser LRU cache size |
57
+ | `units` | `Record<string, string \| UnitHandler>` | Built-in | Custom units (merged with built-in). See [built-in units](dsl.md#built-in-units) |
58
+ | `functions` | `Record<string, FunctionDefinition \| Function>` | - | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
59
+ | `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in | Custom style handlers (replace built-in). See [Custom Style Handlers](#custom-style-handlers) |
60
+ | `propHandlers` | `Record<string, PropHandlerDefinition>` | - | Props middleware for every component — props in, props out. See [Props Middleware](#props-middleware) |
61
+ | `baseStyleProps` | `readonly string[]` | - | Style names exposed as props on **every** component. See [Base Style Props](#base-style-props) |
62
+ | `tokens` | `Record<string, value \| stateMap>` | - | Design tokens injected as `:root` CSS custom properties |
63
+ | `replaceTokens` | `Record<string, string \| number \| boolean>` | `{ '#clear': 'transparent' }` | Parse-time token substitution (inline replacement). `boolean` is allowed for `#` color tokens |
64
+ | `keyframes` | `Record<string, KeyframesSteps>` | - | Global keyframes for animations |
65
+ | `properties` | `Record<string, PropertyDefinition>` | - | Global CSS @property definitions |
66
+ | `fontFaces` | `Record<string, FontFaceInput>` | - | Global @font-face definitions |
67
+ | `counterStyles` | `Record<string, CounterStyleDescriptors>` | - | Global @counter-style definitions |
68
+ | `polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills for not-yet-baseline features. `functions: true` inlines `@function` calls into plain CSS at parse time |
69
+ | `autoPropertyTypes` | `boolean` | `true` | Auto-infer and register `@property` types from values |
70
+ | `recipes` | `Record<string, RecipeStyles>` | - | Predefined style recipes (named style bundles) |
71
+ | `presets` | `Record<string, TypographyPreset>` | - | Typography presets — shorthand for `generateTypographyTokens()` |
72
+ | `globalStyles` | `Record<string, Styles>` | - | Global Tasty styles keyed by CSS selector |
73
+ | `plugins` | `TastyPlugin[]` | - | Plugins that bundle `functions`, `units`, `states`, `handlers`, `propHandlers`, `baseStyleProps`, `tokens`, `replaceTokens`, `recipes`, `presets`, `globalStyles`, `properties`, `keyframes`, `fontFaces`, and `counterStyles` (processed in order; later override earlier, and direct config wins over all). See [Plugins](plugins.md) |
74
+ | `gc` | `GCConfig` | - | Turns on automatic collection of unused styles: `{ touchInterval?, capacity?, grace? }` (defaults `1000`, `1000`, `10000` ms). Without it nothing is collected automatically; call `gc()` yourself |
75
+ | `batchInjection` | `boolean \| 'always'` | `false` | Defer stylesheet writes and apply them in one batch. See [Batched injection](#batched-injection) |
76
+ | `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | - | **Deprecated** — no longer has any effect. See [Color space](#color-space) |
77
+ | `namePrefix` | `string` | `'t'` (`tasty`) / `'ts'` (build-time) | Prefix prepended to every generated identifier (class, keyframe, counter-style names). Must match `^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$`. See [Name prefix](#name-prefix). |
79
78
 
80
79
  ---
81
80
 
@@ -334,8 +333,7 @@ Tokens support state maps for responsive or theme-aware values:
334
333
  ```jsx
335
334
  configure({
336
335
  tokens: {
337
- $gap: '4px',
338
- $radius: '6px',
336
+ '$sidebar-width': '280px',
339
337
  '#primary': {
340
338
  '': '#purple',
341
339
  '@dark': '#light-purple',
@@ -350,9 +348,10 @@ configure({
350
348
 
351
349
  - `$name` keys become `--name` CSS custom properties
352
350
  - `#name` keys become `--name-color` custom properties
351
+ - Tokens do not change units: `2x` stays `16px` whatever `$gap` is. To drive a unit from a token, point the unit at it: `units: { x: 'var(--gap)' }` makes `2x` compile to `calc(2 * var(--gap))`
353
352
  - Names keep their case, since CSS custom properties are case-sensitive: `$myVar` is `--myVar` and is referenced as `$myVar`. A leading capital is not supported and folds — `$Foo` and `#Purple` become `--foo` and `--purple-color` — so start names lowercase. Kebab-case (`$my-var`) remains the convention.
354
353
 
355
- Tokens are automatically emitted in all delivery modes: client rendering, server rendering, and build-time extraction with the Babel plugin.
354
+ Tokens are emitted by the client injector, by the SSR collector (`TastyRegistry`, `tastyIntegration()`, `runWithCollector()`), by the `withTastyNext()` shared stylesheet, and by the Babel plugin. The inline React Server Component path without a collector does not emit them — see [Server-Side Rendering](ssr.md).
356
355
 
357
356
  ---
358
357
 
@@ -375,6 +374,17 @@ configure({
375
374
 
376
375
  When a component uses `padding: '$card-padding'`, the parser replaces it with `'4x'` before generating CSS. When a component uses `fill: '#accent'`, it is replaced with `'#purple'`, which in turn resolves to `var(--purple-color)`.
377
376
 
377
+ Tasty includes `'#clear': 'transparent'` as a default replace token, available
378
+ without calling `configure()`. It survives `resetConfig()`, and explicit
379
+ `replaceTokens` entries can override it. A replace token resolving to the literal
380
+ `transparent` ignores numeric and custom-property opacity suffixes:
381
+ `#clear`, `#clear.5`, and `#clear.$opacity` all emit `transparent`.
382
+
383
+ `#clear` no longer reads `--clear-color`, and Tasty no longer registers that
384
+ property by default. If you previously customized `#clear` through `tokens`,
385
+ local token definitions, or CSS, use a different color token or explicitly
386
+ override `replaceTokens['#clear']` instead.
387
+
378
388
  See [Replace Tokens](dsl.md#replace-tokens) in the Style DSL reference.
379
389
 
380
390
  ---
@@ -471,7 +481,7 @@ See [Functions (`@function`)](dsl.md#functions-function) for inline usage inside
471
481
 
472
482
  ### Custom color functions
473
483
 
474
- A parse function whose output is an already-supported color (`rgb`, `hsl`, `#…`, `oklch`, …) is treated as a **color function**: it works everywhere a color is accepted — style values, `#token.alpha` opacity injection, and `parseColor` — with no extra registration. This is the same mechanism the built-in `okhsl`/`okhst` plugins use; they are ordinary plugins registered by default.
484
+ A parse function whose output is an already-supported color (`rgb`, `hsl`, `#…`, `oklch`, …) is treated as a **color function**: it works everywhere a color is accepted — style values, `parseColor`, and the `#token.alpha` opacity suffix, which calls the function with a `/ alpha` part appended (read `groups[0].parts[1]`, as below) — with no extra registration. This is the same mechanism the built-in `okhsl`/`okhst` plugins use; they are ordinary plugins registered by default.
475
485
 
476
486
  ```ts
477
487
  import { configure, createColorFunc } from '@tenphi/tasty';
@@ -482,8 +492,12 @@ const myColorPlugin = () => ({
482
492
  functions: {
483
493
  // Hand-written parse function:
484
494
  mycolor: (groups) => {
485
- const [r, g, b] = groups[0].all;
486
- return `rgb(${r} ${g} ${b})`;
495
+ // `mycolor(255 0 0 / .5)` and `#brand.5` both arrive as two parts
496
+ const [main, alpha] = groups[0].parts;
497
+ const [r, g, b] = main.all;
498
+ return alpha?.all.length
499
+ ? `rgb(${r} ${g} ${b} / ${alpha.output})`
500
+ : `rgb(${r} ${g} ${b})`;
487
501
  },
488
502
  },
489
503
  });
@@ -690,10 +704,16 @@ Override or extend the built-in style property handlers. A handler definition ca
690
704
  | Single dep | `['styleName', handler]` | Triggered by the specified style property |
691
705
  | Multi dep | `[['dep1', 'dep2', ...], handler]` | Triggered by any of the listed properties; receives all of them |
692
706
 
693
- The multi-dep form is useful when output depends on several style properties together (e.g., `gap` needs to know `display` and `flow` to decide the CSS strategy). Use `defineHandler` for it and the dependency types are inferred from the dependency list, so a typo in the destructure is a type error instead of a silent `undefined`.
707
+ The multi-dep form is useful when output depends on several style properties together (e.g., `gap` needs to know `display` and `flow` to decide the CSS strategy). A handler whose dependencies include a built-in style takes over that style's whole group — see [Replacing a built-in handler](#replacing-a-built-in-handler). Use `defineHandler` for it and the dependency types are inferred from the dependency list, so a typo in the destructure is a type error instead of a silent `undefined`.
694
708
 
695
709
  ```jsx
696
- import { configure, defineHandler, styleHandlers } from '@tenphi/tasty';
710
+ import {
711
+ configure,
712
+ defineHandler,
713
+ parseColor,
714
+ parseStyle,
715
+ styleHandlers,
716
+ } from '@tenphi/tasty';
697
717
 
698
718
  configure({
699
719
  handlers: {
@@ -717,6 +737,10 @@ configure({
717
737
  'backgroundPosition',
718
738
  'backgroundSize',
719
739
  'backgroundRepeat',
740
+ 'backgroundAttachment',
741
+ 'backgroundOrigin',
742
+ 'backgroundClip',
743
+ 'background',
720
744
  ],
721
745
  (props) => {
722
746
  if (
@@ -729,12 +753,18 @@ configure({
729
753
  },
730
754
  ),
731
755
 
732
- // Multi dep — handler reads multiple style properties
733
- gap: defineHandler(['display', 'flow', 'gap'], ({ display, flow, gap }) => {
734
- if (!gap) return;
735
- const isGrid = String(display ?? '').includes('grid');
736
- return { gap: isGrid ? gap : `/* custom logic for ${flow} */` };
737
- }),
756
+ // Multi dep — triggered by either name, receives both
757
+ stripe: defineHandler(
758
+ ['stripe', 'stripeColor'],
759
+ ({ stripe, stripeColor }) => {
760
+ if (!stripe) return;
761
+ const width = parseStyle(String(stripe)).output;
762
+ const color = stripeColor
763
+ ? parseColor(String(stripeColor)).color
764
+ : 'currentcolor';
765
+ return { 'border-left': `${width} solid ${color}` };
766
+ },
767
+ ),
738
768
  },
739
769
  });
740
770
  ```
@@ -745,6 +775,7 @@ A handler returns a `CSSMap`, an array of them, or nothing:
745
775
 
746
776
  - Keys are **kebab-case** CSS property names (`'background-color'`, not `backgroundColor`) or `--custom-property` names. A camelCase key warns in development and is emitted verbatim, which the browser ignores.
747
777
  - Values are stringified, so numbers are fine (`{ '-webkit-line-clamp': 3 }`).
778
+ - An array value emits one declaration per entry, in order, so the browser keeps the last one it supports: `{ width: ['-webkit-fill-available', '-moz-available', 'stretch'] }` emits three `width` declarations.
748
779
  - The reserved `$` key is a **selector suffix**: `{ $: '& > *:not(:last-child)', 'margin-right': gap }` applies the declarations to a nested selector. It accepts an array to fan out over several.
749
780
  - Return an array of maps to emit several declaration sets, each with its own `$`.
750
781
 
@@ -756,12 +787,17 @@ Built-in handlers are shared across several style names. Registering a handler f
756
787
 
757
788
  The shared groups worth knowing:
758
789
 
759
- | Registering a handler for | Also takes over |
760
- | ------------------------------ | --------------------------------------------------------------------------------------------------------- |
761
- | `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat` |
762
- | `display` | `hide`, `overflow`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
763
- | `preset` | `font`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
764
- | `padding` / `margin` / `inset` | their `*Top`/`*Right`/`*Bottom`/`*Left`/`*Block`/`*Inline` longhands |
790
+ | Registering a handler for | Also takes over |
791
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
792
+ | `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat`, `backgroundAttachment`, `backgroundOrigin`, `backgroundClip`, `background` |
793
+ | `display` | `hide`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
794
+ | `flow` | `gap` |
795
+ | `preset` | `font`, `fontFamily`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
796
+ | `padding` / `margin` | their `Top`/`Right`/`Bottom`/`Left` longhands (`paddingTop`, `marginLeft`, …) |
797
+ | `inset` | `top`, `right`, `bottom`, `left` |
798
+ | `width` / `height` | `minWidth`/`maxWidth`, `minHeight`/`maxHeight` |
799
+
800
+ A multi-dep handler takes over every group its dependencies touch: `defineHandler(['display', 'flow', 'gap'], …)` takes over `display`, `flow`, and `gap` together, so it must emit `display` and `flex-flow` itself.
765
801
 
766
802
  Either declare the whole group and delegate the rest to `styleHandlers.*`, or pick a name that isn't shared.
767
803
 
@@ -822,18 +858,98 @@ Each name costs one property check per render of every component, and the effect
822
858
 
823
859
  ---
824
860
 
825
- ## Extending Style Types (TypeScript)
861
+ ## TypeScript
826
862
 
827
- Use module augmentation to extend the `StylesInterface`:
863
+ Component props are typed from the `tasty()` definition with no setup. Module augmentation adds the names your design system defines and the extensions your plugins add.
864
+
865
+ ### Component props
866
+
867
+ | In `tasty({ … })` | Adds the props |
868
+ | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
869
+ | `styleProps: ['gap', 'padding'] as const` | `gap`, `padding`, each typed like the style: a value or a state map |
870
+ | `modProps: { isLoading: Boolean, size: ['sm', 'md', 'lg'] as const }` | `isLoading?: boolean`, `size?: 'sm' \| 'md' \| 'lg'` (`String` gives `string`, `Number` gives `number`) |
871
+ | `modProps: ['isLoading'] as const` | `isLoading?: ModValue` |
872
+ | `tokenProps: ['progress', 'accentColor'] as const` | `progress?: TokenValue`, `accentColor?: TokenValue` |
873
+ | `variants: { default: {…}, danger: {…} }` | `variant?: 'default' \| 'danger'` |
874
+ | `elements: { Title: 'h3' }` | `Card.Title`, with the `h3` props plus `qa`, `qaVal`, `mods`, `tokens`, `isDisabled`, `isHidden`, `isChecked`, and a `ref` typed as `HTMLHeadingElement`; no `styles` |
875
+ | `as: SomeComponent` | `SomeComponent`'s own props, required ones still required |
876
+
877
+ Write the array forms with `as const`. Without it, `modProps: ['isLoading']` widens to `string[]` and every prop on the component is typed `ModValue`, so an `onClick` handler no longer type-checks. A `modProps` value list without `as const` widens to `string`.
878
+
879
+ `tasty(Base, { … })` keeps `Base`'s prop types and sub-elements, and adds the props and sub-elements its own options above declare. Its `variant` accepts both its own variant names and `Base`'s. See [Extend an existing component](react-api.md#extend-an-existing-component).
880
+
881
+ ### Registering your names and extensions
882
+
883
+ ```ts
884
+ // tasty.d.ts — keep the import: it makes this file a module, so the block
885
+ // below extends '@tenphi/tasty' instead of replacing it.
886
+ import '@tenphi/tasty';
828
887
 
829
- ```tsx
830
- // tasty.d.ts
831
888
  declare module '@tenphi/tasty' {
889
+ interface TastyNamedColors {
890
+ surface: true;
891
+ text: true;
892
+ primary: true;
893
+ }
894
+ interface TastyPresetNames {
895
+ h1: true;
896
+ t2: true;
897
+ }
898
+ interface TastyThemeNames {
899
+ danger: true;
900
+ success: true;
901
+ }
832
902
  interface StylesInterface {
833
- elevation?: string;
834
- gradient?: string;
903
+ stripe?: string;
904
+ }
905
+ interface TastyCustomProps {
906
+ glaze: 'soft' | 'strong' | { tone: string; intensity?: number };
907
+ }
908
+ interface TastyBaseStylePropNames {
909
+ radius: true;
910
+ stripe: true;
835
911
  }
836
912
  }
837
913
  ```
838
914
 
915
+ | Interface | Register | What it types |
916
+ | ------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
917
+ | `TastyNamedColors` | Color token names from `tokens` and `replaceTokens`, without the `#` | Suggestions for `#name` and `#name.50` in `fill`, `color`, and `svgFill` |
918
+ | `TastyPresetNames` | `presets` names | The `preset` style and prop |
919
+ | `TastyThemeNames` | Theme names | The `theme` prop (rendered as `data-theme`) |
920
+ | `StylesInterface` | Style properties added with `handlers` | Their value type in `styles`, `styleProps`, and base style props |
921
+ | `TastyCustomProps` | Props added with `propHandlers` | An optional prop on every `tasty()` component (not on sub-elements) |
922
+ | `TastyBaseStylePropNames` | Names passed to `baseStyleProps` | A prop on every component, typed like the style it names |
923
+
924
+ - The first three add autocomplete, not checking: any other string still type-checks (`preset: 'h9'` compiles). The ESLint plugin is what reports unknown names.
925
+ - In `StylesInterface`, declare the plain value type (`stripe?: string`). Tasty adds state-map support itself, as it does for built-in styles.
926
+ - A custom style name in `styles` type-checks without augmentation, but `styleProps` and `TastyBaseStylePropNames` accept only names in `StylesInterface`. A base style prop name missing from it adds no prop.
927
+ - `TastyCustomProps` types the component prop, not the handler. A prop handler receives `Record<string, unknown>`, so cast inside it (`rest.styles as Styles`).
928
+ - State keys, `$` value tokens, units, and recipe names have no TypeScript types. The ESLint plugin checks them against `tasty.config.ts`.
929
+
930
+ ### `tasty.config.ts`
931
+
932
+ TypeScript augmentation does not reach the linter or the editor. [`@tenphi/eslint-plugin-tasty`](https://github.com/tenphi/eslint-plugin-tasty) and the Tasty VS Code extension read a `tasty.config.ts` (or `.js`, `.json`) at the project root instead. It only lists names: it is not passed to `configure()`, and it is unrelated to the Babel plugin's `configFile` (such as `tasty-zero.config.ts`).
933
+
934
+ ```ts
935
+ // tasty.config.ts
936
+ import type { TastyExtensionConfig } from '@tenphi/tasty';
937
+ import type { TastyValidationConfig } from '@tenphi/eslint-plugin-tasty';
938
+
939
+ export default {
940
+ tokens: ['#surface', '#text', '#primary', '$sidebar-width'],
941
+ units: ['cols'],
942
+ states: ['@mobile', '@dark'],
943
+ presets: ['h1', 't2'],
944
+ recipes: ['card', 'elevated'],
945
+ styles: ['stripe'],
946
+ importSources: ['@my-org/ui'],
947
+ presetDescriptions: { h1: 'Heading 1 (32px, bold)' },
948
+ } satisfies TastyExtensionConfig & TastyValidationConfig;
949
+ ```
950
+
951
+ `TastyExtensionConfig` from `@tenphi/tasty` types the fields the VS Code extension reads: `extends`, `tokens`, `units`, `functions`, `states`, `presets`, `styles`, `presetDescriptions`, and `stateDescriptions`. It has no `recipes`, `importSources`, `ownedSources`, or `styleFunctions`, so annotating the file with it alone rejects those ESLint fields; intersect it with the plugin's `TastyValidationConfig`, as above. An omitted list is not checked; `tokens: false`, `units: false`, or `functions: false` turns a check off even when an `extends` parent sets it.
952
+
953
+ Keep it in step with `configure()`: token names with their `#` or `$`, custom units, parse-function names, state aliases, presets, recipes, and custom style names.
954
+
839
955
  See [Style DSL](dsl.md) for state maps, tokens, units, and extending semantics, and [React API](react-api.md) for `tasty()`, style functions, and component props.