@tenphi/tasty 0.0.0-snapshot.056b911

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 (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +635 -0
  3. package/dist/_virtual/_rolldown/runtime.js +7 -0
  4. package/dist/chunks/cacheKey.d.ts +1 -0
  5. package/dist/chunks/cacheKey.js +77 -0
  6. package/dist/chunks/cacheKey.js.map +1 -0
  7. package/dist/chunks/definitions.d.ts +37 -0
  8. package/dist/chunks/definitions.js +258 -0
  9. package/dist/chunks/definitions.js.map +1 -0
  10. package/dist/chunks/index.d.ts +1 -0
  11. package/dist/chunks/renderChunk.d.ts +1 -0
  12. package/dist/chunks/renderChunk.js +59 -0
  13. package/dist/chunks/renderChunk.js.map +1 -0
  14. package/dist/compute-styles.d.ts +31 -0
  15. package/dist/compute-styles.js +335 -0
  16. package/dist/compute-styles.js.map +1 -0
  17. package/dist/config.d.ts +409 -0
  18. package/dist/config.js +584 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/core/index.d.ts +34 -0
  21. package/dist/core/index.js +27 -0
  22. package/dist/counter-style/index.js +51 -0
  23. package/dist/counter-style/index.js.map +1 -0
  24. package/dist/debug.d.ts +89 -0
  25. package/dist/debug.js +453 -0
  26. package/dist/debug.js.map +1 -0
  27. package/dist/font-face/index.js +63 -0
  28. package/dist/font-face/index.js.map +1 -0
  29. package/dist/hooks/index.d.ts +7 -0
  30. package/dist/hooks/useCounterStyle.d.ts +36 -0
  31. package/dist/hooks/useCounterStyle.js +64 -0
  32. package/dist/hooks/useCounterStyle.js.map +1 -0
  33. package/dist/hooks/useFontFace.d.ts +45 -0
  34. package/dist/hooks/useFontFace.js +66 -0
  35. package/dist/hooks/useFontFace.js.map +1 -0
  36. package/dist/hooks/useGlobalStyles.d.ts +46 -0
  37. package/dist/hooks/useGlobalStyles.js +88 -0
  38. package/dist/hooks/useGlobalStyles.js.map +1 -0
  39. package/dist/hooks/useKeyframes.d.ts +58 -0
  40. package/dist/hooks/useKeyframes.js +54 -0
  41. package/dist/hooks/useKeyframes.js.map +1 -0
  42. package/dist/hooks/useProperty.d.ts +81 -0
  43. package/dist/hooks/useProperty.js +96 -0
  44. package/dist/hooks/useProperty.js.map +1 -0
  45. package/dist/hooks/useRawCSS.d.ts +22 -0
  46. package/dist/hooks/useRawCSS.js +103 -0
  47. package/dist/hooks/useRawCSS.js.map +1 -0
  48. package/dist/hooks/useStyles.d.ts +40 -0
  49. package/dist/hooks/useStyles.js +31 -0
  50. package/dist/hooks/useStyles.js.map +1 -0
  51. package/dist/index.d.ts +51 -0
  52. package/dist/index.js +36 -0
  53. package/dist/injector/index.d.ts +182 -0
  54. package/dist/injector/index.js +185 -0
  55. package/dist/injector/index.js.map +1 -0
  56. package/dist/injector/injector.d.ts +193 -0
  57. package/dist/injector/injector.js +564 -0
  58. package/dist/injector/injector.js.map +1 -0
  59. package/dist/injector/sheet-manager.d.ts +132 -0
  60. package/dist/injector/sheet-manager.js +698 -0
  61. package/dist/injector/sheet-manager.js.map +1 -0
  62. package/dist/injector/types.d.ts +228 -0
  63. package/dist/keyframes/index.js +206 -0
  64. package/dist/keyframes/index.js.map +1 -0
  65. package/dist/parser/classify.js +319 -0
  66. package/dist/parser/classify.js.map +1 -0
  67. package/dist/parser/const.js +60 -0
  68. package/dist/parser/const.js.map +1 -0
  69. package/dist/parser/lru.js +109 -0
  70. package/dist/parser/lru.js.map +1 -0
  71. package/dist/parser/parser.d.ts +25 -0
  72. package/dist/parser/parser.js +115 -0
  73. package/dist/parser/parser.js.map +1 -0
  74. package/dist/parser/tokenizer.js +69 -0
  75. package/dist/parser/tokenizer.js.map +1 -0
  76. package/dist/parser/types.d.ts +51 -0
  77. package/dist/parser/types.js +46 -0
  78. package/dist/parser/types.js.map +1 -0
  79. package/dist/pipeline/conditions.d.ts +134 -0
  80. package/dist/pipeline/conditions.js +406 -0
  81. package/dist/pipeline/conditions.js.map +1 -0
  82. package/dist/pipeline/exclusive.js +230 -0
  83. package/dist/pipeline/exclusive.js.map +1 -0
  84. package/dist/pipeline/index.d.ts +55 -0
  85. package/dist/pipeline/index.js +708 -0
  86. package/dist/pipeline/index.js.map +1 -0
  87. package/dist/pipeline/materialize.js +1103 -0
  88. package/dist/pipeline/materialize.js.map +1 -0
  89. package/dist/pipeline/parseStateKey.d.ts +15 -0
  90. package/dist/pipeline/parseStateKey.js +446 -0
  91. package/dist/pipeline/parseStateKey.js.map +1 -0
  92. package/dist/pipeline/simplify.js +515 -0
  93. package/dist/pipeline/simplify.js.map +1 -0
  94. package/dist/pipeline/warnings.js +18 -0
  95. package/dist/pipeline/warnings.js.map +1 -0
  96. package/dist/plugins/index.d.ts +2 -0
  97. package/dist/plugins/okhsl-plugin.d.ts +35 -0
  98. package/dist/plugins/okhsl-plugin.js +97 -0
  99. package/dist/plugins/okhsl-plugin.js.map +1 -0
  100. package/dist/plugins/types.d.ts +87 -0
  101. package/dist/properties/index.js +222 -0
  102. package/dist/properties/index.js.map +1 -0
  103. package/dist/properties/property-type-resolver.d.ts +24 -0
  104. package/dist/properties/property-type-resolver.js +90 -0
  105. package/dist/properties/property-type-resolver.js.map +1 -0
  106. package/dist/rsc-cache.js +81 -0
  107. package/dist/rsc-cache.js.map +1 -0
  108. package/dist/ssr/astro-client.d.ts +1 -0
  109. package/dist/ssr/astro-client.js +24 -0
  110. package/dist/ssr/astro-client.js.map +1 -0
  111. package/dist/ssr/astro-middleware.d.ts +15 -0
  112. package/dist/ssr/astro-middleware.js +19 -0
  113. package/dist/ssr/astro-middleware.js.map +1 -0
  114. package/dist/ssr/astro.d.ts +106 -0
  115. package/dist/ssr/astro.js +149 -0
  116. package/dist/ssr/astro.js.map +1 -0
  117. package/dist/ssr/async-storage.d.ts +17 -0
  118. package/dist/ssr/async-storage.js +44 -0
  119. package/dist/ssr/async-storage.js.map +1 -0
  120. package/dist/ssr/collect-auto-properties.js +58 -0
  121. package/dist/ssr/collect-auto-properties.js.map +1 -0
  122. package/dist/ssr/collector.d.ts +102 -0
  123. package/dist/ssr/collector.js +227 -0
  124. package/dist/ssr/collector.js.map +1 -0
  125. package/dist/ssr/context.js +16 -0
  126. package/dist/ssr/context.js.map +1 -0
  127. package/dist/ssr/format-global-rules.js +22 -0
  128. package/dist/ssr/format-global-rules.js.map +1 -0
  129. package/dist/ssr/format-keyframes.js +69 -0
  130. package/dist/ssr/format-keyframes.js.map +1 -0
  131. package/dist/ssr/format-property.js +49 -0
  132. package/dist/ssr/format-property.js.map +1 -0
  133. package/dist/ssr/format-rules.js +73 -0
  134. package/dist/ssr/format-rules.js.map +1 -0
  135. package/dist/ssr/hydrate.d.ts +22 -0
  136. package/dist/ssr/hydrate.js +49 -0
  137. package/dist/ssr/hydrate.js.map +1 -0
  138. package/dist/ssr/index.d.ts +4 -0
  139. package/dist/ssr/index.js +10 -0
  140. package/dist/ssr/index.js.map +1 -0
  141. package/dist/ssr/next.d.ts +45 -0
  142. package/dist/ssr/next.js +75 -0
  143. package/dist/ssr/next.js.map +1 -0
  144. package/dist/ssr/ssr-collector-ref.js +29 -0
  145. package/dist/ssr/ssr-collector-ref.js.map +1 -0
  146. package/dist/states/index.d.ts +49 -0
  147. package/dist/states/index.js +170 -0
  148. package/dist/states/index.js.map +1 -0
  149. package/dist/static/index.d.ts +5 -0
  150. package/dist/static/index.js +4 -0
  151. package/dist/static/inject.d.ts +5 -0
  152. package/dist/static/inject.js +17 -0
  153. package/dist/static/inject.js.map +1 -0
  154. package/dist/static/tastyStatic.d.ts +46 -0
  155. package/dist/static/tastyStatic.js +30 -0
  156. package/dist/static/tastyStatic.js.map +1 -0
  157. package/dist/static/types.d.ts +49 -0
  158. package/dist/static/types.js +24 -0
  159. package/dist/static/types.js.map +1 -0
  160. package/dist/styles/border.d.ts +25 -0
  161. package/dist/styles/border.js +120 -0
  162. package/dist/styles/border.js.map +1 -0
  163. package/dist/styles/color.d.ts +14 -0
  164. package/dist/styles/color.js +26 -0
  165. package/dist/styles/color.js.map +1 -0
  166. package/dist/styles/const.js +17 -0
  167. package/dist/styles/const.js.map +1 -0
  168. package/dist/styles/createStyle.js +79 -0
  169. package/dist/styles/createStyle.js.map +1 -0
  170. package/dist/styles/dimension.js +109 -0
  171. package/dist/styles/dimension.js.map +1 -0
  172. package/dist/styles/directional.js +133 -0
  173. package/dist/styles/directional.js.map +1 -0
  174. package/dist/styles/display.d.ts +30 -0
  175. package/dist/styles/display.js +73 -0
  176. package/dist/styles/display.js.map +1 -0
  177. package/dist/styles/fade.d.ts +15 -0
  178. package/dist/styles/fade.js +62 -0
  179. package/dist/styles/fade.js.map +1 -0
  180. package/dist/styles/fill.d.ts +42 -0
  181. package/dist/styles/fill.js +51 -0
  182. package/dist/styles/fill.js.map +1 -0
  183. package/dist/styles/flow.d.ts +16 -0
  184. package/dist/styles/flow.js +12 -0
  185. package/dist/styles/flow.js.map +1 -0
  186. package/dist/styles/gap.d.ts +31 -0
  187. package/dist/styles/gap.js +38 -0
  188. package/dist/styles/gap.js.map +1 -0
  189. package/dist/styles/height.d.ts +17 -0
  190. package/dist/styles/height.js +19 -0
  191. package/dist/styles/height.js.map +1 -0
  192. package/dist/styles/index.d.ts +1 -0
  193. package/dist/styles/index.js +8 -0
  194. package/dist/styles/index.js.map +1 -0
  195. package/dist/styles/inset.d.ts +24 -0
  196. package/dist/styles/inset.js +34 -0
  197. package/dist/styles/inset.js.map +1 -0
  198. package/dist/styles/list.d.ts +16 -0
  199. package/dist/styles/list.js +100 -0
  200. package/dist/styles/list.js.map +1 -0
  201. package/dist/styles/margin.d.ts +24 -0
  202. package/dist/styles/margin.js +32 -0
  203. package/dist/styles/margin.js.map +1 -0
  204. package/dist/styles/outline.d.ts +29 -0
  205. package/dist/styles/outline.js +55 -0
  206. package/dist/styles/outline.js.map +1 -0
  207. package/dist/styles/padding.d.ts +24 -0
  208. package/dist/styles/padding.js +32 -0
  209. package/dist/styles/padding.js.map +1 -0
  210. package/dist/styles/placement.d.ts +37 -0
  211. package/dist/styles/placement.js +74 -0
  212. package/dist/styles/placement.js.map +1 -0
  213. package/dist/styles/predefined.d.ts +71 -0
  214. package/dist/styles/predefined.js +237 -0
  215. package/dist/styles/predefined.js.map +1 -0
  216. package/dist/styles/preset.d.ts +52 -0
  217. package/dist/styles/preset.js +127 -0
  218. package/dist/styles/preset.js.map +1 -0
  219. package/dist/styles/radius.d.ts +12 -0
  220. package/dist/styles/radius.js +83 -0
  221. package/dist/styles/radius.js.map +1 -0
  222. package/dist/styles/scrollMargin.d.ts +24 -0
  223. package/dist/styles/scrollMargin.js +32 -0
  224. package/dist/styles/scrollMargin.js.map +1 -0
  225. package/dist/styles/scrollbar.d.ts +25 -0
  226. package/dist/styles/scrollbar.js +51 -0
  227. package/dist/styles/scrollbar.js.map +1 -0
  228. package/dist/styles/shadow.d.ts +14 -0
  229. package/dist/styles/shadow.js +25 -0
  230. package/dist/styles/shadow.js.map +1 -0
  231. package/dist/styles/shared.js +17 -0
  232. package/dist/styles/shared.js.map +1 -0
  233. package/dist/styles/transition.d.ts +14 -0
  234. package/dist/styles/transition.js +159 -0
  235. package/dist/styles/transition.js.map +1 -0
  236. package/dist/styles/types.d.ts +564 -0
  237. package/dist/styles/width.d.ts +17 -0
  238. package/dist/styles/width.js +19 -0
  239. package/dist/styles/width.js.map +1 -0
  240. package/dist/tasty.d.ts +134 -0
  241. package/dist/tasty.js +243 -0
  242. package/dist/tasty.js.map +1 -0
  243. package/dist/types.d.ts +184 -0
  244. package/dist/utils/cache-wrapper.js +21 -0
  245. package/dist/utils/cache-wrapper.js.map +1 -0
  246. package/dist/utils/case-converter.js +8 -0
  247. package/dist/utils/case-converter.js.map +1 -0
  248. package/dist/utils/color-math.d.ts +46 -0
  249. package/dist/utils/color-math.js +749 -0
  250. package/dist/utils/color-math.js.map +1 -0
  251. package/dist/utils/color-space.d.ts +5 -0
  252. package/dist/utils/color-space.js +228 -0
  253. package/dist/utils/color-space.js.map +1 -0
  254. package/dist/utils/colors.d.ts +5 -0
  255. package/dist/utils/colors.js +10 -0
  256. package/dist/utils/colors.js.map +1 -0
  257. package/dist/utils/css-types.d.ts +7 -0
  258. package/dist/utils/deps-equal.js +15 -0
  259. package/dist/utils/deps-equal.js.map +1 -0
  260. package/dist/utils/dotize.d.ts +26 -0
  261. package/dist/utils/dotize.js +122 -0
  262. package/dist/utils/dotize.js.map +1 -0
  263. package/dist/utils/filter-base-props.d.ts +15 -0
  264. package/dist/utils/filter-base-props.js +45 -0
  265. package/dist/utils/filter-base-props.js.map +1 -0
  266. package/dist/utils/get-display-name.d.ts +7 -0
  267. package/dist/utils/get-display-name.js +10 -0
  268. package/dist/utils/get-display-name.js.map +1 -0
  269. package/dist/utils/has-keys.js +13 -0
  270. package/dist/utils/has-keys.js.map +1 -0
  271. package/dist/utils/hash.js +14 -0
  272. package/dist/utils/hash.js.map +1 -0
  273. package/dist/utils/is-dev-env.js +19 -0
  274. package/dist/utils/is-dev-env.js.map +1 -0
  275. package/dist/utils/is-valid-element-type.js +15 -0
  276. package/dist/utils/is-valid-element-type.js.map +1 -0
  277. package/dist/utils/merge-styles.d.ts +7 -0
  278. package/dist/utils/merge-styles.js +145 -0
  279. package/dist/utils/merge-styles.js.map +1 -0
  280. package/dist/utils/mod-attrs.d.ts +6 -0
  281. package/dist/utils/mod-attrs.js +20 -0
  282. package/dist/utils/mod-attrs.js.map +1 -0
  283. package/dist/utils/process-tokens.d.ts +17 -0
  284. package/dist/utils/process-tokens.js +83 -0
  285. package/dist/utils/process-tokens.js.map +1 -0
  286. package/dist/utils/resolve-recipes.d.ts +17 -0
  287. package/dist/utils/resolve-recipes.js +146 -0
  288. package/dist/utils/resolve-recipes.js.map +1 -0
  289. package/dist/utils/selector-transform.js +32 -0
  290. package/dist/utils/selector-transform.js.map +1 -0
  291. package/dist/utils/string.js +8 -0
  292. package/dist/utils/string.js.map +1 -0
  293. package/dist/utils/styles.d.ts +99 -0
  294. package/dist/utils/styles.js +220 -0
  295. package/dist/utils/styles.js.map +1 -0
  296. package/dist/utils/typography.d.ts +58 -0
  297. package/dist/utils/typography.js +51 -0
  298. package/dist/utils/typography.js.map +1 -0
  299. package/dist/utils/warnings.d.ts +16 -0
  300. package/dist/utils/warnings.js +16 -0
  301. package/dist/utils/warnings.js.map +1 -0
  302. package/dist/zero/babel.d.ts +195 -0
  303. package/dist/zero/babel.js +456 -0
  304. package/dist/zero/babel.js.map +1 -0
  305. package/dist/zero/css-writer.d.ts +45 -0
  306. package/dist/zero/css-writer.js +73 -0
  307. package/dist/zero/css-writer.js.map +1 -0
  308. package/dist/zero/extractor.d.ts +24 -0
  309. package/dist/zero/extractor.js +266 -0
  310. package/dist/zero/extractor.js.map +1 -0
  311. package/dist/zero/index.d.ts +3 -0
  312. package/dist/zero/index.js +3 -0
  313. package/dist/zero/next.d.ts +86 -0
  314. package/dist/zero/next.js +143 -0
  315. package/dist/zero/next.js.map +1 -0
  316. package/docs/PIPELINE.md +519 -0
  317. package/docs/README.md +31 -0
  318. package/docs/adoption.md +298 -0
  319. package/docs/comparison.md +419 -0
  320. package/docs/configuration.md +389 -0
  321. package/docs/debug.md +318 -0
  322. package/docs/design-system.md +436 -0
  323. package/docs/dsl.md +688 -0
  324. package/docs/getting-started.md +217 -0
  325. package/docs/injector.md +544 -0
  326. package/docs/methodology.md +616 -0
  327. package/docs/react-api.md +557 -0
  328. package/docs/ssr.md +440 -0
  329. package/docs/styles.md +596 -0
  330. package/docs/tasty-static.md +532 -0
  331. package/package.json +221 -0
  332. package/tasty.config.ts +14 -0
package/docs/dsl.md ADDED
@@ -0,0 +1,688 @@
1
+ # Style DSL Reference
2
+
3
+ This is the Tasty style language reference — the value syntax, state mappings, tokens, units, extending semantics, and special declarations that apply to both runtime `tasty()` and build-time `tastyStatic()`.
4
+
5
+ For the runtime React API (`tasty()`, hooks, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
6
+
7
+ ---
8
+
9
+ ## Dictionary
10
+
11
+ ### Style Mapping
12
+
13
+ Object where keys represent states and values are the styles to apply:
14
+
15
+ ```jsx
16
+ fill: { '': '#white', hovered: '#gray.05', 'theme=danger': '#red' }
17
+ ```
18
+
19
+ #### State Key Types
20
+
21
+ | Syntax | Example | Generated CSS |
22
+ |--------|---------|---------------|
23
+ | Boolean modifier | `hovered` | `[data-hovered]` |
24
+ | Value modifier | `theme=danger` | `[data-theme="danger"]` |
25
+ | Pseudo-class | `:hover` | `:hover` |
26
+ | Class selector | `.active` | `.active` |
27
+ | Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]` |
28
+ | Combined (AND) | `hovered & .active` | `[data-hovered].active` |
29
+ | Combined (OR) | `hovered \| focused` | `[data-hovered], [data-focused]` |
30
+ | Negated (NOT) | `!disabled` | `:not([data-disabled])` |
31
+ | Exclusive (XOR) | `hovered ^ focused` | `[data-hovered]:not([data-focused]), :not([data-hovered])[data-focused]` |
32
+
33
+ Operator precedence (highest to lowest): `!` (NOT) > `^` (XOR) > `|` (OR) > `&` (AND). Use parentheses to override: `hovered & (pressed ^ focused)`.
34
+
35
+ `^` (XOR) means "exactly one of" — `A ^ B` expands to `(A & !B) | (!A & B)`. This is useful for mutually exclusive states where exactly one should be active:
36
+
37
+ ```jsx
38
+ fill: {
39
+ '': '#surface',
40
+ 'hovered ^ focused': '#accent', // active when hovered OR focused, but not both
41
+ }
42
+ ```
43
+
44
+ ### Sub-element
45
+
46
+ Element styled using a capitalized key. Identified by `data-element` attribute:
47
+
48
+ ```jsx
49
+ styles: { Title: { preset: 'h3' } }
50
+ // Targets: <div data-element="Title">
51
+ ```
52
+
53
+ #### Selector Affix (`$`)
54
+
55
+ Control how a sub-element selector attaches to the root selector using the `$` property inside the sub-element's styles:
56
+
57
+ | Pattern | Result | Description |
58
+ |---------|--------|-------------|
59
+ | *(none)* | ` [el]` | Descendant (default) |
60
+ | `>` | `> [el]` | Direct child |
61
+ | `>Body>Row>` | `> [Body] > [Row] > [el]` | Chained elements |
62
+ | `h1` | ` h1` | Tag selector (no key injection) |
63
+ | `h1 >` | ` h1 > [el]` | Key is direct child of tag |
64
+ | `h1 *` | ` h1 *` | Any descendant of tag |
65
+ | `*` | ` *` | All descendants |
66
+ | `::before` | `::before` | Root pseudo (no key) |
67
+ | `@::before` | `[el]::before` | Pseudo on the sub-element |
68
+ | `>@:hover` | `> [el]:hover` | Pseudo-class on the sub-element |
69
+ | `>@.active` | `> [el].active` | Class on the sub-element |
70
+
71
+ Rules for key injection (`[data-element="..."]`):
72
+
73
+ - **Trailing combinator** (`>`, `+`, `~`) — key is injected after it
74
+ - **Uppercase element name** (`Body`, `Row`) — key is injected as descendant
75
+ - **HTML tag** (`h1`, `a`, `span`) — no key injection; the tag IS the selector
76
+ - **Universal selector** (`*`) — no key injection
77
+ - **Pseudo / class / attribute** — no key injection
78
+
79
+ The `@` placeholder marks exactly where `[data-element="..."]` is injected, allowing you to attach pseudo-classes, pseudo-elements, or class selectors directly to the sub-element instead of the root:
80
+
81
+ ```jsx
82
+ const List = tasty({
83
+ styles: {
84
+ Item: {
85
+ $: '>@:last-child',
86
+ border: 'none',
87
+ },
88
+ },
89
+ });
90
+ // → .t0 > [data-element="Item"]:last-child { border: none }
91
+ ```
92
+
93
+ ### Color Token
94
+
95
+ Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
96
+
97
+ ```jsx
98
+ fill: '#purple.5' // → var(--purple-color) with 50% opacity
99
+ ```
100
+
101
+ ### Modifier
102
+
103
+ State value via `mods` prop that generates `data-*` attributes:
104
+
105
+ ```jsx
106
+ mods={{ hovered: true, theme: 'danger' }}
107
+ // → data-hovered="" data-theme="danger"
108
+ ```
109
+
110
+ Modifiers can also be exposed as top-level component props via `modProps` — see [Runtime — Mod Props](react-api.md#mod-props).
111
+
112
+ ---
113
+
114
+ ## Color Tokens & Opacity
115
+
116
+ ```jsx
117
+ color: '#purple', // Full opacity
118
+ color: '#purple.5', // 50% opacity
119
+ color: '#purple.05', // 5% opacity
120
+ fill: '#current', // → currentcolor
121
+ fill: '#current.5', // → color-mix(in oklab, currentcolor 50%, transparent)
122
+ color: '(#primary, #secondary)', // Fallback syntax
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Built-in Units
128
+
129
+ | Unit | Description | Example | CSS Output |
130
+ |------|-------------|---------|------------|
131
+ | `x` | Gap multiplier | `2x` | `calc(var(--gap) * 2)` |
132
+ | `r` | Border radius | `1r` | `var(--radius)` |
133
+ | `cr` | Card border radius | `1cr` | `var(--card-radius)` |
134
+ | `bw` | Border width | `2bw` | `calc(var(--border-width) * 2)` |
135
+ | `ow` | Outline width | `1ow` | `var(--outline-width)` |
136
+ | `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)` |
137
+
138
+ You can register additional custom units via [`configure()`](configuration.md#options).
139
+
140
+ ---
141
+
142
+ ## Replace Tokens
143
+
144
+ Tokens defined via [`configure({ replaceTokens })`](configuration.md#replace-tokens-parse-time-substitution) are replaced at parse time and baked into the generated CSS:
145
+
146
+ ```jsx
147
+ const Card = tasty({
148
+ styles: {
149
+ padding: '$card-padding',
150
+ fill: '#surface',
151
+ border: '1bw solid #accent',
152
+ },
153
+ });
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Recipes
159
+
160
+ Apply predefined style bundles (defined via [`configure({ recipes })`](configuration.md#recipes)) using the `recipe` style property:
161
+
162
+ ```jsx
163
+ const Card = tasty({
164
+ styles: {
165
+ recipe: 'card',
166
+ color: '#text',
167
+ },
168
+ });
169
+
170
+ // Compose multiple recipes
171
+ const ElevatedCard = tasty({
172
+ styles: {
173
+ recipe: 'card elevated',
174
+ color: '#text',
175
+ },
176
+ });
177
+ ```
178
+
179
+ **Post-merge recipes (`/` separator):**
180
+
181
+ Recipes listed after `/` are applied *after* component styles using `mergeStyles`:
182
+
183
+ ```jsx
184
+ const Input = tasty({
185
+ styles: {
186
+ recipe: 'reset input / input-autofill',
187
+ preset: 't3',
188
+ },
189
+ });
190
+ ```
191
+
192
+ Use `none` to skip base recipes and apply only post recipes:
193
+
194
+ ```jsx
195
+ const Custom = tasty({
196
+ styles: {
197
+ recipe: 'none / disabled',
198
+ padding: '2x',
199
+ },
200
+ });
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Extending vs. Replacing State Maps
206
+
207
+ When a style property uses a state map, the merge behavior depends on whether the child provides a `''` (default) key:
208
+
209
+ - **No `''` key** — extend mode: parent states are preserved, child adds/overrides
210
+ - **Has `''` key** — replace mode: child defines everything from scratch
211
+
212
+ ```jsx
213
+ // Parent has: fill: { '': '#white', hovered: '#blue', disabled: '#gray' }
214
+
215
+ // Extend — no '' key, parent states preserved
216
+ const MyButton = tasty(Button, {
217
+ styles: {
218
+ fill: {
219
+ 'loading': '#yellow', // append new state
220
+ 'disabled': '#gray.20', // override existing state in place
221
+ },
222
+ },
223
+ });
224
+
225
+ // Replace — has '' key, parent states dropped
226
+ const MyButton = tasty(Button, {
227
+ styles: {
228
+ fill: {
229
+ '': '#red',
230
+ 'hovered': '#blue',
231
+ },
232
+ },
233
+ });
234
+ ```
235
+
236
+ Use `'@inherit'` to pull a parent state value. In extend mode it repositions the state; in replace mode it cherry-picks it:
237
+
238
+ ```jsx
239
+ // Extend mode: reposition disabled to end (highest CSS priority)
240
+ fill: {
241
+ 'loading': '#yellow',
242
+ disabled: '@inherit',
243
+ }
244
+
245
+ // Replace mode: cherry-pick disabled from parent
246
+ fill: {
247
+ '': '#red',
248
+ disabled: '@inherit',
249
+ }
250
+ ```
251
+
252
+ Use `null` inside a state map to remove a state, or `false` to block it entirely (tombstone):
253
+
254
+ ```jsx
255
+ fill: { pressed: null } // removes pressed from the result
256
+ fill: { disabled: false } // tombstone — no CSS for disabled, blocks recipe too
257
+ ```
258
+
259
+ ### Resetting Properties with `null` and `false`
260
+
261
+ ```jsx
262
+ const SimpleButton = tasty(Button, {
263
+ styles: {
264
+ fill: null, // discard parent's fill, let recipe fill in
265
+ border: false, // no border at all (tombstone — blocks recipe too)
266
+ },
267
+ });
268
+ ```
269
+
270
+ | Value | Meaning | Recipe fills in? |
271
+ |-------|---------|-----------------|
272
+ | `undefined` | Not provided — parent preserved | N/A |
273
+ | `null` | Intentional unset — parent discarded | Yes |
274
+ | `false` | Tombstone — blocks everything | No |
275
+
276
+ ---
277
+
278
+ ## Advanced States (`@` prefix)
279
+
280
+ | Prefix | Purpose | Example |
281
+ |--------|---------|---------|
282
+ | `@media` | Media queries | `@media(w < 768px)` |
283
+ | `@(...)` | Container queries | `@(panel, w >= 300px)` |
284
+ | `@supports` | Feature/selector support | `@supports(display: grid)` |
285
+ | `@root` | Root element states | `@root(schema=dark)` |
286
+ | `@parent` | Parent/ancestor element states | `@parent(hovered)` |
287
+ | `@own` | Sub-element's own state | `@own(hovered)` |
288
+ | `@starting` | Entry animation | `@starting` |
289
+ | `:is()` | CSS `:is()` structural pseudo-class | `:is(fieldset > label)` |
290
+ | `:has()` | CSS `:has()` relational pseudo-class | `:has(> Icon)` |
291
+ | `:not()` | CSS `:not()` negation (prefer `!:is()`) | `:not(:first-child)` |
292
+ | `:where()` | CSS `:where()` (zero specificity) | `:where(Section)` |
293
+
294
+ ### `@media(...)` — Media Queries
295
+
296
+ Media queries support dimension shorthands and custom unit expansion:
297
+
298
+ | Shorthand | Expands to |
299
+ |-----------|------------|
300
+ | `w` | `width` |
301
+ | `h` | `height` |
302
+
303
+ ```jsx
304
+ fill: {
305
+ '': '#surface',
306
+ '@media(w < 768px)': '#surface-mobile',
307
+ '@media(600px <= w < 1200px)': '#surface-tablet',
308
+ '@media(prefers-color-scheme: dark)': '#surface-dark',
309
+ }
310
+ ```
311
+
312
+ | Tasty syntax | CSS output |
313
+ |--------------|------------|
314
+ | `@media(w < 768px)` | `@media (width < 768px)` |
315
+ | `@media(600px <= w < 1200px)` | `@media (600px <= width < 1200px)` |
316
+ | `@media:print` | `@media print` |
317
+ | `@media:screen` | `@media screen` |
318
+ | `@media(prefers-color-scheme: dark)` | `@media (prefers-color-scheme: dark)` |
319
+ | `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
320
+
321
+ Custom units work inside media queries: `@media(w < 40x)` → `@media (width < calc(var(--gap) * 40))`.
322
+
323
+ In practice, define state aliases via `configure({ states })` and use `@mobile` instead of writing the full query in every component.
324
+
325
+ ### `@(...)` — Container Queries
326
+
327
+ Container queries use the syntax `@(name, condition)` for named containers or `@(condition)` for the nearest ancestor container. Dimension shorthands (`w`, `h`, `is`, `bs`) are expanded the same way as `@media`.
328
+
329
+ | Shorthand | Expands to |
330
+ |-----------|------------|
331
+ | `w` | `width` |
332
+ | `h` | `height` |
333
+ | `is` | `inline-size` |
334
+ | `bs` | `block-size` |
335
+
336
+ ```jsx
337
+ const Panel = tasty({
338
+ styles: {
339
+ flow: {
340
+ '': 'column',
341
+ '@(layout, w >= 600px)': 'row',
342
+ },
343
+ },
344
+ });
345
+ ```
346
+
347
+ | Tasty syntax | CSS output |
348
+ |--------------|------------|
349
+ | `@(layout, w < 600px)` | `@container layout (width < 600px)` |
350
+ | `@(w < 600px)` | `@container (width < 600px)` |
351
+ | `@(layout, $variant=danger)` | `@container layout style(--variant: "danger")` |
352
+ | `@(layout, $compact)` | `@container layout style(--compact)` |
353
+ | `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)` |
354
+ | `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)` |
355
+
356
+ Container style queries use `$prop` (boolean) or `$prop=value` syntax, which maps to CSS `style(--prop)` or `style(--prop: "value")`.
357
+
358
+ ### `@supports(...)` — Feature Queries
359
+
360
+ Feature queries test CSS property support. Use `$` as the first argument to test selector support:
361
+
362
+ | Tasty syntax | CSS output |
363
+ |--------------|------------|
364
+ | `@supports(display: grid)` | `@supports (display: grid)` |
365
+ | `@supports($, :has(*))` | `@supports selector(:has(*))` |
366
+ | `!@supports(display: grid)` | `@supports (not (display: grid))` |
367
+
368
+ ```jsx
369
+ display: {
370
+ '': 'flex',
371
+ '@supports(display: grid)': 'grid',
372
+ }
373
+ ```
374
+
375
+ ### `@root(...)` — Root Element States
376
+
377
+ Root states generate selectors on the `:root` element. They are useful for theme modes, feature flags, and other page-level conditions:
378
+
379
+ These docs use `data-schema` in examples. If your app standardizes on a different root attribute, keep the same pattern and swap the attribute name consistently in your aliases and selectors.
380
+
381
+ ```jsx
382
+ color: {
383
+ '': '#text',
384
+ '@root(schema=dark)': '#text-on-dark',
385
+ '@root(.premium-user)': '#gold',
386
+ }
387
+ ```
388
+
389
+ | Tasty syntax | CSS selector |
390
+ |--------------|-------------|
391
+ | `@root(schema=dark)` | `:root[data-schema="dark"]` |
392
+ | `@root(hovered)` | `:root[data-hovered]` |
393
+ | `@root(.premium-user)` | `:root.premium-user` |
394
+ | `@root([lang="en"])` | `:root[lang="en"]` |
395
+ | `!@root(schema=dark)` | `:root:not([data-schema="dark"])` |
396
+
397
+ Root conditions are prepended to the component selector: `:root[data-schema="dark"] .t0.t0 { ... }`.
398
+
399
+ ### `@own(...)` — Sub-element's Own State
400
+
401
+ By default, state keys in sub-element styles refer to the root component's state context. Use `@own(...)` when the sub-element should react to its own state:
402
+
403
+ ```jsx
404
+ const Nav = tasty({
405
+ styles: {
406
+ NavItem: {
407
+ color: {
408
+ '': '#text',
409
+ '@own(:hover)': '#primary',
410
+ '@own(:focus-visible)': '#primary',
411
+ 'selected': '#primary', // root-level modifier
412
+ },
413
+ },
414
+ },
415
+ elements: { NavItem: 'a' },
416
+ });
417
+ ```
418
+
419
+ | Tasty syntax (inside sub-element) | CSS output |
420
+ |-----------------------------------|------------|
421
+ | `@own(:hover)` | `:hover` on the sub-element selector |
422
+ | `@own(hovered)` | `[data-hovered]` on the sub-element selector |
423
+ | `@own(theme=dark)` | `[data-theme="dark"]` on the sub-element selector |
424
+
425
+ `@own()` is only valid inside sub-element styles. Using it on root styles emits a warning and is treated as a regular modifier.
426
+
427
+ ### `@starting` — Entry Animation
428
+
429
+ Wraps the rule in `@starting-style`, enabling CSS entry animations for elements as they appear in the DOM:
430
+
431
+ ```jsx
432
+ const FadeIn = tasty({
433
+ styles: {
434
+ opacity: { '': '1', '@starting': '0' },
435
+ transform: { '': 'scale(1)', '@starting': 'scale(0.95)' },
436
+ transition: 'opacity 0.3s, translate 0.3s',
437
+ },
438
+ });
439
+ ```
440
+
441
+ | Tasty syntax | CSS output |
442
+ |--------------|------------|
443
+ | `@starting` | `@starting-style { .t0.t0 { ... } }` |
444
+
445
+ ### `@parent(...)` — Parent Element States
446
+
447
+ Style based on ancestor element attributes. Uses `:is([selector] *)` / `:not([selector] *)` for symmetric, composable parent checks. Boolean logic (`&`, `|`, `!`, `^`) is supported inside `@parent()`.
448
+
449
+ ```jsx
450
+ const Highlight = tasty({
451
+ styles: {
452
+ fill: {
453
+ '': '#white',
454
+ '@parent(hovered)': '#gray.05', // Any ancestor has [data-hovered]
455
+ '@parent(theme=dark, >)': '#dark-02', // Direct parent has [data-theme="dark"]
456
+ },
457
+ },
458
+ });
459
+ ```
460
+
461
+ | Syntax | CSS Output |
462
+ |--------|------------|
463
+ | `@parent(hovered)` | `:is([data-hovered] *)` |
464
+ | `!@parent(hovered)` | `:not([data-hovered] *)` |
465
+ | `@parent(hovered, >)` | `:is([data-hovered] > *)` (direct parent) |
466
+ | `@parent(.active)` | `:is(.active *)` |
467
+ | `@parent(hovered & focused)` | `:is([data-hovered][data-focused] *)` (same ancestor) |
468
+ | `@parent(hovered) & @parent(focused)` | `:is([data-hovered] *):is([data-focused] *)` (independent ancestors) |
469
+ | `@parent(hovered \| focused)` | `:is([data-hovered] *, [data-focused] *)` (OR inside single wrapper) |
470
+
471
+ For sub-elements, the parent check applies to the root element's ancestors:
472
+
473
+ ```jsx
474
+ const Card = tasty({
475
+ styles: {
476
+ Label: {
477
+ color: {
478
+ '': '#text',
479
+ '@parent(hovered)': '#primary',
480
+ },
481
+ },
482
+ },
483
+ });
484
+ // → .t0.t0:is([data-hovered] *) [data-element="Label"]
485
+ ```
486
+
487
+ ### `:is()`, `:has()` — CSS Structural Pseudo-classes
488
+
489
+ Use CSS structural pseudo-classes directly in state keys. Capitalized words become `[data-element="..."]` selectors; lowercase words are HTML tags. A trailing combinator (`>`, `+`, `~`) is auto-completed with `*`.
490
+
491
+ `:where()` and `:not()` are also supported but rarely needed — use `:is()` and `!` negation instead.
492
+
493
+ > **Performance warning:** CSS structural pseudo-classes — especially `:has()` — can be costly for the browser to evaluate because they require inspecting the DOM tree beyond the matched element. Tasty already provides a rich, purpose-built state system (`@parent()`, `@own()`, modifiers, boolean logic) that covers the vast majority of use cases without the performance trade-off. **Prefer Tasty's built-in mechanisms and treat `:has()` / `:is()` as a last resort** for conditions that cannot be expressed any other way.
494
+
495
+ ```jsx
496
+ const Card = tasty({
497
+ styles: {
498
+ display: {
499
+ '': 'block',
500
+ ':has(> Icon)': 'grid', // has Icon as direct child
501
+ ':has(+ Icon)': 'grid', // immediately followed by an Icon sibling
502
+ ':has(~ Icon)': 'grid', // has an Icon sibling somewhere after
503
+ ':has(Icon +)': 'grid', // immediately preceded by an Icon sibling (auto-completes to `Icon + *`)
504
+ ':has(Icon ~)': 'grid', // has an Icon sibling somewhere before (auto-completes to `Icon ~ *`)
505
+ ':is(fieldset > label)': 'inline', // is a label inside a fieldset (HTML tags)
506
+ '!:has(> Icon)': 'flex', // negation: no Icon child
507
+ },
508
+ },
509
+ });
510
+ ```
511
+
512
+ | Syntax | CSS Output | Meaning |
513
+ |--------|------------|---------|
514
+ | `:has(> Icon)` | `:has(> [data-element="Icon"])` | Has Icon as direct child |
515
+ | `:has(+ Icon)` | `:has(+ [data-element="Icon"])` | Immediately followed by an Icon sibling |
516
+ | `:has(~ Icon)` | `:has(~ [data-element="Icon"])` | Has an Icon sibling somewhere after |
517
+ | `:has(Icon +)` | `:has([data-element="Icon"] + *)` | Immediately preceded by an Icon sibling |
518
+ | `:has(Icon ~)` | `:has([data-element="Icon"] ~ *)` | Has an Icon sibling somewhere before |
519
+ | `:has(>)` | `:has(> *)` | Has any direct child |
520
+ | `:is(> Field + input)` | `:is(> [data-element="Field"] + input)` | Structural match |
521
+ | `:has(button)` | `:has(button)` | HTML tag (lowercase, unchanged) |
522
+ | `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
523
+ | `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
524
+
525
+ Combine with other states using boolean logic (`&`, `|`, `!`, `^`):
526
+
527
+ ```jsx
528
+ ':has(> Icon) & hovered' // AND: structural + data attribute
529
+ '@parent(hovered) & :has(> Icon)' // AND: parent check + structural
530
+ ':has(> Icon) | :has(> Button)' // OR: either sub-element present
531
+ ':has(> Icon) ^ :has(> Button)' // XOR: exactly one present
532
+ ```
533
+
534
+ > **Nesting limit:** The state key parser supports up to 2 levels of nested parentheses inside `:is()`, `:has()`, `:not()`, and `:where()` — e.g. `:has(Input:not(:disabled))` works, but 3+ levels like `:has(:is(:not(:hover)))` will not be tokenized correctly. This covers virtually all practical use cases.
535
+
536
+ ---
537
+
538
+ ## Keyframes
539
+
540
+ Define animations inline using the `@keyframes` key in styles:
541
+
542
+ ```jsx
543
+ const Pulse = tasty({
544
+ styles: {
545
+ animation: 'pulse 2s infinite',
546
+ '@keyframes': {
547
+ pulse: {
548
+ '0%, 100%': { transform: 'scale(1)' },
549
+ '50%': { transform: 'scale(1.05)' },
550
+ },
551
+ },
552
+ },
553
+ });
554
+ ```
555
+
556
+ ---
557
+
558
+ ## Properties (`@property`)
559
+
560
+ CSS cannot transition or animate custom properties unless the browser knows their type. Tasty solves this automatically — when you assign a concrete value to a custom property, the type is inferred and a CSS `@property` rule is registered behind the scenes:
561
+
562
+ ```jsx
563
+ const AnimatedGradient = tasty({
564
+ styles: {
565
+ '$gradient-angle': '0deg',
566
+ '#theme': 'okhsl(280 80% 50%)',
567
+ background: 'linear-gradient($gradient-angle, #theme, transparent)',
568
+ transition: '$$gradient-angle 0.3s, ##theme 0.3s',
569
+ },
570
+ });
571
+ ```
572
+
573
+ Here `$gradient-angle: '0deg'` is detected as `<angle>` and `#theme` as `<color>` (via the `#name` naming convention), so both transitions work without any manual `@property` declarations. Numeric types (`<number>`, `<length>`, `<percentage>`, `<angle>`, `<time>`) are inferred from values; `<color>` is inferred from `#name` tokens.
574
+
575
+ Use explicit `@properties` when you need non-default settings like `inherits: false`:
576
+
577
+ ```jsx
578
+ '@properties': {
579
+ '$gradient-angle': { syntax: '<angle>', inherits: false, initialValue: '0deg' },
580
+ },
581
+ ```
582
+
583
+ ---
584
+
585
+ ## Font Face (`@fontFace`)
586
+
587
+ Register custom fonts directly inside a `styles` object. Keys are font-family names, values are descriptor objects (or arrays of them for multiple weights/styles).
588
+
589
+ ```ts
590
+ const Heading = tasty({
591
+ styles: {
592
+ '@fontFace': {
593
+ 'Brand Sans': {
594
+ src: 'url("/fonts/brand-sans.woff2") format("woff2")',
595
+ fontDisplay: 'swap',
596
+ },
597
+ },
598
+ fontFamily: '"Brand Sans", sans-serif',
599
+ },
600
+ });
601
+ ```
602
+
603
+ ### Multiple weights
604
+
605
+ Supply an array to register several variants of the same family:
606
+
607
+ ```ts
608
+ '@fontFace': {
609
+ 'Brand Sans': [
610
+ { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
611
+ { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
612
+ ],
613
+ }
614
+ ```
615
+
616
+ ### Supported descriptors
617
+
618
+ | Descriptor | CSS property | Type |
619
+ |---|---|---|
620
+ | `src` (required) | `src` | `string` |
621
+ | `fontWeight` | `font-weight` | `string \| number` |
622
+ | `fontStyle` | `font-style` | `string` |
623
+ | `fontStretch` | `font-stretch` | `string` |
624
+ | `fontDisplay` | `font-display` | `'auto' \| 'block' \| 'swap' \| 'fallback' \| 'optional'` |
625
+ | `unicodeRange` | `unicode-range` | `string` |
626
+ | `ascentOverride` | `ascent-override` | `string` |
627
+ | `descentOverride` | `descent-override` | `string` |
628
+ | `lineGapOverride` | `line-gap-override` | `string` |
629
+ | `sizeAdjust` | `size-adjust` | `string` |
630
+ | `fontFeatureSettings` | `font-feature-settings` | `string` |
631
+ | `fontVariationSettings` | `font-variation-settings` | `string` |
632
+
633
+ > Font-face rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@font-face`.
634
+
635
+ ---
636
+
637
+ ## Counter Style (`@counterStyle`)
638
+
639
+ Define custom list markers via the CSS `@counter-style` at-rule. Keys are counter-style names, values are descriptor objects.
640
+
641
+ ```ts
642
+ const EmojiList = tasty({
643
+ tag: 'ol',
644
+ styles: {
645
+ '@counterStyle': {
646
+ thumbs: {
647
+ system: 'cyclic',
648
+ symbols: '"👍"',
649
+ suffix: '" "',
650
+ },
651
+ },
652
+ listStyleType: 'thumbs',
653
+ },
654
+ });
655
+ ```
656
+
657
+ ### Supported descriptors
658
+
659
+ | Descriptor | CSS property | Type |
660
+ |---|---|---|
661
+ | `system` (required) | `system` | `'cyclic' \| 'numeric' \| 'alphabetic' \| 'symbolic' \| 'additive' \| 'fixed' \| string` |
662
+ | `symbols` | `symbols` | `string` |
663
+ | `additiveSymbols` | `additive-symbols` | `string` |
664
+ | `prefix` | `prefix` | `string` |
665
+ | `suffix` | `suffix` | `string` |
666
+ | `negative` | `negative` | `string` |
667
+ | `range` | `range` | `string` |
668
+ | `pad` | `pad` | `string` |
669
+ | `fallback` | `fallback` | `string` |
670
+ | `speakAs` | `speak-as` | `string` |
671
+
672
+ > Counter-style rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@counter-style`.
673
+
674
+ ---
675
+
676
+ ## Style Properties
677
+
678
+ For a complete reference of all enhanced style properties — syntax, values, modifiers, and recommendations — see **[Style Properties Reference](styles.md)**.
679
+
680
+ ---
681
+
682
+ ## Learn more
683
+
684
+ - **[React API](react-api.md)** — `tasty()` factory, component props, variants, sub-elements, style functions
685
+ - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
686
+ - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
687
+ - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
688
+ - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin