@tenphi/tasty 3.4.0 → 3.5.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-ib7E7V4Y.js +307 -0
  3. package/dist/astro-ib7E7V4Y.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 +37 -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 +99 -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
@@ -1,6 +1,8 @@
1
1
  # Configuration
2
2
 
3
- Configure the Tasty style system before your app renders using the `configure()` function. Configuration must be done **before any styles are generated** (before first render). For a higher-level docs map, see the [Docs Hub](README.md).
3
+ Tasty works without upfront configuration. Use `configure()` when your app or design system is ready to define shared tokens, state aliases, units, recipes, presets, or extension points.
4
+
5
+ Call it **before any styles are generated** (before first render). For a guided setup, see [Getting Started](getting-started.md). For the higher-level docs map, see the [Docs Hub](README.md).
4
6
 
5
7
  ```jsx
6
8
  import { configure } from '@tenphi/tasty';
@@ -45,35 +47,35 @@ These docs use `data-schema="dark"` in examples. If your app already standardize
45
47
 
46
48
  ## Options
47
49
 
48
- | Option | Type | Default | Description |
49
- |--------|------|---------|-------------|
50
- | `nonce` | `string` | - | CSP nonce for style elements |
51
- | `maxRulesPerSheet` | `number` | `8192` | Maximum rules per injected stylesheet |
52
- | `forceTextInjection` | `boolean` | auto (`true` in test envs) | Force text-node CSS injection instead of constructable stylesheets |
53
- | `devMode` | `boolean` | auto | Enable development-mode features: performance metrics and debug info |
54
- | `states` | `Record<string, string>` | - | Global state aliases for advanced state mapping |
55
- | `parserCacheSize` | `number` | `1000` | Parser LRU cache size |
56
- | `units` | `Record<string, string \| UnitHandler>` | Built-in | Custom units (merged with built-in). See [built-in units](dsl.md#built-in-units) |
57
- | `functions` | `Record<string, FunctionDefinition \| Function>` | - | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
58
- | `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in | Custom style handlers (replace built-in). See [Custom Style Handlers](#custom-style-handlers) |
59
- | `propHandlers` | `Record<string, PropHandlerDefinition>` | - | Props middleware for every component — props in, props out. See [Props Middleware](#props-middleware) |
60
- | `baseStyleProps` | `readonly string[]` | - | Style names exposed as props on **every** component. See [Base Style Props](#base-style-props) |
61
- | `tokens` | `Record<string, value \| stateMap>` | - | Design tokens injected as `:root` CSS custom properties |
62
- | `replaceTokens` | `Record<string, string \| number \| boolean>` | - | Parse-time token substitution (inline replacement). `boolean` is allowed for `#` color tokens |
63
- | `keyframes` | `Record<string, KeyframesSteps>` | - | Global keyframes for animations |
64
- | `properties` | `Record<string, PropertyDefinition>` | - | Global CSS @property definitions |
65
- | `fontFaces` | `Record<string, FontFaceInput>` | - | Global @font-face definitions |
66
- | `counterStyles` | `Record<string, CounterStyleDescriptors>` | - | Global @counter-style definitions |
67
- | `polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills for not-yet-baseline features. `functions: true` inlines `@function` calls into plain CSS at parse time |
68
- | `autoPropertyTypes` | `boolean` | `true` | Auto-infer and register `@property` types from values |
69
- | `recipes` | `Record<string, RecipeStyles>` | - | Predefined style recipes (named style bundles) |
70
- | `presets` | `Record<string, TypographyPreset>` | - | Typography presets — shorthand for `generateTypographyTokens()` |
71
- | `globalStyles` | `Record<string, Styles>` | - | Global Tasty styles keyed by CSS selector |
72
- | `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) |
73
- | `gc` | `GCConfig` | - | Garbage-collection tuning for unused styles (`{ touchInterval, capacity }`) |
74
- | `batchInjection` | `boolean \| 'always'` | `false` | Defer stylesheet writes and apply them in one batch. See [Batched injection](#batched-injection) |
75
- | `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | - | **Deprecated** — no longer has any effect. See [Color space](#color-space) |
76
- | `namePrefix` | `string` | `'t'` (runtime) / `'ts'` (zero-runtime) | 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). |
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). |
77
79
 
78
80
  ---
79
81
 
@@ -107,10 +109,10 @@ createRoot(el).render(
107
109
  ### Why the provider is required
108
110
 
109
111
  Deferring a write past React's layout phase would let a `useLayoutEffect`
110
- measure an element whose rules are not in the sheet yet and read its *unstyled*
112
+ measure an element whose rules are not in the sheet yet and read its _unstyled_
111
113
  box — a wrong number, not a stale one, so it never self-corrects.
112
114
 
113
- `TastyBatchProvider` closes that hole. It opens a *batch window* during its
115
+ `TastyBatchProvider` closes that hole. It opens a _batch window_ during its
114
116
  render and closes it — flushing — in `useInsertionEffect`, which React runs in
115
117
  the mutation phase, before any layout effect:
116
118
 
@@ -135,11 +137,11 @@ commits it takes part in.
135
137
 
136
138
  ### Modes
137
139
 
138
- | Value | Behaviour |
139
- |-------|-----------|
140
- | `false` (default) | One `insertRule()` per component, synchronously. |
141
- | `true` | Batch inside a provider window only. Cannot affect measurement. Without the provider nothing is batched, and dev mode says so once. |
142
- | `'always'` | Batch every injection, flushing on a microtask when no window is open. Covers more commits; a `useLayoutEffect` measuring a freshly mounted element can read its unstyled box. Paint is unaffected — microtasks always drain before the browser paints. |
140
+ | Value | Behaviour |
141
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
142
+ | `false` (default) | One `insertRule()` per component, synchronously. |
143
+ | `true` | Batch inside a provider window only. Cannot affect measurement. Without the provider nothing is batched, and dev mode says so once. |
144
+ | `'always'` | Batch every injection, flushing on a microtask when no window is open. Covers more commits; a `useLayoutEffect` measuring a freshly mounted element can read its unstyled box. Paint is unaffected — microtasks always drain before the browser paints. |
143
145
 
144
146
  ### Ordering
145
147
 
@@ -148,7 +150,7 @@ All writes share one FIFO queue — component rules, global rules, raw CSS,
148
150
  Draining it in insertion order keeps the sheet byte-identical to unbatched
149
151
  output, which matters because equal-specificity rules resolve by document order.
150
152
 
151
- ### SSR, RSC and zero-runtime
153
+ ### Client rendering, server rendering, and build-time extraction
152
154
 
153
155
  **Server render — nothing to batch, safe to leave enabled.** SSR and RSC collect
154
156
  CSS as text through `ServerStyleCollector`; the runtime injector never runs
@@ -173,12 +175,12 @@ tasty components to be worth it. `'always'` needs no provider and covers every
173
175
  island, at the cost of the measurement hazard above. Either way, an island that
174
176
  only re-hydrates server-rendered classes has nothing to batch.
175
177
 
176
- **Zero-runtime (`tastyStatic`) — unaffected.** Build-time extraction never
178
+ **Build-time extraction (`tastyStatic`) — unaffected.** The Babel plugin never
177
179
  touches the injector: the babel plugin emits either a CSS file import or an
178
180
  `injectCSS()` call from `@tenphi/tasty/static/inject`, which appends text to a
179
181
  single `<style data-tasty-static>` element. `batchInjection` only defers CSSOM
180
- writes made by the runtime injector, so extracted styles are unchanged. In an app
181
- that mixes both, it still applies to the runtime half.
182
+ writes made by the browser injector, so extracted styles are unchanged. In an app
183
+ that mixes both, it still applies to the `tasty()` half.
182
184
 
183
185
  ### `flushStyles()`
184
186
 
@@ -282,7 +284,7 @@ color syntax works on every `<color>`, including a `color-mix()`, a
282
284
 
283
285
  ## Name Prefix
284
286
 
285
- Every identifier Tasty generates — class names, keyframe names, counter-style names — starts with a configurable prefix. The runtime, SSR, and RSC paths default to `'t'`; the zero-runtime build path (`tastyStatic` via the Babel plugin) defaults to `'ts'` so static-extracted classes can never collide with runtime classes when both are loaded on the same page.
287
+ Every identifier Tasty generates — class names, keyframe names, counter-style names — starts with a configurable prefix. The `tasty()` client, SSR, and RSC paths default to `'t'`; the build-time extraction path (`tastyStatic` via the Babel plugin) defaults to `'ts'` so extracted classes can never collide with `tasty()` classes when both are loaded on the same page.
286
288
 
287
289
  ```jsx
288
290
  configure({
@@ -292,12 +294,12 @@ configure({
292
294
 
293
295
  The prefix is prepended verbatim to the hash, so include any separator inside the prefix string itself:
294
296
 
295
- | Setting | Class | Keyframe | Counter-style |
296
- |---|---|---|---|
297
- | `'t'` (runtime default) | `t1a2b3` | `tk1a2b3` | `tc1a2b3` |
298
- | `'ts'` (zero-runtime default) | `ts1a2b3` | `tsk1a2b3` | `tsc1a2b3` |
299
- | `'mb'` | `mb1a2b3` | `mbk1a2b3` | `mbc1a2b3` |
300
- | `'myapp-'` | `myapp-1a2b3` | `myapp-k1a2b3` | `myapp-c1a2b3` |
297
+ | Setting | Class | Keyframe | Counter-style |
298
+ | --------------------------- | ------------- | -------------- | -------------- |
299
+ | `'t'` (`tasty` default) | `t1a2b3` | `tk1a2b3` | `tc1a2b3` |
300
+ | `'ts'` (build-time default) | `ts1a2b3` | `tsk1a2b3` | `tsc1a2b3` |
301
+ | `'mb'` | `mb1a2b3` | `mbk1a2b3` | `mbc1a2b3` |
302
+ | `'myapp-'` | `myapp-1a2b3` | `myapp-k1a2b3` | `myapp-c1a2b3` |
301
303
 
302
304
  The single-letter discriminators (`k` for keyframes, `c` for counter-styles) keep the three kinds visually distinct in devtools — they are not required for correctness because CSS keeps these in separate namespaces.
303
305
 
@@ -307,12 +309,12 @@ The single-letter discriminators (`k` for keyframes, `c` for counter-styles) kee
307
309
  - Validated at `configure()` time; an invalid prefix throws synchronously rather than silently producing broken hydration.
308
310
  - Locked once styles have been generated, like all other config.
309
311
 
310
- ### Coexistence with the zero-runtime build
312
+ ### Coexistence with build-time extraction
311
313
 
312
- The runtime and zero-runtime builds **must use different prefixes** when both are loaded on the same page. Defaults already guarantee this; if you customize one, customize the other accordingly:
314
+ The `tasty()` and `tastyStatic()` paths **must use different prefixes** when both are loaded on the same page. Defaults already guarantee this; if you customize one, customize the other accordingly:
313
315
 
314
316
  ```jsx
315
- // app config (runtime / SSR / RSC)
317
+ // app config (client / SSR / RSC)
316
318
  configure({ namePrefix: 'mb' });
317
319
 
318
320
  // tasty-zero.config.ts (Babel plugin)
@@ -332,8 +334,8 @@ Tokens support state maps for responsive or theme-aware values:
332
334
  ```jsx
333
335
  configure({
334
336
  tokens: {
335
- '$gap': '4px',
336
- '$radius': '6px',
337
+ $gap: '4px',
338
+ $radius: '6px',
337
339
  '#primary': {
338
340
  '': '#purple',
339
341
  '@dark': '#light-purple',
@@ -350,7 +352,7 @@ configure({
350
352
  - `#name` keys become `--name-color` custom properties
351
353
  - 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.
352
354
 
353
- Tokens are automatically emitted in all rendering modes: runtime (client), SSR, and zero-runtime (Babel plugin).
355
+ Tokens are automatically emitted in all delivery modes: client rendering, server rendering, and build-time extraction with the Babel plugin.
354
356
 
355
357
  ---
356
358
 
@@ -404,7 +406,7 @@ configure({
404
406
  });
405
407
  ```
406
408
 
407
- Now any component can use `fontFamily: '"Brand Sans", sans-serif'` and the browser will already have the `@font-face` rules in the stylesheet.
409
+ Now any component can use `font: '"Brand Sans", sans-serif'` and the browser will already have the `@font-face` rules in the stylesheet.
408
410
 
409
411
  See [Font Face (`@font-face`)](dsl.md#font-face-font-face) for inline usage inside component styles and the full list of supported descriptors.
410
412
 
@@ -461,7 +463,7 @@ configure({
461
463
  });
462
464
  ```
463
465
 
464
- Components then invoke parse functions as `double(...)` and CSS functions with the `$$name(...)` sugar, e.g. `marginTop: '$$negative(10px)'`.
466
+ Components then invoke parse functions as `double(...)` and CSS functions with the `$$name(...)` sugar, e.g. `margin: '$$negative(10px) top'`.
465
467
 
466
468
  > A key whose prefix doesn't match its value type (an object under a bare key, or a function under a `$$` key) is **ignored with a dev-mode warning**.
467
469
 
@@ -511,7 +513,7 @@ configure({
511
513
  },
512
514
  });
513
515
 
514
- // Now `marginTop: '$$negative(10px)'` renders `margin-top: calc(-1 * 10px)`
516
+ // Now `margin: '$$negative(10px) top'` renders `margin: calc(-1 * 10px) 0 0 0`
515
517
  // — no native @function rule is emitted.
516
518
  ```
517
519
 
@@ -604,7 +606,7 @@ configure({
604
606
  color: '#text',
605
607
  preset: 't2',
606
608
  margin: 0,
607
- fontFamily: 'system-ui, sans-serif',
609
+ font: 'system-ui, sans-serif',
608
610
  },
609
611
  html: {
610
612
  overflow: 'hidden',
@@ -615,7 +617,7 @@ configure({
615
617
 
616
618
  Each key is a CSS selector, and each value is a Tasty `Styles` object supporting the full style syntax including style properties, tokens, state maps, and selector-based sub-styling (e.g. `$: '> .app'` for elements outside React scope). Global styles are injected alongside `:root` tokens when the first style is rendered.
617
619
 
618
- Global styles are automatically emitted in all rendering modes: runtime (client), SSR, and zero-runtime (Babel plugin). Plugins can also provide `globalStyles`; they are merged per selector with config global styles (config wins on conflict).
620
+ Global styles are automatically emitted in all delivery modes: client rendering, server rendering, and build-time extraction with the Babel plugin. Plugins can also provide `globalStyles`; they are merged per selector with config global styles (config wins on conflict).
619
621
 
620
622
  ---
621
623
 
@@ -623,18 +625,18 @@ Global styles are automatically emitted in all rendering modes: runtime (client)
623
625
 
624
626
  CSS cannot transition or animate custom properties unless their type is declared via [`@property`](https://developer.mozilla.org/en-US/docs/Web/CSS/@property). Tasty handles this automatically — when a custom property is assigned a concrete value (e.g. `'$scale': 1`, `'$gap': '10px'`, `'#accent': 'purple'`), the type is inferred and a `@property` rule is registered.
625
627
 
626
- This works across all declaration contexts: component styles, `@keyframes`, global config, and the zero-runtime Babel plugin. It also resolves `var()` chains — if `$a` references `var(--b)`, the type propagates once `--b` is resolved.
628
+ This works across all declaration contexts: component styles, `@keyframes`, global config, and build-time extraction with the Babel plugin. It also resolves `var()` chains — if `$a` references `var(--b)`, the type propagates once `--b` is resolved.
627
629
 
628
630
  Supported types:
629
631
 
630
- | Detection | Inferred syntax |
631
- |-----------|-----------------|
632
- | `1`, `0.5`, `-3` (bare numbers) | `<number>` |
633
- | `10px`, `2rem`, `100vw` (length units) | `<length>` |
634
- | `50%` | `<percentage>` |
635
- | `45deg`, `0.5turn` (angle units) | `<angle>` |
636
- | `300ms`, `1s` (time units) | `<time>` |
637
- | `#name` tokens (by naming convention) | `<color>` |
632
+ | Detection | Inferred syntax |
633
+ | -------------------------------------- | --------------- |
634
+ | `1`, `0.5`, `-3` (bare numbers) | `<number>` |
635
+ | `10px`, `2rem`, `100vw` (length units) | `<length>` |
636
+ | `50%` | `<percentage>` |
637
+ | `45deg`, `0.5turn` (angle units) | `<angle>` |
638
+ | `300ms`, `1s` (time units) | `<time>` |
639
+ | `#name` tokens (by naming convention) | `<color>` |
638
640
 
639
641
  Auto-inferred properties use `inherits: true` (the CSS default). Use explicit `@property` when you need different settings:
640
642
 
@@ -666,11 +668,11 @@ configure({ autoPropertyTypes: false });
666
668
 
667
669
  Override or extend the built-in style property handlers. A handler definition can take three forms:
668
670
 
669
- | Form | Syntax | Description |
670
- |------|--------|-------------|
671
- | Function only | `handler` | Triggered by its key name; receives only that property |
672
- | Single dep | `['styleName', handler]` | Triggered by the specified style property |
673
- | Multi dep | `[['dep1', 'dep2', ...], handler]` | Triggered by any of the listed properties; receives all of them |
671
+ | Form | Syntax | Description |
672
+ | ------------- | ---------------------------------- | --------------------------------------------------------------- |
673
+ | Function only | `handler` | Triggered by its key name; receives only that property |
674
+ | Single dep | `['styleName', handler]` | Triggered by the specified style property |
675
+ | Multi dep | `[['dep1', 'dep2', ...], handler]` | Triggered by any of the listed properties; receives all of them |
674
676
 
675
677
  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`.
676
678
 
@@ -692,11 +694,19 @@ configure({
692
694
  // them all through, or the ones you leave out stop working (see below).
693
695
  fill: defineHandler(
694
696
  [
695
- 'fill', 'backgroundColor', 'image', 'backgroundImage',
696
- 'backgroundPosition', 'backgroundSize', 'backgroundRepeat',
697
+ 'fill',
698
+ 'backgroundColor',
699
+ 'image',
700
+ 'backgroundImage',
701
+ 'backgroundPosition',
702
+ 'backgroundSize',
703
+ 'backgroundRepeat',
697
704
  ],
698
705
  (props) => {
699
- if (typeof props.fill === 'string' && props.fill.startsWith('gradient:')) {
706
+ if (
707
+ typeof props.fill === 'string' &&
708
+ props.fill.startsWith('gradient:')
709
+ ) {
700
710
  return { background: props.fill.slice(9) };
701
711
  }
702
712
  return styleHandlers.fill(props);
@@ -730,18 +740,18 @@ Built-in handlers are shared across several style names. Registering a handler f
730
740
 
731
741
  The shared groups worth knowing:
732
742
 
733
- | Registering a handler for | Also takes over |
734
- |---|---|
735
- | `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat` |
736
- | `display` | `hide`, `overflow`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
737
- | `preset` | `font`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
738
- | `padding` / `margin` / `inset` | their `*Top`/`*Right`/`*Bottom`/`*Left`/`*Block`/`*Inline` longhands |
743
+ | Registering a handler for | Also takes over |
744
+ | ------------------------------ | --------------------------------------------------------------------------------------------------------- |
745
+ | `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat` |
746
+ | `display` | `hide`, `overflow`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
747
+ | `preset` | `font`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
748
+ | `padding` / `margin` / `inset` | their `*Top`/`*Right`/`*Bottom`/`*Left`/`*Block`/`*Inline` longhands |
739
749
 
740
750
  Either declare the whole group and delegate the rest to `styleHandlers.*`, or pick a name that isn't shared.
741
751
 
742
752
  ### Chunk membership
743
753
 
744
- Tasty renders and caches CSS in independent chunks, and a chunk's cache key covers only its own style values. All of a handler's dependencies must therefore live in one chunk. Custom style names are pulled into their handler's chunk automatically at registration; a handler whose dependencies span two *built-in* chunks (say `fill` and `padding`) warns, because it would be invoked once per chunk with a subset of its inputs.
754
+ Tasty renders and caches CSS in independent chunks, and a chunk's cache key covers only its own style values. All of a handler's dependencies must therefore live in one chunk. Custom style names are pulled into their handler's chunk automatically at registration; a handler whose dependencies span two _built-in_ chunks (say `fill` and `padding`) warns, because it would be invoked once per chunk with a subset of its inputs.
745
755
 
746
756
  `configure({ handlers })` must run before the first render, like every other config option.
747
757
 
@@ -749,7 +759,7 @@ Tasty renders and caches CSS in independent chunks, and a chunk's cache key cove
749
759
 
750
760
  ## Props Middleware
751
761
 
752
- `propHandlers` are middleware over a component's props — props in, props out. Where a style *handler* turns a style property into CSS declarations, a prop handler turns a **component prop** into other props, including `styles`. It is the extension point for props whose value isn't a style value.
762
+ `propHandlers` are middleware over a component's props — props in, props out. Where a style _handler_ turns a style property into CSS declarations, a prop handler turns a **component prop** into other props, including `styles`. It is the extension point for props whose value isn't a style value.
753
763
 
754
764
  ```jsx
755
765
  import { configure, mergeStyles } from '@tenphi/tasty';
@@ -765,7 +775,7 @@ configure({
765
775
  },
766
776
  });
767
777
 
768
- <Element glaze="purple" />
778
+ <Element glaze="purple" />;
769
779
  ```
770
780
 
771
781
  The map key is the handler's name and, by default, the prop that triggers it, so an absent prop costs one property check rather than a call. A tuple overrides that: `['glaze', fn]`, `[['glaze', 'tint'], fn]`, or `['*', fn]` for unconditional.
@@ -774,7 +784,7 @@ Handlers run at the very top of every component's render, before any prop is des
774
784
 
775
785
  Handlers must be **pure** and must not mutate their input, and should memoize the styles they build per input value: style values are cached by object identity, so mutating one in place yields stale CSS, and a reference-stable object avoids re-serializing on every render.
776
786
 
777
- Injected styles occupy the `styles` slot, so they beat a component's own default styles and lose to a style prop at the call site. Prop handlers do not apply to sub-elements or to zero-runtime `tastyStatic()`, which has no props.
787
+ Injected styles occupy the `styles` slot, so they beat a component's own default styles and lose to a style prop at the call site. Prop handlers do not apply to sub-elements or to build-time `tastyStatic()`, which has no props.
778
788
 
779
789
  See [Plugins → Props middleware](plugins.md#props-middleware) for the full contract and a worked example.
780
790
 
@@ -787,7 +797,7 @@ See [Plugins → Props middleware](plugins.md#props-middleware) for the full con
787
797
  ```jsx
788
798
  configure({ baseStyleProps: ['radius', 'shadow'] });
789
799
 
790
- <Card radius="1r" shadow />
800
+ <Card radius="1r" shadow />;
791
801
  ```
792
802
 
793
803
  `configure()` may run after your components are defined — each factory resolves its prop list lazily.
package/docs/debug.md CHANGED
@@ -68,7 +68,7 @@ Retrieves CSS text for a given target. Logs the result with rule count and size.
68
68
  |---|---|
69
69
  | `'all'` | All tasty CSS (component + global + raw) |
70
70
  | `'active'` | CSS for classes currently in the DOM |
71
- | `'unused'` | CSS with refCount = 0 (cached but not used) |
71
+ | `'unused'` | CSS for injected classes no element carries (cached but not rendered) |
72
72
  | `'global'` | Only global CSS (from `injectGlobal`) |
73
73
  | `'page'` | All CSS on the page (including non-tasty) |
74
74
  | `'t3a5f'` | CSS for a specific tasty class (class names are `t` + base36 hash) |
@@ -241,7 +241,9 @@ tastyDebug.cache();
241
241
 
242
242
  ### `cleanup(opts?): void`
243
243
 
244
- Forces immediate cleanup of all unused styles (those with `refCount = 0`).
244
+ Removes every injected style that is neither carried by an element nor pinned by
245
+ an outstanding `inject()` handle — the same set `summary()` reports as unused.
246
+ Equivalent to `gc({ force: true })`.
245
247
 
246
248
  ```typescript
247
249
  tastyDebug.cleanup();
@@ -1,8 +1,8 @@
1
1
  # Building a Design System
2
2
 
3
- This guide walks through building a design-system styling layer on top of Tasty defining tokens, state aliases, recipes, primitive components, and compound components with sub-elements.
3
+ This guide shows how to build shared React components whose styles stay predictable as states, variants, themes, and responsive rules accumulate. You will define tokens, state aliases, recipes, primitive APIs, and compound components with sub-elements.
4
4
 
5
- It assumes you have already decided to adopt Tasty. The goal is not just to centralize tokens, but to define a styling language whose component states resolve deterministically across variants, responsive rules, and sub-elements. For evaluation criteria, audience fit, and incremental adoption phases, see the [Adoption Guide](adoption.md). For the recommended component patterns and mental model, see [Methodology](methodology.md).
5
+ It assumes you have already decided to adopt Tasty. The goal is not only to centralize values, but to give your design system one state model and a governed component-facing styling API. For evaluation criteria and incremental rollout, see the [Adoption Guide](adoption.md). For the recommended component architecture, see [Methodology](methodology.md).
6
6
 
7
7
  ---
8
8
 
@@ -43,8 +43,8 @@ configure({
43
43
  '#border': '#e0e0e0',
44
44
 
45
45
  // Unit values
46
- '$gap': '8px',
47
- '$radius': '4px',
46
+ $gap: '8px',
47
+ $radius: '4px',
48
48
  '$border-width': '1px',
49
49
  '$outline-width': '2px',
50
50
  },
@@ -91,8 +91,18 @@ import { configure } from '@tenphi/tasty';
91
91
 
92
92
  configure({
93
93
  presets: {
94
- h1: { fontSize: '2rem', lineHeight: '1.2', letterSpacing: '-0.02em', fontWeight: 700 },
95
- t2: { fontSize: '0.875rem', lineHeight: '1.5', letterSpacing: 'normal', fontWeight: 400 },
94
+ h1: {
95
+ fontSize: '2rem',
96
+ lineHeight: '1.2',
97
+ letterSpacing: '-0.02em',
98
+ fontWeight: 700,
99
+ },
100
+ t2: {
101
+ fontSize: '0.875rem',
102
+ lineHeight: '1.5',
103
+ letterSpacing: 'normal',
104
+ fontWeight: 400,
105
+ },
96
106
  },
97
107
  });
98
108
  ```
@@ -123,8 +133,16 @@ Register your design system's custom fonts via `configure({ fontFaces })` so eve
123
133
  configure({
124
134
  fontFaces: {
125
135
  'Brand Sans': [
126
- { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
127
- { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
136
+ {
137
+ src: 'url("/fonts/brand-regular.woff2") format("woff2")',
138
+ fontWeight: 400,
139
+ fontDisplay: 'swap',
140
+ },
141
+ {
142
+ src: 'url("/fonts/brand-bold.woff2") format("woff2")',
143
+ fontWeight: 700,
144
+ fontDisplay: 'swap',
145
+ },
128
146
  ],
129
147
  },
130
148
  });
@@ -144,7 +162,8 @@ configure({
144
162
  '@mobile': '@media(w < 768px)',
145
163
  '@tablet': '@media(768px <= w < 1024px)',
146
164
  '@desktop': '@media(w >= 1024px)',
147
- '@dark': '@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
165
+ '@dark':
166
+ '@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
148
167
  '@reduced-motion': '@media(prefers-reduced-motion: reduce)',
149
168
  },
150
169
  });
@@ -185,7 +204,7 @@ configure({
185
204
  'input-reset': {
186
205
  border: 'none',
187
206
  outline: 'none',
188
- fill: 'transparent',
207
+ fill: '#clear',
189
208
  font: true,
190
209
  preset: 't3',
191
210
  },
@@ -211,7 +230,7 @@ const ProfileCard = tasty({
211
230
  });
212
231
  ```
213
232
 
214
- Use the `/` separator when a recipe should be applied *after* local styles (post-merge), so recipe states take priority:
233
+ Use the `/` separator when a recipe should be applied _after_ local styles (post-merge), so recipe states take priority:
215
234
 
216
235
  ```tsx
217
236
  const Input = tasty({
@@ -283,12 +302,12 @@ Product engineers use these to compose layouts without writing CSS:
283
302
 
284
303
  Match the prop set to the component's role:
285
304
 
286
- | Component category | Recommended styleProps |
287
- |--------------------|----------------------|
288
- | Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align, justify, padding, fill |
289
- | Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order |
290
- | Text elements | `['preset', 'color']` or a custom subset |
291
- | Compound components | Typically none — styling happens via sub-elements and wrapping |
305
+ | Component category | Recommended styleProps |
306
+ | ------------------------------------------ | -------------------------------------------------------------- |
307
+ | Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align, justify, padding, fill |
308
+ | Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order |
309
+ | Text elements | `['preset', 'color']` or a custom subset |
310
+ | Compound components | Typically none — styling happens via sub-elements and wrapping |
292
311
 
293
312
  Exposing too many props weakens the design system's constraints. See [Methodology — styleProps as the public API](methodology.md#styleprops-as-the-public-api) for the rationale.
294
313
 
@@ -335,9 +354,7 @@ Usage:
335
354
  ```tsx
336
355
  <Card>
337
356
  <Card.Title>Monthly Revenue</Card.Title>
338
- <Card.Content>
339
- $1.2M — up 12% from last month.
340
- </Card.Content>
357
+ <Card.Content>$1.2M — up 12% from last month.</Card.Content>
341
358
  <Card.Footer>
342
359
  <Button>Details</Button>
343
360
  </Card.Footer>
@@ -365,13 +382,15 @@ A design system works best when the rules for customization are explicit. Tasty
365
382
  2. **Use modProps** — control component states through typed props instead of `mods`:
366
383
 
367
384
  ```tsx
368
- <Button isLoading size="large">Submit</Button>
385
+ <Button isLoading size="large">
386
+ Submit
387
+ </Button>
369
388
  ```
370
389
 
371
390
  3. **Pass tokens** — inject runtime values through the `tokens` prop for per-instance customization:
372
391
 
373
392
  ```tsx
374
- <ProgressBar tokens={{ '$progress': `${percent}%` }} />
393
+ <ProgressBar tokens={{ $progress: `${percent}%` }} />
375
394
  ```
376
395
 
377
396
  4. **Create styled wrappers** — extend a component's styles with `tasty(Base, { styles })`: