@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/ssr.md CHANGED
@@ -1,16 +1,22 @@
1
1
  # Server-Side Rendering (SSR)
2
2
 
3
- Tasty supports server-side rendering with zero-cost client hydration. This does **not** introduce a separate styling engine: SSR uses the same runtime `tasty()` pipeline you already use on the client, then adds server-side CSS collection and client-side cache hydration. Your existing `tasty()` components work unchanged, and SSR remains opt-in with no per-component modifications. For the broader docs map, see the [Docs Hub](README.md).
3
+ Tasty supports server-side rendering with zero-cost client hydration. This does **not** introduce a separate styling engine: `tasty()` uses the same rendering pipeline on the server and in the browser, while the SSR integrations add server-side CSS collection and client-side cache hydration. Your existing `tasty()` components work unchanged, and SSR remains opt-in with no per-component modifications. For the broader docs map, see the [Docs Hub](README.md).
4
+
5
+ ## Zero-runtime terminology
6
+
7
+ Zero-runtime delivery is an outcome, not an alias for `tastyStatic()`. When `tasty()` components render only on the server, their CSS is delivered with the HTML and no Tasty styling runtime is shipped to the browser. Astro's `tastyIntegration({ islands: false })` is the explicit integration for this setup. Server-only Next.js React Server Components follow the same architecture, although you should verify the generated output for your deployment.
8
+
9
+ `tastyStatic()` reaches the same client-side outcome by extracting CSS during the build instead of during React rendering. Use it when extraction must happen before rendering or when the consumer is not React; see [Build-Time Extraction](tasty-static.md).
4
10
 
5
11
  ---
6
12
 
7
13
  ## Requirements
8
14
 
9
- | Dependency | Version | Required for |
10
- |---|---|---|
11
- | `react` | >= 18 | All SSR entry points (matches the current peer dependency of `@tenphi/tasty`) |
12
- | `next` | >= 13 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
13
- | Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
15
+ | Dependency | Version | Required for |
16
+ | ---------- | ------- | ----------------------------------------------------------------------------------------------- |
17
+ | `react` | >= 18 | All SSR entry points (matches the current peer dependency of `@tenphi/tasty`) |
18
+ | `next` | >= 13 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
19
+ | Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
14
20
 
15
21
  The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies beyond `react`.
16
22
 
@@ -18,18 +24,18 @@ The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies
18
24
 
19
25
  ## How It Works
20
26
 
21
- `tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()` discovers a `ServerStyleCollector` via a registered getter (module-level for Next.js, `globalThis` for Astro/generic frameworks using `AsyncLocalStorage`) and collects CSS into it instead of trying to access the DOM. On the client, CSS is injected synchronously into the DOM during render; the injector's content-based cache makes this idempotent. The collector accumulates all styles, serializes them as `<style>` tags and a cache state script in the HTML. On the client, `hydrateTastyCache()` pre-populates the injector cache so that `computeStyles()` skips the rendering pipeline entirely during hydration.
27
+ `tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()` discovers a `ServerStyleCollector` via a registered getter (module-level for Next.js, `globalThis` for Astro/generic frameworks using `AsyncLocalStorage`) and collects CSS into it instead of trying to access the DOM. On the client, CSS is injected synchronously into the DOM during render; the injector's content-based cache makes this idempotent. The collector accumulates all styles and serializes them as `<style>` tags plus a class-list script in the HTML. On the client, `hydrateTastyClasses()` pre-populates the injector's rules map with the rendered class names so that `computeStyles()` skips the rendering pipeline entirely during hydration.
22
28
 
23
29
  ```
24
30
  Server Client
25
31
  ────── ──────
26
- tasty() renders hydrateTastyCache() pre-populates cache
27
- └─ computeStyles() └─ cacheKey → className map ready
32
+ tasty() renders hydrateTastyClasses() reads window.__TASTY__
33
+ └─ computeStyles() └─ marks rendered class names as already-in-DOM
28
34
  └─ collector.collect()
29
35
  tasty() renders
30
36
  After render: └─ computeStyles()
31
- <style data-tasty-ssr> └─ cache hit → skip pipeline
32
- <script data-tasty-cache> └─ no CSS re-injection
37
+ <style data-tasty-ssr> └─ class name known → skip pipeline
38
+ <script> (pushes to __TASTY__) └─ no CSS re-injection
33
39
  ```
34
40
 
35
41
  ---
@@ -85,8 +91,8 @@ That's it. All `tasty()` components inside the tree automatically get SSR suppor
85
91
  - `TastyRegistry` is a `'use client'` component, but Next.js still server-renders it on initial page load. The `'use client'` boundary is required solely to access `useServerInsertedHTML` — **not** because `tasty()` components need the client.
86
92
  - During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level getter (not `globalThis` — this avoids leaking between Next.js's separate RSC and SSR module graphs). It also wraps children in a React context provider so that hooks inside the SSR tree can discover the collector. All style functions — `tasty()` components, `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `useCounterStyle()` — discover the collector through the module-level getter or context provider.
87
93
  - `TastyRegistry` uses `useServerInsertedHTML` to flush collected CSS into the HTML stream as `<style data-tasty-ssr>` tags. This is fully streaming-compatible — styles are injected alongside each Suspense boundary as it resolves.
88
- - A companion inline `<script>` tag merges the `cacheKey → className` mapping into `window.__TASTY_SSR_CACHE__` for each flush. This streaming-friendly approach accumulates cache entries incrementally as Suspense boundaries resolve.
89
- - When the `@tenphi/tasty/ssr/next` module loads on the client, `hydrateTastyCache()` runs automatically from `window.__TASTY_SSR_CACHE__` and pre-populates the injector cache. During hydration, `computeStyles()` hits the cache and skips the entire pipeline.
94
+ - A companion inline `<script>` tag pushes the rendered class names into `window.__TASTY__` for each flush. This streaming-friendly approach accumulates the class list incrementally as Suspense boundaries resolve.
95
+ - When the `@tenphi/tasty/ssr/next` module loads on the client, `hydrateTastyClasses()` runs automatically from `window.__TASTY__` and pre-populates the injector's rules map. During hydration, `computeStyles()` sees the class name is already registered and skips the entire pipeline.
90
96
 
91
97
  ### Using Tasty in Server Components
92
98
 
@@ -124,11 +130,11 @@ The nonce is automatically applied to all `<style>` and `<script>` tags injected
124
130
 
125
131
  Tasty offers three levels of Astro integration. Choose the one that matches your needs:
126
132
 
127
- | Setup | Config needed | Deduplication | Hooks work | Client JS |
128
- |---|---|---|---|---|
129
- | Zero setup | None | Per render tree | Yes (within each tree) | None |
130
- | `tastyIntegration({ islands: false })` | One line | Cross-tree | Yes | None |
131
- | `tastyIntegration()` | One line | Cross-tree | Yes | Auto-hydration |
133
+ | Setup | Config needed | Deduplication | Hooks work | Client JS |
134
+ | -------------------------------------- | ------------- | --------------- | ---------------------- | -------------- |
135
+ | Zero setup | None | Per render tree | Yes (within each tree) | None |
136
+ | `tastyIntegration({ islands: false })` | One line | Cross-tree | Yes | None |
137
+ | `tastyIntegration()` | One line | Cross-tree | Yes | Auto-hydration |
132
138
 
133
139
  ### Zero setup (static pages)
134
140
 
@@ -188,7 +194,7 @@ This gives you:
188
194
 
189
195
  - A `ServerStyleCollector` per request via `AsyncLocalStorage`, deduplicating CSS across all React trees on the page
190
196
  - A single consolidated `<style data-tasty-ssr>` injected into `</head>`
191
- - A `<script data-tasty-cache>` tag with the `cacheKey -> className` map for client hydration
197
+ - A `<script>` tag pushing the rendered class names into `window.__TASTY__` for client hydration
192
198
  - Auto-injected client hydration script (via `injectScript('before-hydration')`) so islands skip the style pipeline during hydration -- no need to import anything manually in each island component
193
199
 
194
200
  All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work on the server.
@@ -223,7 +229,7 @@ export default defineConfig({
223
229
  });
224
230
  ```
225
231
 
226
- This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No `<script data-tasty-cache>` is emitted.
232
+ This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No class-list `<script>` is emitted.
227
233
 
228
234
  ### Manual middleware (advanced)
229
235
 
@@ -234,10 +240,7 @@ If you need to compose Tasty's middleware with other middleware (e.g., via `sequ
234
240
  import { sequence } from 'astro:middleware';
235
241
  import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
236
242
 
237
- export const onRequest = sequence(
238
- tastyMiddleware(),
239
- myOtherMiddleware,
240
- );
243
+ export const onRequest = sequence(tastyMiddleware(), myOtherMiddleware);
241
244
  ```
242
245
 
243
246
  For island hydration with manual middleware, import the client module in a shared entry point or in each island:
@@ -258,7 +261,7 @@ export const onRequest = tastyMiddleware({ transferCache: false });
258
261
  Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware creates a `ServerStyleCollector` and binds it via `AsyncLocalStorage`. All `computeStyles()` calls within the request discover this collector automatically.
259
262
 
260
263
  - **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>` as a single `<style>` tag. No JavaScript is shipped.
261
- - **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, the hydration script (auto-injected by `tastyIntegration()` or manually via `@tenphi/tasty/ssr/astro-client`) reads the cache state from `<script data-tasty-cache>` and pre-populates the injector. The island's `computeStyles()` calls hit the cache during hydration.
264
+ - **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, the hydration script (auto-injected by `tastyIntegration()` or manually via `@tenphi/tasty/ssr/astro-client`) reads the class list from `window.__TASTY__` and pre-populates the injector's rules map. The island's `computeStyles()` calls see the class names as already registered and skip the pipeline during hydration.
262
265
  - The middleware reads the full response body, then injects the collected CSS into `</head>` before sending the final HTML.
263
266
 
264
267
  ### CSP nonce
@@ -274,31 +277,28 @@ Any React-based framework can integrate using `runWithCollector`, which binds a
274
277
  ```tsx
275
278
  import {
276
279
  ServerStyleCollector,
280
+ createServerStyleCollector,
277
281
  runWithCollector,
278
- hydrateTastyCache,
282
+ hydrateTastyClasses,
279
283
  } from '@tenphi/tasty/ssr';
280
284
  import { renderToString } from 'react-dom/server';
281
285
  import { hydrateRoot } from 'react-dom/client';
282
286
 
283
287
  // ── Server ──────────────────────────────────────────────
284
288
 
285
- const collector = new ServerStyleCollector();
289
+ const collector = createServerStyleCollector();
286
290
 
287
- const html = await runWithCollector(collector, () =>
288
- renderToString(<App />)
289
- );
291
+ const html = await runWithCollector(collector, () => renderToString(<App />));
290
292
 
291
293
  const css = collector.getCSS();
292
- const cacheState = collector.getCacheState();
294
+ const classNames = collector.getRenderedClassNames();
293
295
 
294
296
  // Embed in your HTML template:
295
297
  const fullHtml = `
296
298
  <html>
297
299
  <head>
298
300
  <style data-tasty-ssr>${css}</style>
299
- <script data-tasty-cache type="application/json">
300
- ${JSON.stringify(cacheState)}
301
- </script>
301
+ <script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>
302
302
  </head>
303
303
  <body>
304
304
  <div id="root">${html}</div>
@@ -309,7 +309,7 @@ const fullHtml = `
309
309
  // ── Client ──────────────────────────────────────────────
310
310
 
311
311
  // Before hydration:
312
- hydrateTastyCache(); // reads from <script data-tasty-cache>
312
+ hydrateTastyClasses(); // reads from window.__TASTY__
313
313
 
314
314
  hydrateRoot(document.getElementById('root'), <App />);
315
315
  ```
@@ -319,7 +319,7 @@ hydrateRoot(document.getElementById('root'), <App />);
319
319
  For streaming with `renderToPipeableStream`, use `flushCSS()` instead of `getCSS()`:
320
320
 
321
321
  ```tsx
322
- const collector = new ServerStyleCollector();
322
+ const collector = createServerStyleCollector();
323
323
 
324
324
  const stream = await runWithCollector(collector, () =>
325
325
  renderToPipeableStream(<App />, {
@@ -330,14 +330,16 @@ const stream = await runWithCollector(collector, () =>
330
330
  stream.pipe(res);
331
331
  },
332
332
  onAllReady() {
333
- // Flush any remaining styles + cache state
333
+ // Flush any remaining styles + class list
334
334
  const css = collector.flushCSS();
335
335
  if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
336
336
 
337
- const state = collector.getCacheState();
338
- res.write(`<script data-tasty-cache type="application/json">${JSON.stringify(state)}</script>`);
337
+ const classNames = collector.getRenderedClassNames();
338
+ res.write(
339
+ `<script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>`,
340
+ );
339
341
  },
340
- })
342
+ }),
341
343
  );
342
344
  ```
343
345
 
@@ -347,62 +349,65 @@ const stream = await runWithCollector(collector, () =>
347
349
 
348
350
  ### Entry points
349
351
 
350
- | Import path | Description |
351
- |---|---|
352
- | `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `runWithCollector`, `hydrateTastyCache` |
353
- | `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
354
- | `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
355
- | `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
352
+ | Import path | Description |
353
+ | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
354
+ | `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `createServerStyleCollector`, `runWithCollector`, `hydrateTastyClasses` |
355
+ | `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
356
+ | `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
357
+ | `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
358
+ | `@tenphi/tasty/ssr/astro-middleware`<br>`@tenphi/tasty/ssr/astro-middleware-static` | Astro: the middleware entrypoints `tastyIntegration()` registers via `addMiddleware()`. Exported so Astro can resolve them by specifier; you should not import them. For manual setups use `tastyMiddleware()`. |
356
359
 
357
360
  ### `ServerStyleCollector`
358
361
 
359
362
  Server-safe style collector. One instance per request.
360
363
 
361
- | Method | Description |
362
- |---|---|
363
- | `allocateClassName(cacheKey)` | Allocate a sequential class name (`t0`, `t1`, ...) for a cache key. Returns `{ className, isNewAllocation }`. |
364
- | `collectChunk(cacheKey, className, rules)` | Record CSS rules for a chunk. Deduplicated by `cacheKey`. |
365
- | `collectKeyframes(name, css)` | Record a `@keyframes` rule. Deduplicated by name. |
366
- | `allocateKeyframeName(providedName?)` | Allocate a keyframe name. Returns `providedName` if given, otherwise generates one (`k0`, `k1`, ...). |
367
- | `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
368
- | `collectFontFace(key, css)` | Record a `@font-face` rule. Deduplicated by content hash. |
369
- | `collectCounterStyle(name, css)` | Record a `@counter-style` rule. Deduplicated by name. |
370
- | `allocateCounterStyleName(providedName?)` | Allocate a counter-style name. Returns `providedName` if given, otherwise generates one (`cs0`, `cs1`, ...). |
371
- | `collectGlobalStyles(key, css)` | Record global styles (from `useGlobalStyles`). Deduplicated by key. |
372
- | `collectRawCSS(key, css)` | Record raw CSS text (from `useRawCSS`). Deduplicated by key. |
373
- | `collectInternals()` | Collect internal `@property` rules, `:root` token defaults, `@font-face`, and `@counter-style` rules from the global config. Called automatically on first chunk collection; idempotent. |
374
- | `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
375
- | `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
376
- | `getCacheState()` | Serialize `{ entries: Record<cacheKey, className>, classCounter }` for client hydration. |
364
+ Constructor: `new ServerStyleCollector(namePrefix?)`, or use the `createServerStyleCollector(namePrefix?)` factory. The optional `namePrefix` overrides the value from `configure({ namePrefix })`; in normal usage you pass nothing and let the global config drive it. See [Configuration: Name prefix](configuration.md#name-prefix).
365
+
366
+ | Method | Description |
367
+ | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
368
+ | `allocateClassName(cacheKey)` | Allocate a deterministic, content-hashed class name for a cache key (e.g. `t1a2b3` with the default prefix). The same `cacheKey` always produces the same class name on server and client when both share the same `namePrefix`. Returns `{ className, isNewAllocation }`. |
369
+ | `collectChunk(cacheKey, className, rules)` | Record CSS rules for a chunk. Deduplicated by `cacheKey`. |
370
+ | `collectKeyframes(name, css)` | Record a `@keyframes` rule. Deduplicated by name. |
371
+ | `allocateKeyframeName(providedName?)` | Allocate a keyframe name. Returns `providedName` if given, otherwise generates one using `${namePrefix}k${counter}` (e.g. `tk0`, `tk1`, ...). |
372
+ | `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
373
+ | `collectFontFace(key, css)` | Record a `@font-face` rule. Deduplicated by content hash. |
374
+ | `collectCounterStyle(name, css)` | Record a `@counter-style` rule. Deduplicated by name. |
375
+ | `allocateCounterStyleName(providedName?)` | Allocate a counter-style name. Returns `providedName` if given, otherwise generates one using `${namePrefix}c${counter}` (e.g. `tc0`, `tc1`, ...). |
376
+ | `collectGlobalStyles(key, css)` | Record global styles (from `useGlobalStyles`). Deduplicated by key. |
377
+ | `collectRawCSS(key, css)` | Record raw CSS text (from `useRawCSS`). Deduplicated by key. |
378
+ | `collectInternals()` | Collect internal `@property` rules, `:root` token defaults, `@font-face`, and `@counter-style` rules from the global config. Called automatically on first chunk collection; idempotent. |
379
+ | `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
380
+ | `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
381
+ | `getRenderedClassNames()` | Get the list of class names rendered so far. Serialized to `window.__TASTY__` for client hydration via `hydrateTastyClasses()`. |
377
382
 
378
383
  ### `TastyRegistry`
379
384
 
380
385
  Next.js App Router component. Props:
381
386
 
382
- | Prop | Type | Default | Description |
383
- |---|---|---|---|
384
- | `children` | `ReactNode` | required | Application tree |
385
- | `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
387
+ | Prop | Type | Default | Description |
388
+ | --------------- | ----------- | -------- | ------------------------------------------------ |
389
+ | `children` | `ReactNode` | required | Application tree |
390
+ | `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
386
391
 
387
392
  ### `tastyIntegration(options?)`
388
393
 
389
394
  Astro integration factory. Registers middleware and optionally injects client hydration.
390
395
 
391
- | Option | Type | Default | Description |
392
- |---|---|---|---|
393
- | `islands` | `boolean` | `true` | When `true`, injects client hydration script and enables `transferCache`. When `false`, no client JS is shipped. |
396
+ | Option | Type | Default | Description |
397
+ | --------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
398
+ | `islands` | `boolean` | `true` | When `true`, injects client hydration script and enables `transferCache`. When `false`, no client JS is shipped. |
394
399
 
395
400
  ### `tastyMiddleware(options?)`
396
401
 
397
402
  Astro middleware factory. Use for manual middleware composition.
398
403
 
399
- | Option | Type | Default | Description |
400
- |---|---|---|---|
401
- | `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
404
+ | Option | Type | Default | Description |
405
+ | --------------- | --------- | ------- | --------------------------------------------- |
406
+ | `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
402
407
 
403
- ### `hydrateTastyCache(state?)`
408
+ ### `hydrateTastyClasses(classes?)`
404
409
 
405
- Pre-populate the client injector cache. When called without arguments, reads from `window.__TASTY_SSR_CACHE__` (streaming) or `<script data-tasty-cache>` (non-streaming).
410
+ Pre-populate the client injector's rules map with class names rendered on the server, marking them as already present in the DOM so `computeStyles()` skips re-injection during hydration. When called without arguments, reads the class list from `window.__TASTY__` (populated by the streaming `<script>` tags emitted during SSR).
406
411
 
407
412
  ### `runWithCollector(collector, fn)`
408
413
 
@@ -418,21 +423,27 @@ The `TastyRegistry` or `tastyIntegration` is missing. Ensure your layout wraps t
418
423
 
419
424
  ### Hydration mismatch warnings
420
425
 
421
- Class names are deterministic for the same render order. If you see mismatches, ensure `hydrateTastyCache()` runs before React hydration. For Next.js, this is automatic. For Astro with `tastyIntegration()`, this is also automatic. For manual Astro middleware setups, import `@tenphi/tasty/ssr/astro-client` in your island components. For custom setups, call `hydrateTastyCache()` before `hydrateRoot()`.
426
+ Class names are deterministic for the same render order. If you see mismatches, ensure `hydrateTastyClasses()` runs before React hydration. For Next.js, this is automatic. For Astro with `tastyIntegration()`, this is also automatic. For manual Astro middleware setups, import `@tenphi/tasty/ssr/astro-client` in your island components. For custom setups, call `hydrateTastyClasses()` before `hydrateRoot()`.
427
+
428
+ Class names are also derived from the _resolved_ styles, so the server and the client must configure Tasty identically. Anything that changes what a component's styles resolve to will produce a mismatch if it is registered on only one side — `namePrefix`, `recipes`, `handlers`, and the `propHandlers` / `baseStyleProps` extension points described in [Plugins](plugins.md). Call the same `configure()` on both; global CSS is deduplicated automatically, so no `typeof window` guard is needed.
422
429
 
423
430
  ### Styles duplicated after hydration
424
431
 
425
- This is expected and harmless. SSR `<style data-tasty-ssr>` tags remain in the DOM. The client injector creates separate `<style>` elements for any new styles. SSR styles are never modified or removed by the client. If this is a concern for very large apps, call `cleanupSSRStyles()` after hydration:
432
+ **Global CSS** (`:root` tokens, `@property`, `globalStyles`, `@font-face`, `@counter-style`) configured via `configure()` is automatically deduplicated. When Tasty detects `<style data-tasty-ssr>` in the document, it skips client-side injection of globals that were already rendered by the SSR collector. This means `configure()` can be called with the full config on both server and client — no `typeof window === 'undefined'` guard is needed.
433
+
434
+ **Component CSS**: SSR `<style data-tasty-ssr>` tags remain in the DOM. The client injector creates separate `<style>` elements for any new styles. SSR styles are never modified or removed by the client. If this is a concern for very large apps, you can remove the SSR style tags and hydration scripts manually after hydration:
426
435
 
427
436
  ```tsx
428
- import { hydrateTastyCache } from '@tenphi/tasty/ssr';
437
+ import { hydrateTastyClasses } from '@tenphi/tasty/ssr';
429
438
 
430
- hydrateTastyCache();
439
+ hydrateTastyClasses();
431
440
  hydrateRoot(root, <App />);
432
441
 
433
- // Optional: remove SSR style tags after hydration
434
- document.querySelectorAll('style[data-tasty-ssr]').forEach(el => el.remove());
435
- document.querySelectorAll('script[data-tasty-cache]').forEach(el => el.remove());
442
+ // Optional: remove SSR style tags and class-list scripts after hydration
443
+ document.querySelectorAll('style[data-tasty-ssr]').forEach((el) => el.remove());
444
+ document.querySelectorAll('script').forEach((el) => {
445
+ if (el.textContent?.includes('__TASTY__')) el.remove();
446
+ });
436
447
  ```
437
448
 
438
449
  ### `AsyncLocalStorage` not available
package/docs/styles.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Style Properties Reference
2
2
 
3
- All standard CSS properties are supported in Tasty and benefit from its syntax sugar: design tokens (`$name`, `#name`), custom units (`2x`, `1r`, `1bw`), color opacity (`#purple.5`), auto-calc (`(100% - 2x)`), and custom functions. Values are parsed through the Tasty engine automatically.
3
+ All standard CSS properties are supported in Tasty and benefit from its syntax sugar: design tokens (`$name`, `#name`), custom units (`2x`, `1r`, `1bw`), color opacity (`#purple.5`), auto-calc (`(100% - 2x)`), and [custom functions](dsl.md#functions-function) (`$$negative(10px)`). Values are parsed through the Tasty engine automatically.
4
4
 
5
5
  The properties documented below have **custom handlers** with enhanced syntax. They should be **preferred** over their raw CSS equivalents because they:
6
6
 
@@ -100,18 +100,23 @@ For flex/grid layouts, outputs native `gap`. For block layouts, emulates gap usi
100
100
 
101
101
  Element padding with directional modifiers and multi-group support. Use **comma-separated groups** to set a base value and then override specific directions.
102
102
 
103
- **Syntax:** `[value]` | `[top right]` | `[top right bottom left]` | `[value directions...]` — comma-separated for multiple groups
103
+ **Syntax:** `[value]` | `[block inline]` | `[top right bottom left]` | `[value direction...]` — comma-separated for multiple groups
104
104
 
105
105
  **Direction modifiers:** `top`, `right`, `bottom`, `left`
106
106
 
107
107
  **Output modifier:** `longhand` — forces output as individual CSS longhand properties (`padding-top`, `padding-right`, `padding-bottom`, `padding-left`) instead of the `padding` shorthand. Useful when children need to selectively inherit individual directions.
108
108
 
109
+ **One value per directional group.** A group that names direction modifiers takes **exactly one** value, applied to every direction it names. For different values per side, use comma-separated groups: `padding: '2x top, 4x right'`. Writing `padding: '2x 4x top right'` ignores `4x` and warns in development — the parser keeps values and modifiers in separate buckets, so their interleaving in the source is not recoverable and `'2x 4x top right'`, `'2x top 4x right'` and `'top 2x right 4x'` are all the same input.
110
+
111
+ A group that names *no* direction keeps plain CSS shorthand order, which is unambiguous and unchanged.
112
+
109
113
  | Value | Effect |
110
114
  |-------|--------|
111
115
  | `"2x"` | All sides `2x` |
112
116
  | `"2x 1x"` | Top/bottom `2x`, left/right `1x` |
113
117
  | `"2x top"` | Top `2x`, right/bottom/left `0` |
114
118
  | `"1x left right"` | Left and right `1x`, top/bottom `0` |
119
+ | `"2x top, 4x right"` | Top `2x`, right `4x`, bottom/left `0` |
115
120
  | `"1x, 2x top"` | All sides `1x`, then top overridden to `2x` |
116
121
  | `"1x, 2x top bottom"` | Left/right `1x`, top/bottom `2x` |
117
122
  | `"2x longhand"` | All sides `2x`, output as 4 individual `padding-*` properties |
@@ -128,17 +133,20 @@ Individual props `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft`, `
128
133
 
129
134
  Element margin. Same syntax, modifiers, and multi-group support as `padding`.
130
135
 
131
- **Syntax:** `[value]` | `[top right]` | `[top right bottom left]` | `[value directions...]` — comma-separated for multiple groups
136
+ **Syntax:** `[value]` | `[block inline]` | `[top right bottom left]` | `[value direction...]` — comma-separated for multiple groups
132
137
 
133
138
  **Direction modifiers:** `top`, `right`, `bottom`, `left`
134
139
 
135
140
  **Output modifier:** `longhand` — forces output as individual CSS longhand properties (`margin-top`, etc.) instead of the `margin` shorthand.
136
141
 
142
+ The [one-value-per-directional-group rule](#padding) from `padding` applies here too.
143
+
137
144
  | Value | Effect |
138
145
  |-------|--------|
139
146
  | `"2x"` | All sides `2x` |
140
147
  | `"2x 1x"` | Top/bottom `2x`, left/right `1x` |
141
148
  | `"2x top"` | Top `2x`, right/bottom/left `0` |
149
+ | `"2x top, 4x right"` | Top `2x`, right `4x`, bottom/left `0` |
142
150
  | `"auto left right, 1x top bottom"` | Left/right `auto`, top/bottom `1x` |
143
151
  | `true` | All sides `1x` |
144
152
  | Number | Converted to `px` |
@@ -191,10 +199,23 @@ Separate `minHeight` and `maxHeight` props are supported and override values fro
191
199
 
192
200
  Positioning offsets with directional modifiers and multi-group support. Same directional syntax as `padding`.
193
201
 
194
- **Syntax:** `[value]` | `[top right]` | `[top right bottom left]` | `[value directions...]` — comma-separated for multiple groups
202
+ **Syntax:** `[value]` | `[block inline]` | `[top right bottom left]` | `[value direction...]` — comma-separated for multiple groups
195
203
 
196
204
  **Direction modifiers:** `top`, `right`, `bottom`, `left`
197
205
 
206
+ The [one-value-per-directional-group rule](#padding) from `padding` applies here too, with `dock`
207
+ as the single exception.
208
+
209
+ **`dock` modifier:** pins the named edge and spans its full length, by also applying the value
210
+ to the two perpendicular sides. `inset: 'bottom dock'` gives `inset: auto 0 0 0` — anchored to
211
+ the bottom, full width. Without a direction, `dock` fills every side.
212
+
213
+ `dock` is the one place a directional group takes **two** values: the first insets the named
214
+ edge, the second the sides it spans. `inset: '2x 4x bottom dock'` pins the bottom at `2x` and
215
+ insets the sides by `4x`. A third value, or a second value without `dock`, is ignored and warns
216
+ in development. With several directions the first value applies to each named edge and the
217
+ second to every side they span.
218
+
198
219
  **Output modifier:** `longhand` — forces output as individual CSS properties (`top`, `right`, `bottom`, `left`) instead of the `inset` shorthand.
199
220
 
200
221
  | Value | Effect |
@@ -205,6 +226,11 @@ Positioning offsets with directional modifiers and multi-group support. Same dir
205
226
  | `"0, 2x top"` | All sides `0`, then top overridden to `2x` |
206
227
  | `"0 longhand"` | All sides `0`, output as individual `top`/`right`/`bottom`/`left` |
207
228
  | `true` | All sides `0` |
229
+ | `"bottom dock"` | Bottom/left/right `0`, top `auto` — bottom-anchored, full width |
230
+ | `"right dock"` | Right/top/bottom `0`, left `auto` — right-anchored, full height |
231
+ | `"2x bottom dock"` | Bottom/left/right `2x`, top `auto` |
232
+ | `"2x 4x bottom dock"` | Bottom `2x`, left/right `4x`, top `auto` |
233
+ | `"dock"` | All sides `0` |
208
234
 
209
235
  Later comma-separated groups override earlier groups for conflicting directions.
210
236
 
@@ -228,6 +254,7 @@ Background color with design token support. Preferred over `backgroundColor` and
228
254
  | `"#purple.10"` | Token color at 10% opacity |
229
255
  | `"#surface #primary.10"` | Background `#surface` with `#primary.10` overlay (two colors enable smooth transitions between both) |
230
256
  | `"rgb(255 128 0)"` | CSS color value |
257
+ | `"color-mix(in oklab, #primary 50%, #surface)"` | CSS color function — see [CSS Color Functions](dsl.md#css-color-functions) |
231
258
  | `true` | Default fill color |
232
259
 
233
260
  When two colors are provided, the first sets the background color and the second is applied as an overlay gradient layer. This enables independent CSS transitions on each color. The overlay is only applied when no explicit `image` or `backgroundImage` is set.
@@ -260,9 +287,17 @@ Text color with design token support.
260
287
  | `"#current"` | Current inherited color |
261
288
  | `"#current.5"` | Current inherited color at 50% opacity |
262
289
  | `"(#primary, #secondary)"` | Fallback: use `#primary`, fall back to `#secondary` |
290
+ | `"light-dark(#dark, #light)"` | CSS color function — see [CSS Color Functions](dsl.md#css-color-functions) |
263
291
  | `true` | `currentColor` |
264
292
 
265
- When set to a named color token, also sets `$current-color` and `$current-color-{colorSpace}` custom properties for downstream use (suffix depends on the configured `colorSpace`, default `oklch`).
293
+ Also sets `$current-color` to the same color, for anything that needs the
294
+ inherited color as a color rather than as the `currentcolor` keyword. Every color
295
+ publishes it, not just a named token, so a reader below always takes the nearest
296
+ `color` rather than the nearest *token* color. A value that already reads the
297
+ inherited color — `#current` itself, a `#current` fade, a bare `currentColor` —
298
+ is not republished: resolving it a second time one level down would fade it
299
+ twice, and the variable into itself is a self-reference. `#current` does not go
300
+ through it; it is the keyword.
266
301
 
267
302
  ### `svgFill`
268
303
 
@@ -270,7 +305,7 @@ SVG fill color. Same color token syntax as `fill` and `color`. Outputs the nativ
270
305
 
271
306
  ```jsx
272
307
  svgFill="#purple.10"
273
- svgFill="currentColor"
308
+ svgFill="#current"
274
309
  ```
275
310
 
276
311
  ---
@@ -319,7 +354,11 @@ Border radius with shape presets and directional modifiers.
319
354
  | `"backleaf"` | Asymmetric: round, sharp, round, sharp |
320
355
  | `"inherit"` | Inherit border-radius from parent |
321
356
 
322
- **Direction modifiers:** `top`, `right`, `bottom`, `left` — rounds only the specified corners.
357
+ **Edge modifiers:** `top`, `right`, `bottom`, `left` — rounds the *pair* of corners along that edge.
358
+
359
+ **Corner modifiers:** `top-left`, `top-right`, `bottom-right`, `bottom-left` — rounds a single corner.
360
+ Use these when an edge modifier would round one corner too many. Edge and corner modifiers combine
361
+ (`radius: 'top bottom-right'`).
323
362
 
324
363
  **Output modifier:** `longhand` — forces output as individual CSS longhand properties (`border-top-left-radius`, `border-top-right-radius`, `border-bottom-right-radius`, `border-bottom-left-radius`) instead of the `border-radius` shorthand. Useful when children need to selectively inherit individual corners via `radius: 'inherit left'`.
325
364
 
@@ -334,6 +373,9 @@ Border radius with shape presets and directional modifiers.
334
373
  | `"1r longhand"` | All corners `1r`, output as 4 individual `border-*-radius` properties |
335
374
  | `"inherit"` | All corners inherit from parent (`border-radius: inherit`) |
336
375
  | `"inherit right"` | Right corners inherit from parent (uses longhand properties) |
376
+ | `"top-right"` | Top-right corner `1r`, every other corner `0` |
377
+ | `"4px top-left"` | Top-left corner `4px`, every other corner `0` |
378
+ | `"top bottom-right"` | Both top corners and bottom-right `1r`, bottom-left `0` |
337
379
 
338
380
  ### `outline`
339
381
 
@@ -378,11 +420,11 @@ Multiple shadows: `shadow="1x 1x 2x #dark.20, inset 0 0 4x #dark.10"`
378
420
 
379
421
  Typography preset that sets font-size, line-height, letter-spacing, font-weight, font-style, and text-transform from named design tokens.
380
422
 
381
- **Syntax:** `name`, `name / modifier`, or `modifier` (shorthand)
423
+ **Syntax:** `name`, `name / modifier...`, or `modifier...` (shorthand)
382
424
 
383
425
  Preset names are project-specific (e.g. `h1`–`h6`, `t1`–`t4`, `p1`–`p4`). Register them for autocomplete by augmenting `TastyPresetNames`.
384
426
 
385
- Use `/` to separate the preset name from a modifier. When a modifier is used alone (without a name), the preset name defaults to `inherit`.
427
+ Use `/` to separate the preset name from one or more space-separated modifiers. When modifiers are used alone (without a name), the preset name defaults to `inherit`. Modifiers can be combined freely because they target different properties.
386
428
 
387
429
  **Modifiers:**
388
430
 
@@ -394,11 +436,13 @@ Use `/` to separate the preset name from a modifier. When a modifier is used alo
394
436
  | `tight` | Sets line-height equal to font-size |
395
437
 
396
438
  ```jsx
397
- preset="h1" // heading 1
398
- preset="h2 / strong" // bold heading 2
399
- preset="t3 / italic" // italic text 3
400
- preset="t2 / tight" // text 2 with tight line-height
401
- preset="bold" // inherit preset with bold (shorthand for "inherit / bold")
439
+ preset="h1" // heading 1
440
+ preset="h2 / strong" // bold heading 2
441
+ preset="h2 / strong italic" // bold + italic heading 2
442
+ preset="t3 / italic" // italic text 3
443
+ preset="t2 / strong tight" // bold text 2 with tight line-height
444
+ preset="bold" // inherit preset with bold (shorthand)
445
+ preset="bold italic" // inherit preset with bold + italic (shorthand)
402
446
  ```
403
447
 
404
448
  Individual typography props (`fontSize`, `lineHeight`, `letterSpacing`, `fontWeight`, `fontStyle`, `textTransform`) can be used alongside `preset` to override specific values, but using `preset` alone is recommended.
@@ -550,15 +594,20 @@ Scrollbar styling using CSS standard properties (`scrollbar-width`, `scrollbar-c
550
594
 
551
595
  Gradient-based edge fading using CSS masks. Use **comma-separated groups** to set different widths and colors per direction.
552
596
 
553
- **Syntax:** `[width] [directions...] [#from-color] [#to-color]` — comma-separated for multiple groups
597
+ **Syntax:** `[width] [direction...] [#from-color] [#to-color]` — comma-separated for multiple groups
554
598
 
555
599
  **Direction modifiers:** `top`, `right`, `bottom`, `left`
556
600
 
601
+ A group that names edges takes a **single** width, applied to every edge it names — different
602
+ widths per edge come from comma groups. A group that names no edge covers all four and keeps
603
+ plain CSS shorthand order, like `padding`. See the [`padding` rule](#padding).
604
+
557
605
  | Value | Effect |
558
606
  |-------|--------|
559
607
  | `"top"` | Fade top edge, default width (`2x`), all other edges unfaded |
560
608
  | `"2x left right"` | Fade left and right edges with `2x` width, top/bottom unfaded |
561
- | `"3x 1x top bottom"` | Fade top with `3x` width, bottom with `1x` width |
609
+ | `"3x 1x"` | All edges faded — top/bottom `3x`, left/right `1x` (CSS shorthand order) |
610
+ | `"3x top, 1x bottom"` | Fade top with `3x` width, bottom with `1x` width |
562
611
  | `"2x #transparent #dark"` | All edges faded with `2x` width, from `#transparent` to `#dark` |
563
612
  | `"top #a #b, bottom #c #d"` | Top fades from `#a` to `#b`, bottom fades from `#c` to `#d` |
564
613