@tenphi/tasty 0.0.0-snapshot.d2dcdeb → 0.0.0-snapshot.d4ccea8

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 (357) hide show
  1. package/README.md +181 -131
  2. package/dist/{ssr/async-storage.js → async-storage-DKK-wTD4.js} +2 -2
  3. package/dist/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/babel-eMs_zZjM.d.ts +83 -0
  5. package/dist/{ssr/collector.js → collector-BjkQEtbh.js} +111 -38
  6. package/dist/collector-BjkQEtbh.js.map +1 -0
  7. package/dist/{ssr/collector.d.ts → collector-DzeNxHOq.d.ts} +58 -7
  8. package/dist/config-DMslHK_e.d.ts +1298 -0
  9. package/dist/config-DX126v_w.js +12318 -0
  10. package/dist/config-DX126v_w.js.map +1 -0
  11. package/dist/{ssr/context.js → context-CA8YKeMn.js} +11 -3
  12. package/dist/context-CA8YKeMn.js.map +1 -0
  13. package/dist/core/index.d.ts +5 -34
  14. package/dist/core/index.js +6 -27
  15. package/dist/core-CMn-Tj2X.js +1573 -0
  16. package/dist/core-CMn-Tj2X.js.map +1 -0
  17. package/dist/{zero/extractor.js → css-writer-C8ShXK5R.js} +148 -24
  18. package/dist/css-writer-C8ShXK5R.js.map +1 -0
  19. package/dist/{ssr/format-global-rules.js → format-global-rules-DklyaXv-.js} +2 -2
  20. package/dist/format-global-rules-DklyaXv-.js.map +1 -0
  21. package/dist/{ssr/format-rules.js → format-rules-BkstDvlN.js} +59 -2
  22. package/dist/format-rules-BkstDvlN.js.map +1 -0
  23. package/dist/{ssr/hydrate.js → hydrate-DXXWZgm0.js} +4 -12
  24. package/dist/hydrate-DXXWZgm0.js.map +1 -0
  25. package/dist/index-BcsaHo8x.d.ts +1568 -0
  26. package/dist/index-CC0wykKn.d.ts +1909 -0
  27. package/dist/index.d.ts +5 -51
  28. package/dist/index.js +922 -36
  29. package/dist/index.js.map +1 -0
  30. package/dist/keyframes-Z0YLVOWq.js +493 -0
  31. package/dist/keyframes-Z0YLVOWq.js.map +1 -0
  32. package/dist/{utils/merge-styles.d.ts → merge-styles-BTFAkYSD.d.ts} +3 -3
  33. package/dist/{utils/merge-styles.js → merge-styles-Dpueyn82.js} +3 -4
  34. package/dist/merge-styles-Dpueyn82.js.map +1 -0
  35. package/dist/{utils/resolve-recipes.js → resolve-recipes-B2Vjr7ET.js} +4 -6
  36. package/dist/resolve-recipes-B2Vjr7ET.js.map +1 -0
  37. package/dist/ssr/astro-client.js +1 -1
  38. package/dist/ssr/astro-client.js.map +1 -1
  39. package/dist/ssr/astro-middleware-static.d.ts +16 -0
  40. package/dist/ssr/astro-middleware-static.js +18 -0
  41. package/dist/ssr/astro-middleware-static.js.map +1 -0
  42. package/dist/ssr/astro-middleware.d.ts +8 -6
  43. package/dist/ssr/astro-middleware.js +10 -10
  44. package/dist/ssr/astro-middleware.js.map +1 -1
  45. package/dist/ssr/astro.d.ts +2 -17
  46. package/dist/ssr/astro.js +29 -21
  47. package/dist/ssr/astro.js.map +1 -1
  48. package/dist/ssr/index.d.ts +37 -4
  49. package/dist/ssr/index.js +5 -5
  50. package/dist/ssr/index.js.map +1 -1
  51. package/dist/ssr/next.d.ts +2 -3
  52. package/dist/ssr/next.js +7 -8
  53. package/dist/ssr/next.js.map +1 -1
  54. package/dist/static/index.d.ts +91 -5
  55. package/dist/static/index.js +49 -3
  56. package/dist/static/index.js.map +1 -0
  57. package/dist/static/inject.js.map +1 -1
  58. package/dist/zero/babel.d.ts +2 -81
  59. package/dist/zero/babel.js +45 -29
  60. package/dist/zero/babel.js.map +1 -1
  61. package/dist/zero/index.d.ts +81 -3
  62. package/dist/zero/index.js +2 -3
  63. package/dist/zero/next.d.ts +1 -1
  64. package/dist/zero/next.js +1 -1
  65. package/dist/zero/next.js.map +1 -1
  66. package/docs/README.md +18 -10
  67. package/docs/adoption.md +56 -31
  68. package/docs/ai-agents.md +224 -0
  69. package/docs/comparison.md +69 -40
  70. package/docs/configuration.md +501 -72
  71. package/docs/debug.md +22 -18
  72. package/docs/design-system.md +43 -24
  73. package/docs/dsl.md +512 -179
  74. package/docs/getting-started.md +60 -39
  75. package/docs/injector.md +110 -32
  76. package/docs/methodology.md +68 -36
  77. package/docs/migration-v3.md +285 -0
  78. package/docs/pipeline.md +741 -0
  79. package/docs/plugins.md +353 -0
  80. package/docs/react-api.md +164 -40
  81. package/docs/runtime-benchmarks.md +216 -0
  82. package/docs/ssr.md +92 -81
  83. package/docs/styles.md +65 -16
  84. package/docs/tasty-static.md +149 -99
  85. package/package.json +49 -21
  86. package/tasty.config.ts +6 -0
  87. package/dist/_virtual/_rolldown/runtime.js +0 -7
  88. package/dist/chunks/cacheKey.d.ts +0 -1
  89. package/dist/chunks/cacheKey.js +0 -77
  90. package/dist/chunks/cacheKey.js.map +0 -1
  91. package/dist/chunks/definitions.d.ts +0 -37
  92. package/dist/chunks/definitions.js +0 -258
  93. package/dist/chunks/definitions.js.map +0 -1
  94. package/dist/chunks/index.d.ts +0 -1
  95. package/dist/chunks/renderChunk.d.ts +0 -1
  96. package/dist/chunks/renderChunk.js +0 -59
  97. package/dist/chunks/renderChunk.js.map +0 -1
  98. package/dist/compute-styles.d.ts +0 -31
  99. package/dist/compute-styles.js +0 -335
  100. package/dist/compute-styles.js.map +0 -1
  101. package/dist/config.d.ts +0 -407
  102. package/dist/config.js +0 -587
  103. package/dist/config.js.map +0 -1
  104. package/dist/counter-style/index.js +0 -51
  105. package/dist/counter-style/index.js.map +0 -1
  106. package/dist/debug.d.ts +0 -89
  107. package/dist/debug.js +0 -453
  108. package/dist/debug.js.map +0 -1
  109. package/dist/font-face/index.js +0 -63
  110. package/dist/font-face/index.js.map +0 -1
  111. package/dist/hooks/index.d.ts +0 -7
  112. package/dist/hooks/useCounterStyle.d.ts +0 -36
  113. package/dist/hooks/useCounterStyle.js +0 -65
  114. package/dist/hooks/useCounterStyle.js.map +0 -1
  115. package/dist/hooks/useFontFace.d.ts +0 -45
  116. package/dist/hooks/useFontFace.js +0 -66
  117. package/dist/hooks/useFontFace.js.map +0 -1
  118. package/dist/hooks/useGlobalStyles.d.ts +0 -46
  119. package/dist/hooks/useGlobalStyles.js +0 -88
  120. package/dist/hooks/useGlobalStyles.js.map +0 -1
  121. package/dist/hooks/useKeyframes.d.ts +0 -58
  122. package/dist/hooks/useKeyframes.js +0 -55
  123. package/dist/hooks/useKeyframes.js.map +0 -1
  124. package/dist/hooks/useProperty.d.ts +0 -81
  125. package/dist/hooks/useProperty.js +0 -96
  126. package/dist/hooks/useProperty.js.map +0 -1
  127. package/dist/hooks/useRawCSS.d.ts +0 -22
  128. package/dist/hooks/useRawCSS.js +0 -103
  129. package/dist/hooks/useRawCSS.js.map +0 -1
  130. package/dist/hooks/useStyles.d.ts +0 -40
  131. package/dist/hooks/useStyles.js +0 -31
  132. package/dist/hooks/useStyles.js.map +0 -1
  133. package/dist/injector/index.d.ts +0 -182
  134. package/dist/injector/index.js +0 -185
  135. package/dist/injector/index.js.map +0 -1
  136. package/dist/injector/injector.d.ts +0 -198
  137. package/dist/injector/injector.js +0 -651
  138. package/dist/injector/injector.js.map +0 -1
  139. package/dist/injector/sheet-manager.d.ts +0 -132
  140. package/dist/injector/sheet-manager.js +0 -699
  141. package/dist/injector/sheet-manager.js.map +0 -1
  142. package/dist/injector/types.d.ts +0 -235
  143. package/dist/keyframes/index.js +0 -206
  144. package/dist/keyframes/index.js.map +0 -1
  145. package/dist/parser/classify.js +0 -319
  146. package/dist/parser/classify.js.map +0 -1
  147. package/dist/parser/const.js +0 -60
  148. package/dist/parser/const.js.map +0 -1
  149. package/dist/parser/lru.js +0 -109
  150. package/dist/parser/lru.js.map +0 -1
  151. package/dist/parser/parser.d.ts +0 -25
  152. package/dist/parser/parser.js +0 -115
  153. package/dist/parser/parser.js.map +0 -1
  154. package/dist/parser/tokenizer.js +0 -69
  155. package/dist/parser/tokenizer.js.map +0 -1
  156. package/dist/parser/types.d.ts +0 -51
  157. package/dist/parser/types.js +0 -46
  158. package/dist/parser/types.js.map +0 -1
  159. package/dist/pipeline/conditions.d.ts +0 -134
  160. package/dist/pipeline/conditions.js +0 -406
  161. package/dist/pipeline/conditions.js.map +0 -1
  162. package/dist/pipeline/exclusive.js +0 -230
  163. package/dist/pipeline/exclusive.js.map +0 -1
  164. package/dist/pipeline/index.d.ts +0 -55
  165. package/dist/pipeline/index.js +0 -708
  166. package/dist/pipeline/index.js.map +0 -1
  167. package/dist/pipeline/materialize.js +0 -1103
  168. package/dist/pipeline/materialize.js.map +0 -1
  169. package/dist/pipeline/parseStateKey.d.ts +0 -15
  170. package/dist/pipeline/parseStateKey.js +0 -446
  171. package/dist/pipeline/parseStateKey.js.map +0 -1
  172. package/dist/pipeline/simplify.js +0 -515
  173. package/dist/pipeline/simplify.js.map +0 -1
  174. package/dist/pipeline/warnings.js +0 -18
  175. package/dist/pipeline/warnings.js.map +0 -1
  176. package/dist/plugins/index.d.ts +0 -2
  177. package/dist/plugins/okhsl-plugin.d.ts +0 -35
  178. package/dist/plugins/okhsl-plugin.js +0 -97
  179. package/dist/plugins/okhsl-plugin.js.map +0 -1
  180. package/dist/plugins/types.d.ts +0 -87
  181. package/dist/properties/index.js +0 -222
  182. package/dist/properties/index.js.map +0 -1
  183. package/dist/properties/property-type-resolver.d.ts +0 -24
  184. package/dist/properties/property-type-resolver.js +0 -90
  185. package/dist/properties/property-type-resolver.js.map +0 -1
  186. package/dist/rsc-cache.js +0 -79
  187. package/dist/rsc-cache.js.map +0 -1
  188. package/dist/ssr/async-storage.d.ts +0 -17
  189. package/dist/ssr/async-storage.js.map +0 -1
  190. package/dist/ssr/collect-auto-properties.js +0 -58
  191. package/dist/ssr/collect-auto-properties.js.map +0 -1
  192. package/dist/ssr/collector.js.map +0 -1
  193. package/dist/ssr/context.js.map +0 -1
  194. package/dist/ssr/format-global-rules.js.map +0 -1
  195. package/dist/ssr/format-keyframes.js +0 -69
  196. package/dist/ssr/format-keyframes.js.map +0 -1
  197. package/dist/ssr/format-property.js +0 -49
  198. package/dist/ssr/format-property.js.map +0 -1
  199. package/dist/ssr/format-rules.js.map +0 -1
  200. package/dist/ssr/hydrate.d.ts +0 -29
  201. package/dist/ssr/hydrate.js.map +0 -1
  202. package/dist/ssr/ssr-collector-ref.js +0 -29
  203. package/dist/ssr/ssr-collector-ref.js.map +0 -1
  204. package/dist/states/index.d.ts +0 -49
  205. package/dist/states/index.js +0 -170
  206. package/dist/states/index.js.map +0 -1
  207. package/dist/static/tastyStatic.d.ts +0 -46
  208. package/dist/static/tastyStatic.js +0 -30
  209. package/dist/static/tastyStatic.js.map +0 -1
  210. package/dist/static/types.d.ts +0 -49
  211. package/dist/static/types.js +0 -24
  212. package/dist/static/types.js.map +0 -1
  213. package/dist/styles/border.d.ts +0 -25
  214. package/dist/styles/border.js +0 -120
  215. package/dist/styles/border.js.map +0 -1
  216. package/dist/styles/color.d.ts +0 -14
  217. package/dist/styles/color.js +0 -26
  218. package/dist/styles/color.js.map +0 -1
  219. package/dist/styles/const.js +0 -17
  220. package/dist/styles/const.js.map +0 -1
  221. package/dist/styles/createStyle.js +0 -79
  222. package/dist/styles/createStyle.js.map +0 -1
  223. package/dist/styles/dimension.js +0 -109
  224. package/dist/styles/dimension.js.map +0 -1
  225. package/dist/styles/directional.js +0 -133
  226. package/dist/styles/directional.js.map +0 -1
  227. package/dist/styles/display.d.ts +0 -30
  228. package/dist/styles/display.js +0 -73
  229. package/dist/styles/display.js.map +0 -1
  230. package/dist/styles/fade.d.ts +0 -15
  231. package/dist/styles/fade.js +0 -62
  232. package/dist/styles/fade.js.map +0 -1
  233. package/dist/styles/fill.d.ts +0 -42
  234. package/dist/styles/fill.js +0 -51
  235. package/dist/styles/fill.js.map +0 -1
  236. package/dist/styles/flow.d.ts +0 -16
  237. package/dist/styles/flow.js +0 -12
  238. package/dist/styles/flow.js.map +0 -1
  239. package/dist/styles/gap.d.ts +0 -31
  240. package/dist/styles/gap.js +0 -38
  241. package/dist/styles/gap.js.map +0 -1
  242. package/dist/styles/height.d.ts +0 -17
  243. package/dist/styles/height.js +0 -19
  244. package/dist/styles/height.js.map +0 -1
  245. package/dist/styles/index.d.ts +0 -1
  246. package/dist/styles/index.js +0 -8
  247. package/dist/styles/index.js.map +0 -1
  248. package/dist/styles/inset.d.ts +0 -24
  249. package/dist/styles/inset.js +0 -34
  250. package/dist/styles/inset.js.map +0 -1
  251. package/dist/styles/list.d.ts +0 -16
  252. package/dist/styles/list.js +0 -100
  253. package/dist/styles/list.js.map +0 -1
  254. package/dist/styles/margin.d.ts +0 -24
  255. package/dist/styles/margin.js +0 -32
  256. package/dist/styles/margin.js.map +0 -1
  257. package/dist/styles/outline.d.ts +0 -29
  258. package/dist/styles/outline.js +0 -55
  259. package/dist/styles/outline.js.map +0 -1
  260. package/dist/styles/padding.d.ts +0 -24
  261. package/dist/styles/padding.js +0 -32
  262. package/dist/styles/padding.js.map +0 -1
  263. package/dist/styles/placement.d.ts +0 -37
  264. package/dist/styles/placement.js +0 -74
  265. package/dist/styles/placement.js.map +0 -1
  266. package/dist/styles/predefined.d.ts +0 -71
  267. package/dist/styles/predefined.js +0 -237
  268. package/dist/styles/predefined.js.map +0 -1
  269. package/dist/styles/preset.d.ts +0 -52
  270. package/dist/styles/preset.js +0 -127
  271. package/dist/styles/preset.js.map +0 -1
  272. package/dist/styles/radius.d.ts +0 -12
  273. package/dist/styles/radius.js +0 -83
  274. package/dist/styles/radius.js.map +0 -1
  275. package/dist/styles/scrollMargin.d.ts +0 -24
  276. package/dist/styles/scrollMargin.js +0 -32
  277. package/dist/styles/scrollMargin.js.map +0 -1
  278. package/dist/styles/scrollbar.d.ts +0 -25
  279. package/dist/styles/scrollbar.js +0 -51
  280. package/dist/styles/scrollbar.js.map +0 -1
  281. package/dist/styles/shadow.d.ts +0 -14
  282. package/dist/styles/shadow.js +0 -25
  283. package/dist/styles/shadow.js.map +0 -1
  284. package/dist/styles/shared.js +0 -17
  285. package/dist/styles/shared.js.map +0 -1
  286. package/dist/styles/transition.d.ts +0 -14
  287. package/dist/styles/transition.js +0 -159
  288. package/dist/styles/transition.js.map +0 -1
  289. package/dist/styles/types.d.ts +0 -564
  290. package/dist/styles/width.d.ts +0 -17
  291. package/dist/styles/width.js +0 -19
  292. package/dist/styles/width.js.map +0 -1
  293. package/dist/tasty.d.ts +0 -134
  294. package/dist/tasty.js +0 -248
  295. package/dist/tasty.js.map +0 -1
  296. package/dist/types.d.ts +0 -184
  297. package/dist/utils/cache-wrapper.js +0 -21
  298. package/dist/utils/cache-wrapper.js.map +0 -1
  299. package/dist/utils/case-converter.js +0 -8
  300. package/dist/utils/case-converter.js.map +0 -1
  301. package/dist/utils/color-math.d.ts +0 -46
  302. package/dist/utils/color-math.js +0 -749
  303. package/dist/utils/color-math.js.map +0 -1
  304. package/dist/utils/color-space.d.ts +0 -5
  305. package/dist/utils/color-space.js +0 -228
  306. package/dist/utils/color-space.js.map +0 -1
  307. package/dist/utils/colors.d.ts +0 -5
  308. package/dist/utils/colors.js +0 -10
  309. package/dist/utils/colors.js.map +0 -1
  310. package/dist/utils/css-types.d.ts +0 -7
  311. package/dist/utils/deps-equal.js +0 -15
  312. package/dist/utils/deps-equal.js.map +0 -1
  313. package/dist/utils/dotize.d.ts +0 -26
  314. package/dist/utils/dotize.js +0 -122
  315. package/dist/utils/dotize.js.map +0 -1
  316. package/dist/utils/filter-base-props.d.ts +0 -15
  317. package/dist/utils/filter-base-props.js +0 -45
  318. package/dist/utils/filter-base-props.js.map +0 -1
  319. package/dist/utils/get-display-name.d.ts +0 -7
  320. package/dist/utils/get-display-name.js +0 -10
  321. package/dist/utils/get-display-name.js.map +0 -1
  322. package/dist/utils/has-keys.js +0 -13
  323. package/dist/utils/has-keys.js.map +0 -1
  324. package/dist/utils/hash.js +0 -14
  325. package/dist/utils/hash.js.map +0 -1
  326. package/dist/utils/is-dev-env.js +0 -19
  327. package/dist/utils/is-dev-env.js.map +0 -1
  328. package/dist/utils/is-valid-element-type.js +0 -15
  329. package/dist/utils/is-valid-element-type.js.map +0 -1
  330. package/dist/utils/merge-styles.js.map +0 -1
  331. package/dist/utils/mod-attrs.d.ts +0 -6
  332. package/dist/utils/mod-attrs.js +0 -20
  333. package/dist/utils/mod-attrs.js.map +0 -1
  334. package/dist/utils/process-tokens.d.ts +0 -17
  335. package/dist/utils/process-tokens.js +0 -83
  336. package/dist/utils/process-tokens.js.map +0 -1
  337. package/dist/utils/resolve-recipes.d.ts +0 -17
  338. package/dist/utils/resolve-recipes.js.map +0 -1
  339. package/dist/utils/selector-transform.js +0 -32
  340. package/dist/utils/selector-transform.js.map +0 -1
  341. package/dist/utils/string.js +0 -8
  342. package/dist/utils/string.js.map +0 -1
  343. package/dist/utils/styles.d.ts +0 -99
  344. package/dist/utils/styles.js +0 -220
  345. package/dist/utils/styles.js.map +0 -1
  346. package/dist/utils/typography.d.ts +0 -58
  347. package/dist/utils/typography.js +0 -51
  348. package/dist/utils/typography.js.map +0 -1
  349. package/dist/utils/warnings.d.ts +0 -16
  350. package/dist/utils/warnings.js +0 -16
  351. package/dist/utils/warnings.js.map +0 -1
  352. package/dist/zero/css-writer.d.ts +0 -45
  353. package/dist/zero/css-writer.js +0 -73
  354. package/dist/zero/css-writer.js.map +0 -1
  355. package/dist/zero/extractor.d.ts +0 -24
  356. package/dist/zero/extractor.js.map +0 -1
  357. package/docs/PIPELINE.md +0 -519
package/docs/dsl.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Style DSL Reference
2
2
 
3
- This is the Tasty style language reference — the value syntax, state mappings, tokens, units, extending semantics, and special declarations that apply to both runtime `tasty()` and build-time `tastyStatic()`.
3
+ This is the language behind Tasty’s predictable state resolution. Every property can use a state map; later branches declare higher priority, and Tasty compiles them so only one generated selector can match.
4
4
 
5
- For the runtime React API (`tasty()`, hooks, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
5
+ The same value syntax, state maps, tokens, units, extension semantics, and special declarations apply to React `tasty()`—whether rendered on the client or server—and build-time `tastyStatic()`.
6
+
7
+ For the React API (`tasty()`, style functions, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
6
8
 
7
9
  ---
8
10
 
@@ -18,17 +20,28 @@ fill: { '': '#white', hovered: '#gray.05', 'theme=danger': '#red' }
18
20
 
19
21
  #### State Key Types
20
22
 
21
- | Syntax | Example | Generated CSS |
22
- |--------|---------|---------------|
23
- | Boolean modifier | `hovered` | `[data-hovered]` |
24
- | Value modifier | `theme=danger` | `[data-theme="danger"]` |
25
- | Pseudo-class | `:hover` | `:hover` |
26
- | Class selector | `.active` | `.active` |
27
- | Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]` |
28
- | Combined (AND) | `hovered & .active` | `[data-hovered].active` |
29
- | Combined (OR) | `hovered \| focused` | `[data-hovered], [data-focused]` |
30
- | Negated (NOT) | `!disabled` | `:not([data-disabled])` |
31
- | Exclusive (XOR) | `hovered ^ focused` | `[data-hovered]:not([data-focused]), :not([data-hovered])[data-focused]` |
23
+ | Syntax | Example | Generated CSS |
24
+ | ------------------ | ------------------------ | ------------------------------------------------------------------------ |
25
+ | Boolean modifier | `hovered` | `[data-hovered]` |
26
+ | Value modifier | `theme=danger` | `[data-theme="danger"]` |
27
+ | Pseudo-class | `:hover` | `:hover` |
28
+ | Class selector | `.active` | `.active` |
29
+ | Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]` |
30
+ | Combined (AND) | `hovered & .active` | `[data-hovered].active` |
31
+ | Combined (OR) | `hovered \| focused` | `[data-hovered], [data-focused]` |
32
+ | Negated (NOT) | `!disabled` | `:not([data-disabled])` |
33
+ | Exclusive (XOR) | `hovered ^ focused` | `[data-hovered]:not([data-focused]), :not([data-hovered])[data-focused]` |
34
+
35
+ #### Automatic Native States
36
+
37
+ `disabled` and `checked` are built-in automatic states. On a Tasty component, they track the corresponding native prop or attribute, so these pairs express the same component state:
38
+
39
+ | Concise Tasty state | Explicit attribute selector |
40
+ | ------------------- | --------------------------- |
41
+ | `disabled` | `[disabled]` |
42
+ | `checked` | `[checked]` |
43
+
44
+ Prefer the concise form when authoring Tasty component styles. Explicit attribute selectors remain valid and can be useful when targeting arbitrary attributes or when CSS-shaped syntax makes an introductory example easier to recognize.
32
45
 
33
46
  Operator precedence (highest to lowest): `!` (NOT) > `^` (XOR) > `|` (OR) > `&` (AND). Use parentheses to override: `hovered & (pressed ^ focused)`.
34
47
 
@@ -41,32 +54,52 @@ fill: {
41
54
  }
42
55
  ```
43
56
 
57
+ #### Default State Ordering
58
+
59
+ Key order sets priority — later keys win and turn off earlier ones via negation. The bare default (`''`) is the lowest-priority state, so it **must be the first key**. If it appears after other states, Tasty moves it to the front and emits a dev warning (`MISPLACED_DEFAULT_STATE`); otherwise it would override every state above it.
60
+
61
+ ```jsx
62
+ // Correct — default first
63
+ color: { '': '#text', hovered: '#accent' }
64
+
65
+ // Auto-corrected with a warning — '' moved to the front
66
+ color: { hovered: '#accent', '': '#text' }
67
+ ```
68
+
69
+ The bare `''` default still **receives negation** (it is turned off when a higher-priority state matches). For a value that must always apply as a guaranteed floor — even where a query is _unknown_ — use the [`_` fallback floor](#_--fallback-floor) instead. The two can coexist: `''` is the negated default, `_` is the always-on floor. If a map contains only `_` and `''` (no other states), the `''` default is redundant — Tasty keeps the `_` value and drops `''` with a `REDUNDANT_DEFAULT_STATE` warning.
70
+
44
71
  ### Sub-element
45
72
 
46
73
  Element styled using a capitalized key. Identified by `data-element` attribute:
47
74
 
48
75
  ```jsx
49
- styles: { Title: { preset: 'h3' } }
76
+ styles: {
77
+ Title: {
78
+ preset: 'h3';
79
+ }
80
+ }
50
81
  // Targets: <div data-element="Title">
51
82
  ```
52
83
 
53
84
  #### Selector Affix (`$`)
54
85
 
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 |
86
+ Control how a sub-element selector attaches to the root selector using the `$` property inside the sub-element's styles.
87
+
88
+ Examples below assume the sub-element key is **Cell** (i.e. `[data-element="Cell"]` in CSS):
89
+
90
+ | Pattern | Result | Description |
91
+ | ----------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
92
+ | _(none)_ | `[data-element="Cell"]` | Descendant (default) |
93
+ | `>` | `> [data-element="Cell"]` | Direct child |
94
+ | `>Body>` | `> [data-element="Body"] > [data-element="Cell"]` | Chained elements |
95
+ | `> Cell` | `> [data-element="Cell"]` | Self-name shorthand — when the trailing element name matches the sub-element's own key, it acts as the placeholder (same as `@`); no duplication |
96
+ | `h1` | `h1` | Tag selector (no key injection) |
97
+ | `h1 >` | `h1 > [data-element="Cell"]` | Key is direct child of tag |
98
+ | `h1 *` | `h1 *` | Any descendant of tag |
99
+ | `*` | `*` | All descendants |
100
+ | `&::before` | `::before` | Root pseudo (no key); `&` is required |
101
+ | `@::before` | `[data-element="Cell"]::before` | Pseudo on the sub-element |
102
+ | `>@.active` | `> [data-element="Cell"].active` | Class on the sub-element |
70
103
 
71
104
  Rules for key injection (`[data-element="..."]`):
72
105
 
@@ -95,7 +128,7 @@ const List = tasty({
95
128
  Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
96
129
 
97
130
  ```jsx
98
- fill: '#purple.5' // → var(--purple-color) with 50% opacity
131
+ fill: '#purple.5'; // → oklch(from var(--purple-color) l c h / .5)
99
132
  ```
100
133
 
101
134
  ### Modifier
@@ -107,7 +140,7 @@ mods={{ hovered: true, theme: 'danger' }}
107
140
  // → data-hovered="" data-theme="danger"
108
141
  ```
109
142
 
110
- Modifiers can also be exposed as top-level component props via `modProps` — see [Runtime — Mod Props](react-api.md#mod-props).
143
+ Modifiers can also be exposed as top-level component props via `modProps` — see [React API — Mod Props](react-api.md#mod-props).
111
144
 
112
145
  ---
113
146
 
@@ -117,23 +150,155 @@ Modifiers can also be exposed as top-level component props via `modProps` — se
117
150
  color: '#purple', // Full opacity
118
151
  color: '#purple.5', // 50% opacity
119
152
  color: '#purple.05', // 5% opacity
153
+ color: '#purple.$fade', // Opacity from a custom property
120
154
  fill: '#current', // → currentcolor
121
- fill: '#current.5', // → color-mix(in oklab, currentcolor 50%, transparent)
122
155
  color: '(#primary, #secondary)', // Fallback syntax
123
156
  ```
124
157
 
158
+ The suffix sets the alpha on the token's color with CSS relative color syntax:
159
+
160
+ ```jsx
161
+ fill: '#purple.5';
162
+ // → oklch(from var(--purple-color) l c h / .5)
163
+ ```
164
+
165
+ The channels are copied over and the alpha slot is written, which asks nothing of
166
+ the color beyond _being_ a color. That means the suffix works on every one of
167
+ these:
168
+
169
+ - a token holding a `color-mix()`, a `light-dark()`, or a `color()` in a space
170
+ Tasty cannot convert — none of which have channels to decompose
171
+ - `#current`, which resolves to the color the element inherits
172
+ - a `--name-color` variable declared in your own CSS, with no Tasty token
173
+ definition behind it
174
+
175
+ Two properties follow from writing the alpha slot rather than compositing:
176
+
177
+ - **Alpha is replaced, not multiplied.** A token holding `rgb(255 0 0 / .8)`
178
+ faded to `.5` is alpha `.5`, not `.4`.
179
+ - **The alpha may be a number or a percentage.** `#purple.$fade` emits
180
+ `/ var(--fade)` unchanged, so it works whether `$fade` holds `.5` or `50%` —
181
+ which is what `--*-opacity` properties are registered to accept.
182
+
183
+ ### `#current` is the keyword
184
+
185
+ `#current` emits the `currentcolor` keyword, so it resolves against the element
186
+ that reads it. That is what lets it follow a color an ancestor faded — a ramp
187
+ that expresses its disabled state once, in `color`, fades everything painted from
188
+ `#current` below it with no further declarations.
189
+
190
+ The `color` style also publishes `--current-color` beside every color it sets,
191
+ for anything that needs the inherited color _as a color_ rather than as the
192
+ keyword — hand-authored CSS, or a place the keyword will not do. Read it as
193
+ `$current-color`. It is registered with `initial-value: currentcolor`, so where
194
+ nothing published it, it still resolves against each element's own color:
195
+
196
+ ```jsx
197
+ fill: '$current-color'; // → var(--current-color)
198
+ ```
199
+
200
+ A color that already reads the color it inherits — `#current`, a `#current` fade
201
+ — is not published into it: resolving such a value a second time at a descendant
202
+ would fade it twice.
203
+
204
+ One case needs the wider floor to be considered. A token _defined_ as `#current`
205
+ and then faded gives relative color syntax a `currentcolor` origin, which Safari
206
+ supports only from 18:
207
+
208
+ ```jsx
209
+ { '#ink': '#current', fill: '#ink.5' }
210
+ // --ink-color: currentcolor
211
+ // fill: oklch(from currentcolor l c h / .5) → Chrome, Firefox, Safari 18+
212
+ ```
213
+
214
+ Put the fade in the token instead — `{ '#ink': '#current.5' }` — and it goes
215
+ through `color-mix()`, which works from Safari 16.2 and composes like every other
216
+ `#current` fade.
217
+
218
+ ### `#current` composes instead
219
+
220
+ `#current` is the one exception, and the difference is deliberate. A token _names_
221
+ a color, so fading it sets its alpha. `currentcolor` is the color an element
222
+ **inherits**, which an ancestor may already have faded — `#current.4` means "40%
223
+ of what reaches me":
224
+
225
+ ```jsx
226
+ fill: '#current.4';
227
+ // → color-mix(in oklab, currentcolor 40%, transparent)
228
+ ```
229
+
230
+ So a `#current` fade nested inside another one composes: a label at `#current.4`
231
+ with a fill of `#current.18` under it lands at `.072`. Color ramps built on
232
+ `#current` depend on that — replacing would double the opacity of every nested
233
+ step. Because a `color-mix()` percentage cannot be a `<number>`, an opacity
234
+ custom property used as `#current.$fade` must hold a unitless number; a token
235
+ accepts either form.
236
+
237
+ The space is always `oklch`: it is unbounded, so a wide-gamut color survives a
238
+ round trip that a gamut-limited space would clamp. Nothing configures this —
239
+ see [Color space](configuration.md#color-space).
240
+
241
+ ---
242
+
243
+ ## CSS Color Functions
244
+
245
+ Every CSS color function is recognized as a color, so it lands in the color slot
246
+ of whichever style property you use it in — and tokens inside it are expanded:
247
+
248
+ ```jsx
249
+ fill: 'color-mix(in oklab, #primary 50%, #surface)',
250
+ color: 'light-dark(#dark, #light)',
251
+ color: 'contrast-color(#primary)',
252
+ fill: 'color(display-p3 1 .5 0)',
253
+ border: '1bw solid oklch(from #primary l c h / 50%)', // relative color syntax
254
+ shadow: '0 0 1x color-mix(in oklab, #dark 20%, transparent)',
255
+ ```
256
+
257
+ `light-dark()` is the one exception to "always a color": CSS lets it pick between
258
+ values of any type, so it is treated as a color only when its arguments are
259
+ colors. `padding: 'light-dark(1x, 2x)'` still reaches the padding slot.
260
+
261
+ A color token defined as one of these functions takes the
262
+ [opacity suffix](#color-tokens--opacity) like any other:
263
+
264
+ ```jsx
265
+ const Card = tasty({
266
+ styles: {
267
+ '#brand': 'color-mix(in oklab, #primary 50%, #surface)',
268
+ fill: '#brand.5',
269
+ },
270
+ });
271
+ ```
272
+
273
+ A [replace token](configuration.md#replace-tokens-parse-time-substitution) is
274
+ substituted while parsing, so its color is right there to fade in place. A
275
+ channel function takes the alpha after a slash; a derived function has no alpha
276
+ channel, so it gets wrapped:
277
+
278
+ ```jsx
279
+ configure({
280
+ replaceTokens: {
281
+ '#solid': 'hsl(220 90% 50%)',
282
+ '#adaptive': 'light-dark(#dark, #light)',
283
+ },
284
+ });
285
+
286
+ fill: '#solid.5'; // → hsl(220 90% 50% / .5)
287
+ fill: '#adaptive.5'; // → oklch(from light-dark(…) l c h / .5)
288
+ ```
289
+
125
290
  ---
126
291
 
127
292
  ## Built-in Units
128
293
 
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)` |
294
+ | Unit | Description | Example | CSS Output |
295
+ | ---- | ------------------ | ------- | ------------------------------- |
296
+ | `x` | Gap multiplier | `2x` | `calc(var(--gap) * 2)` |
297
+ | `r` | Border radius | `1r` | `var(--radius)` |
298
+ | `cr` | Card border radius | `1cr` | `var(--card-radius)` |
299
+ | `bw` | Border width | `2bw` | `calc(var(--border-width) * 2)` |
300
+ | `ow` | Outline width | `1ow` | `var(--outline-width)` |
301
+ | `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)` |
137
302
 
138
303
  You can register additional custom units via [`configure()`](configuration.md#options).
139
304
 
@@ -178,7 +343,7 @@ const ElevatedCard = tasty({
178
343
 
179
344
  **Post-merge recipes (`/` separator):**
180
345
 
181
- Recipes listed after `/` are applied *after* component styles using `mergeStyles`:
346
+ Recipes listed after `/` are applied _after_ component styles using `mergeStyles`:
182
347
 
183
348
  ```jsx
184
349
  const Input = tasty({
@@ -216,8 +381,8 @@ When a style property uses a state map, the merge behavior depends on whether th
216
381
  const MyButton = tasty(Button, {
217
382
  styles: {
218
383
  fill: {
219
- 'loading': '#yellow', // append new state
220
- 'disabled': '#gray.20', // override existing state in place
384
+ loading: '#yellow', // append new state
385
+ disabled: '#gray.20', // override existing state in place
221
386
  },
222
387
  },
223
388
  });
@@ -227,7 +392,7 @@ const MyButton = tasty(Button, {
227
392
  styles: {
228
393
  fill: {
229
394
  '': '#red',
230
- 'hovered': '#blue',
395
+ hovered: '#blue',
231
396
  },
232
397
  },
233
398
  });
@@ -252,8 +417,12 @@ fill: {
252
417
  Use `null` inside a state map to remove a state, or `false` to block it entirely (tombstone):
253
418
 
254
419
  ```jsx
255
- fill: { pressed: null } // removes pressed from the result
256
- fill: { disabled: false } // tombstone — no CSS for disabled, blocks recipe too
420
+ fill: {
421
+ pressed: null;
422
+ } // removes pressed from the result
423
+ fill: {
424
+ disabled: false;
425
+ } // tombstone — no CSS for disabled, blocks recipe too
257
426
  ```
258
427
 
259
428
  ### Resetting Properties with `null` and `false`
@@ -261,44 +430,54 @@ fill: { disabled: false } // tombstone — no CSS for disabled, blocks recipe to
261
430
  ```jsx
262
431
  const SimpleButton = tasty(Button, {
263
432
  styles: {
264
- fill: null, // discard parent's fill, let recipe fill in
433
+ fill: null, // discard parent's fill, let recipe fill in
265
434
  border: false, // no border at all (tombstone — blocks recipe too)
266
435
  },
267
436
  });
268
437
  ```
269
438
 
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 |
439
+ | Value | Meaning | Recipe fills in? |
440
+ | ----------- | ------------------------------------ | ---------------- |
441
+ | `undefined` | Not provided — parent preserved | N/A |
442
+ | `null` | Intentional unset — parent discarded | Yes |
443
+ | `false` | Tombstone — blocks everything | No |
275
444
 
276
445
  ---
277
446
 
278
447
  ## Advanced States (`@` prefix)
279
448
 
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)` |
449
+ | Prefix | Purpose | Example |
450
+ | ----------- | ----------------------------------------- | -------------------------- |
451
+ | `@media` | Media queries | `@media(w < 768px)` |
452
+ | `@(...)` | Container queries | `@(panel, w >= 300px)` |
453
+ | `@supports` | Feature/selector support | `@supports(display: grid)` |
454
+ | `@root` | Root element states | `@root(schema=dark)` |
455
+ | `@parent` | Parent/ancestor element states | `@parent(hovered)` |
456
+ | `@own` | Sub-element's own state | `@own(hovered)` |
457
+ | `@starting` | Entry animation | `@starting` |
458
+ | `_` | Fallback floor (always-on, never negated) | `_` |
459
+ | `:is()` | CSS `:is()` structural pseudo-class | `:is(fieldset > label)` |
460
+ | `:has()` | CSS `:has()` relational pseudo-class | `:has(> Icon)` |
461
+ | `:not()` | CSS `:not()` negation (prefer `!:is()`) | `:not(:first-child)` |
462
+ | `:where()` | CSS `:where()` (zero specificity) | `:where(Section)` |
463
+
464
+ > **Specificity.** All state selectors Tasty generates (modifiers, pseudo-classes,
465
+ > `:is()`/`:not()` groups, and `@root` / `@parent` context) are wrapped in
466
+ > `:where(...)` so they carry **zero specificity**. The only specificity anchors
467
+ > are the doubled component class (`.t0.t0`) and sub-element `[data-element]`
468
+ > attributes. This means overlapping rules (e.g. a `_` fallback floor and the
469
+ > states layered over it) resolve purely by **source order** — Tasty emits
470
+ > lower-priority states first and higher-priority states last so the cascade
471
+ > produces the intended winner.
293
472
 
294
473
  ### `@media(...)` — Media Queries
295
474
 
296
475
  Media queries support dimension shorthands and custom unit expansion:
297
476
 
298
477
  | Shorthand | Expands to |
299
- |-----------|------------|
300
- | `w` | `width` |
301
- | `h` | `height` |
478
+ | --------- | ---------- |
479
+ | `w` | `width` |
480
+ | `h` | `height` |
302
481
 
303
482
  ```jsx
304
483
  fill: {
@@ -309,14 +488,14 @@ fill: {
309
488
  }
310
489
  ```
311
490
 
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` |
491
+ | Tasty syntax | CSS output |
492
+ | ------------------------------------ | ------------------------------------- |
493
+ | `@media(w < 768px)` | `@media (width < 768px)` |
494
+ | `@media(600px <= w < 1200px)` | `@media (600px <= width < 1200px)` |
495
+ | `@media:print` | `@media print` |
496
+ | `@media:screen` | `@media screen` |
318
497
  | `@media(prefers-color-scheme: dark)` | `@media (prefers-color-scheme: dark)` |
319
- | `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
498
+ | `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
320
499
 
321
500
  Custom units work inside media queries: `@media(w < 40x)` → `@media (width < calc(var(--gap) * 40))`.
322
501
 
@@ -326,12 +505,12 @@ In practice, define state aliases via `configure({ states })` and use `@mobile`
326
505
 
327
506
  Container queries use the syntax `@(name, condition)` for named containers or `@(condition)` for the nearest ancestor container. Dimension shorthands (`w`, `h`, `is`, `bs`) are expanded the same way as `@media`.
328
507
 
329
- | Shorthand | Expands to |
330
- |-----------|------------|
331
- | `w` | `width` |
332
- | `h` | `height` |
333
- | `is` | `inline-size` |
334
- | `bs` | `block-size` |
508
+ | Shorthand | Expands to |
509
+ | --------- | ------------- |
510
+ | `w` | `width` |
511
+ | `h` | `height` |
512
+ | `is` | `inline-size` |
513
+ | `bs` | `block-size` |
335
514
 
336
515
  ```jsx
337
516
  const Panel = tasty({
@@ -344,14 +523,14 @@ const Panel = tasty({
344
523
  });
345
524
  ```
346
525
 
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)` |
526
+ | Tasty syntax | CSS output |
527
+ | ---------------------------------- | ---------------------------------------------- |
528
+ | `@(layout, w < 600px)` | `@container layout (width < 600px)` |
529
+ | `@(w < 600px)` | `@container (width < 600px)` |
530
+ | `@(layout, $variant=danger)` | `@container layout style(--variant: "danger")` |
531
+ | `@(layout, $compact)` | `@container layout style(--compact)` |
532
+ | `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)` |
533
+ | `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)` |
355
534
 
356
535
  Container style queries use `$prop` (boolean) or `$prop=value` syntax, which maps to CSS `style(--prop)` or `style(--prop: "value")`.
357
536
 
@@ -359,10 +538,10 @@ Container style queries use `$prop` (boolean) or `$prop=value` syntax, which map
359
538
 
360
539
  Feature queries test CSS property support. Use `$` as the first argument to test selector support:
361
540
 
362
- | Tasty syntax | CSS output |
363
- |--------------|------------|
364
- | `@supports(display: grid)` | `@supports (display: grid)` |
365
- | `@supports($, :has(*))` | `@supports selector(:has(*))` |
541
+ | Tasty syntax | CSS output |
542
+ | --------------------------- | --------------------------------- |
543
+ | `@supports(display: grid)` | `@supports (display: grid)` |
544
+ | `@supports($, :has(*))` | `@supports selector(:has(*))` |
366
545
  | `!@supports(display: grid)` | `@supports (not (display: grid))` |
367
546
 
368
547
  ```jsx
@@ -372,6 +551,95 @@ display: {
372
551
  }
373
552
  ```
374
553
 
554
+ ### `_` — Fallback Floor
555
+
556
+ By default Tasty makes states **mutually exclusive**: a higher-priority state
557
+ turns the lower-priority ones off via negation, so exactly one branch applies.
558
+ This relies on `A | !A` always being true. CSS feature/container queries break
559
+ that assumption: `@supports(...)` and `@(...)` queries can be **unknown** (not
560
+ just true/false), and `not(unknown)` is also unknown — so a negated default
561
+ branch silently never applies. The classic case is `scroll-state`: a browser can
562
+ support `container-type: scroll-state` while a specific `scroll-state(...)` query
563
+ is unknown, leaving _no_ branch active.
564
+
565
+ The `_` fallback floor solves this. Use `_` as a **standalone key** and its value
566
+ **always applies** as a guaranteed floor: it never receives negation, and
567
+ higher-priority states simply layer over it via the cascade.
568
+
569
+ ```jsx
570
+ inset: {
571
+ _: '0 top',
572
+ '@supports(container-type: scroll-state) & @(scroll-state(scrolled: block-end))':
573
+ '-80x top',
574
+ }
575
+ ```
576
+
577
+ ```css
578
+ .t0.t0 {
579
+ inset: 0...;
580
+ }
581
+ @container scroll-state(scrolled: block-end) {
582
+ @supports (container-type: scroll-state) {
583
+ .t0.t0 {
584
+ inset: -80px...;
585
+ }
586
+ }
587
+ }
588
+ ```
589
+
590
+ The floor is emitted as a bare rule (no negated `@supports` / container branches),
591
+ so it always applies. When the override matches it wins because it is emitted
592
+ later and all state selectors share the same specificity (see the note on
593
+ `:where()` below).
594
+
595
+ Notes:
596
+
597
+ - `_` is a **standalone key only** — it defines a single map-wide floor and
598
+ cannot be combined with state logic. Keys like `_ & hovered` or `_ | focused`
599
+ are ignored with an `INVALID_FALLBACK_KEY` dev warning.
600
+ - `_` can coexist with the bare `''` default when other states exist: `''` is the
601
+ negated default, `_` is the always-on floor. With only `_` and `''` (no other
602
+ states) the `''` default is redundant — Tasty keeps the `_` value and drops
603
+ `''` with a `REDUNDANT_DEFAULT_STATE` warning.
604
+ - `_` is position-independent: it is always placed at the lowest priority
605
+ regardless of where it appears in the map.
606
+
607
+ #### `_` vs the `''` default
608
+
609
+ In simple maps where every other state is a plain modifier (always cleanly
610
+ on/off), `_` and `''` produce the **same visible result** — the base value shows
611
+ whenever no other state wins. They diverge in three situations, and the root
612
+ cause is always the same: **`''` is mutually exclusive (turned off by negation),
613
+ while `_` is always on (layered underneath).**
614
+
615
+ - **Unknown / invalid branches.** If a higher-priority branch sits behind a query
616
+ that can be _unknown_ (`@supports`, container, `scroll-state`) or uses a
617
+ selector the browser drops, the negated `''` default disappears along with it
618
+ (`not(unknown) = unknown`), leaving the property with no rule. The `_` floor
619
+ survives because it is an unconditional bare rule. This is the case `_` exists
620
+ for.
621
+
622
+ - **Empty / "unset" states.** A state can intentionally produce _no_ output (an
623
+ empty value) to leave a property unset while that state is active. With `''`
624
+ this works — the default is negated away and nothing replaces it, so the
625
+ property unsets. With `_` the floor can never be turned off, so its value keeps
626
+ applying and the "unset on this state" intent is lost.
627
+
628
+ ```jsx
629
+ // '' : color unsets while loading. '_' : color stays #text while loading.
630
+ color: { '': '#text', loading: '' }
631
+ ```
632
+
633
+ - **States with different output shapes.** Because `_` layers additively, when
634
+ different states emit _different sets_ of declarations, the floor's
635
+ declarations bleed through wherever the winning state does not override the
636
+ same property — which can produce inconsistent combinations. A `''` default is
637
+ swapped out cleanly by its mutually-exclusive condition.
638
+
639
+ Rule of thumb: **use `''` for the normal default** (clean mutual exclusivity,
640
+ supports unsetting), and reach for `_` only when a value must survive an
641
+ _unknown_ higher-priority branch.
642
+
375
643
  ### `@root(...)` — Root Element States
376
644
 
377
645
  Root states generate selectors on the `:root` element. They are useful for theme modes, feature flags, and other page-level conditions:
@@ -386,13 +654,13 @@ color: {
386
654
  }
387
655
  ```
388
656
 
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"])` |
657
+ | Tasty syntax | CSS selector |
658
+ | ---------------------- | --------------------------------- |
659
+ | `@root(schema=dark)` | `:root[data-schema="dark"]` |
660
+ | `@root(hovered)` | `:root[data-hovered]` |
661
+ | `@root(.premium-user)` | `:root.premium-user` |
662
+ | `@root([lang="en"])` | `:root[lang="en"]` |
663
+ | `!@root(schema=dark)` | `:root:not([data-schema="dark"])` |
396
664
 
397
665
  Root conditions are prepended to the component selector: `:root[data-schema="dark"] .t0.t0 { ... }`.
398
666
 
@@ -408,7 +676,7 @@ const Nav = tasty({
408
676
  '': '#text',
409
677
  '@own(:hover)': '#primary',
410
678
  '@own(:focus-visible)': '#primary',
411
- 'selected': '#primary', // root-level modifier
679
+ selected: '#primary', // root-level modifier
412
680
  },
413
681
  },
414
682
  },
@@ -416,11 +684,11 @@ const Nav = tasty({
416
684
  });
417
685
  ```
418
686
 
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 |
687
+ | Tasty syntax (inside sub-element) | CSS output |
688
+ | --------------------------------- | ------------------------------------------------- |
689
+ | `@own(:hover)` | `:hover` on the sub-element selector |
690
+ | `@own(hovered)` | `[data-hovered]` on the sub-element selector |
691
+ | `@own(theme=dark)` | `[data-theme="dark"]` on the sub-element selector |
424
692
 
425
693
  `@own()` is only valid inside sub-element styles. Using it on root styles emits a warning and is treated as a regular modifier.
426
694
 
@@ -438,9 +706,9 @@ const FadeIn = tasty({
438
706
  });
439
707
  ```
440
708
 
441
- | Tasty syntax | CSS output |
442
- |--------------|------------|
443
- | `@starting` | `@starting-style { .t0.t0 { ... } }` |
709
+ | Tasty syntax | CSS output |
710
+ | ------------ | ------------------------------------ |
711
+ | `@starting` | `@starting-style { .t0.t0 { ... } }` |
444
712
 
445
713
  ### `@parent(...)` — Parent Element States
446
714
 
@@ -451,22 +719,22 @@ const Highlight = tasty({
451
719
  styles: {
452
720
  fill: {
453
721
  '': '#white',
454
- '@parent(hovered)': '#gray.05', // Any ancestor has [data-hovered]
455
- '@parent(theme=dark, >)': '#dark-02', // Direct parent has [data-theme="dark"]
722
+ '@parent(hovered)': '#gray.05', // Any ancestor has [data-hovered]
723
+ '@parent(theme=dark, >)': '#dark-02', // Direct parent has [data-theme="dark"]
456
724
  },
457
725
  },
458
726
  });
459
727
  ```
460
728
 
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) |
729
+ | Syntax | CSS Output |
730
+ | ------------------------------------- | -------------------------------------------------------------------- |
731
+ | `@parent(hovered)` | `:is([data-hovered] *)` |
732
+ | `!@parent(hovered)` | `:not([data-hovered] *)` |
733
+ | `@parent(hovered, >)` | `:is([data-hovered] > *)` (direct parent) |
734
+ | `@parent(.active)` | `:is(.active *)` |
735
+ | `@parent(hovered & focused)` | `:is([data-hovered][data-focused] *)` (same ancestor) |
468
736
  | `@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) |
737
+ | `@parent(hovered \| focused)` | `:is([data-hovered] *, [data-focused] *)` (OR inside single wrapper) |
470
738
 
471
739
  For sub-elements, the parent check applies to the root element's ancestors:
472
740
 
@@ -497,38 +765,38 @@ const Card = tasty({
497
765
  styles: {
498
766
  display: {
499
767
  '': '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
768
+ ':has(> Icon)': 'grid', // has Icon as direct child
769
+ ':has(+ Icon)': 'grid', // immediately followed by an Icon sibling
770
+ ':has(~ Icon)': 'grid', // has an Icon sibling somewhere after
771
+ ':has(Icon +)': 'grid', // immediately preceded by an Icon sibling (auto-completes to `Icon + *`)
772
+ ':has(Icon ~)': 'grid', // has an Icon sibling somewhere before (auto-completes to `Icon ~ *`)
773
+ ':is(fieldset > label)': 'inline', // is a label inside a fieldset (HTML tags)
774
+ '!:has(> Icon)': 'flex', // negation: no Icon child
507
775
  },
508
776
  },
509
777
  });
510
778
  ```
511
779
 
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`) |
780
+ | Syntax | CSS Output | Meaning |
781
+ | ---------------------- | --------------------------------------- | --------------------------------------- |
782
+ | `:has(> Icon)` | `:has(> [data-element="Icon"])` | Has Icon as direct child |
783
+ | `:has(+ Icon)` | `:has(+ [data-element="Icon"])` | Immediately followed by an Icon sibling |
784
+ | `:has(~ Icon)` | `:has(~ [data-element="Icon"])` | Has an Icon sibling somewhere after |
785
+ | `:has(Icon +)` | `:has([data-element="Icon"] + *)` | Immediately preceded by an Icon sibling |
786
+ | `:has(Icon ~)` | `:has([data-element="Icon"] ~ *)` | Has an Icon sibling somewhere before |
787
+ | `:has(>)` | `:has(> *)` | Has any direct child |
788
+ | `:is(> Field + input)` | `:is(> [data-element="Field"] + input)` | Structural match |
789
+ | `:has(button)` | `:has(button)` | HTML tag (lowercase, unchanged) |
790
+ | `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
791
+ | `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
524
792
 
525
793
  Combine with other states using boolean logic (`&`, `|`, `!`, `^`):
526
794
 
527
795
  ```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
796
+ ':has(> Icon) & hovered'; // AND: structural + data attribute
797
+ '@parent(hovered) & :has(> Icon)'; // AND: parent check + structural
798
+ ':has(> Icon) | :has(> Button)'; // OR: either sub-element present
799
+ ':has(> Icon) ^ :has(> Button)'; // XOR: exactly one present
532
800
  ```
533
801
 
534
802
  > **Nesting limit:** The state key parser supports up to 2 levels of nested parentheses inside `:is()`, `:has()`, `:not()`, and `:where()` — e.g. `:has(Input:not(:disabled))` works, but 3+ levels like `:has(:is(:not(:hover)))` will not be tokenized correctly. This covers virtually all practical use cases.
@@ -563,8 +831,8 @@ CSS cannot transition or animate custom properties unless the browser knows thei
563
831
  const AnimatedGradient = tasty({
564
832
  styles: {
565
833
  '$gradient-angle': '0deg',
566
- '#theme': 'okhsl(280 80% 50%)',
567
- background: 'linear-gradient($gradient-angle, #theme, transparent)',
834
+ '#theme': 'okhst(280 80% 50%)',
835
+ image: 'linear-gradient($gradient-angle, #theme, #clear)',
568
836
  transition: '$$gradient-angle 0.3s, ##theme 0.3s',
569
837
  },
570
838
  });
@@ -572,30 +840,30 @@ const AnimatedGradient = tasty({
572
840
 
573
841
  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
842
 
575
- Use explicit `@properties` when you need non-default settings like `inherits: false`:
843
+ Use explicit `@property` when you need non-default settings like `inherits: false`:
576
844
 
577
845
  ```jsx
578
- '@properties': {
846
+ '@property': {
579
847
  '$gradient-angle': { syntax: '<angle>', inherits: false, initialValue: '0deg' },
580
848
  },
581
849
  ```
582
850
 
583
851
  ---
584
852
 
585
- ## Font Face (`@fontFace`)
853
+ ## Font Face (`@font-face`)
586
854
 
587
855
  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
856
 
589
857
  ```ts
590
858
  const Heading = tasty({
591
859
  styles: {
592
- '@fontFace': {
860
+ '@font-face': {
593
861
  'Brand Sans': {
594
862
  src: 'url("/fonts/brand-sans.woff2") format("woff2")',
595
863
  fontDisplay: 'swap',
596
864
  },
597
865
  },
598
- fontFamily: '"Brand Sans", sans-serif',
866
+ font: '"Brand Sans", sans-serif',
599
867
  },
600
868
  });
601
869
  ```
@@ -605,7 +873,7 @@ const Heading = tasty({
605
873
  Supply an array to register several variants of the same family:
606
874
 
607
875
  ```ts
608
- '@fontFace': {
876
+ '@font-face': {
609
877
  'Brand Sans': [
610
878
  { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
611
879
  { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
@@ -615,26 +883,26 @@ Supply an array to register several variants of the same family:
615
883
 
616
884
  ### Supported descriptors
617
885
 
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` |
886
+ | Descriptor | CSS property | Type |
887
+ | ----------------------- | ------------------------- | --------------------------------------------------------- |
888
+ | `src` (required) | `src` | `string` |
889
+ | `fontWeight` | `font-weight` | `string \| number` |
890
+ | `fontStyle` | `font-style` | `string` |
891
+ | `fontStretch` | `font-stretch` | `string` |
892
+ | `fontDisplay` | `font-display` | `'auto' \| 'block' \| 'swap' \| 'fallback' \| 'optional'` |
893
+ | `unicodeRange` | `unicode-range` | `string` |
894
+ | `ascentOverride` | `ascent-override` | `string` |
895
+ | `descentOverride` | `descent-override` | `string` |
896
+ | `lineGapOverride` | `line-gap-override` | `string` |
897
+ | `sizeAdjust` | `size-adjust` | `string` |
898
+ | `fontFeatureSettings` | `font-feature-settings` | `string` |
899
+ | `fontVariationSettings` | `font-variation-settings` | `string` |
632
900
 
633
901
  > Font-face rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@font-face`.
634
902
 
635
903
  ---
636
904
 
637
- ## Counter Style (`@counterStyle`)
905
+ ## Counter Style (`@counter-style`)
638
906
 
639
907
  Define custom list markers via the CSS `@counter-style` at-rule. Keys are counter-style names, values are descriptor objects.
640
908
 
@@ -642,7 +910,7 @@ Define custom list markers via the CSS `@counter-style` at-rule. Keys are counte
642
910
  const EmojiList = tasty({
643
911
  tag: 'ol',
644
912
  styles: {
645
- '@counterStyle': {
913
+ '@counter-style': {
646
914
  thumbs: {
647
915
  system: 'cyclic',
648
916
  symbols: '"👍"',
@@ -656,23 +924,88 @@ const EmojiList = tasty({
656
924
 
657
925
  ### Supported descriptors
658
926
 
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` |
927
+ | Descriptor | CSS property | Type |
928
+ | ------------------- | ------------------ | ---------------------------------------------------------------------------------------- |
929
+ | `system` (required) | `system` | `'cyclic' \| 'numeric' \| 'alphabetic' \| 'symbolic' \| 'additive' \| 'fixed' \| string` |
930
+ | `symbols` | `symbols` | `string` |
931
+ | `additiveSymbols` | `additive-symbols` | `string` |
932
+ | `prefix` | `prefix` | `string` |
933
+ | `suffix` | `suffix` | `string` |
934
+ | `negative` | `negative` | `string` |
935
+ | `range` | `range` | `string` |
936
+ | `pad` | `pad` | `string` |
937
+ | `fallback` | `fallback` | `string` |
938
+ | `speakAs` | `speak-as` | `string` |
671
939
 
672
940
  > Counter-style rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@counter-style`.
673
941
 
674
942
  ---
675
943
 
944
+ ## Functions (`@function`)
945
+
946
+ Define reusable, parameterized CSS [custom functions](https://developer.mozilla.org/en-US/docs/Web/CSS/@function) via the CSS `@function` at-rule. A custom function takes parameters and returns a single value — like a dynamic custom property.
947
+
948
+ The token convention follows Tasty's `$` vs `$$` distinction:
949
+
950
+ - **`$$name`** — the literal callable name (`--name`). Used for the **definition key** and for **invocation** `$$name(...)`, mirroring the existing `$$gradient-angle` → `--gradient-angle` sugar. The definition key matches the call site.
951
+ - **`$name`** — a custom-property declaration/reference (`var(--name)`). Used for **parameters** and **local variables**, exactly like declaring custom props in a styles object.
952
+ - **Local variables** are declared directly as `$name` keys on the descriptor — the body reads like a mini styles object.
953
+ - `result`, local-variable values, and parameter `default` values flow through the full Tasty DSL, so units (`2x`), color tokens (`#theme`), auto-calc (`(-1 * $value)` → `calc(-1 * var(--value))`), and fallbacks (`($shadow-color, black)` → `var(--shadow-color, black)`) all work.
954
+
955
+ ```ts
956
+ const Box = tasty({
957
+ styles: {
958
+ '@function': {
959
+ // simplest: one bare param + result (auto-calc, no explicit calc())
960
+ $$negative: { args: ['$value'], result: '(-1 * $value)' },
961
+
962
+ // typed param + default + return type + local variable
963
+ $$shadow: {
964
+ args: { '$shadow-color': { syntax: '<color>', default: 'inherit' } },
965
+ returns: '<color>',
966
+ $offset: '2px', // local variable
967
+ result: '$offset $offset ($shadow-color, black)',
968
+ },
969
+ },
970
+ margin: '$$negative(10px) top', // → margin: --negative(10px) 0 0 0
971
+ shadow: '$$shadow(#accent)', // → box-shadow: --shadow(var(--accent-color))
972
+ },
973
+ });
974
+ ```
975
+
976
+ Generated CSS:
977
+
978
+ ```css
979
+ @function --negative(--value) {
980
+ result: calc(-1 * var(--value));
981
+ }
982
+ @function --shadow(--shadow-color <color>: inherit) returns <color> {
983
+ --offset: 2px;
984
+ result: var(--offset) var(--offset) var(--shadow-color, black);
985
+ }
986
+ .t0 {
987
+ margin: --negative(10px) 0 0 0;
988
+ box-shadow: --shadow(var(--accent-color));
989
+ }
990
+ ```
991
+
992
+ ### Descriptor shape
993
+
994
+ | Field | Type | Notes |
995
+ | ------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
996
+ | `result` (required) | `string` | The `result:` value, parsed through the Tasty DSL. |
997
+ | `args` | `string[]` \| `Record<string, FunctionParameter>` | Ordered parameters. Array form lists bare names; object form maps names to type/default. |
998
+ | `returns` | `string` | Optional return type, e.g. `'<color>'`. |
999
+ | `$name` keys | `string \| number` | Any `$name` key declares a local variable `--name` (value parsed). |
1000
+
1001
+ A `FunctionParameter` is one of: `true` (bare param), a string CSS type shorthand (`'<length>'`), or `{ syntax?: string; default?: string | number }`.
1002
+
1003
+ Functions can also be registered with the [`useFunction`](react-api.md#usefunction) hook or globally via [`configure({ functions })`](configuration.md#functions) (the same map also accepts bare-key parse functions).
1004
+
1005
+ > `@function` is permanent — injected once and never cleaned up, like `@counter-style`. It is an experimental CSS feature ([Chrome 139+](https://developer.mozilla.org/en-US/docs/Web/CSS/@function)); unsupported browsers safely ignore the native rule, so use it as progressive enhancement — or enable the [`configure({ polyfills: { functions: true } })`](configuration.md#polyfills) inline polyfill to expand `$$name(...)` calls into plain CSS for full cross-browser support.
1006
+
1007
+ ---
1008
+
676
1009
  ## Style Properties
677
1010
 
678
1011
  For a complete reference of all enhanced style properties — syntax, values, modifiers, and recommendations — see **[Style Properties Reference](styles.md)**.
@@ -685,4 +1018,4 @@ For a complete reference of all enhanced style properties — syntax, values, mo
685
1018
  - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
686
1019
  - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
687
1020
  - **[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
1021
+ - **[Build-Time Extraction (`tastyStatic`)](tasty-static.md)** — Static styling with the Babel plugin