@tenphi/tasty 0.0.0-snapshot.0c0ba7f → 0.0.0-snapshot.0c2d028

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/README.md +493 -273
  2. package/dist/babel-types-BKDTT4--.d.ts +18 -0
  3. package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
  4. package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
  5. package/dist/chunks/build-config-5d8WlQbq.js +60 -0
  6. package/dist/chunks/build-config-5d8WlQbq.js.map +1 -0
  7. package/dist/chunks/collector-CjCak4iE.js +210 -0
  8. package/dist/chunks/collector-CjCak4iE.js.map +1 -0
  9. package/dist/chunks/config-engine-uOFYZidB.js +272 -0
  10. package/dist/chunks/config-engine-uOFYZidB.js.map +1 -0
  11. package/dist/chunks/config-file-DlZXJMj8.js +25 -0
  12. package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
  13. package/dist/chunks/css-definitions-ubrHP97m.js +910 -0
  14. package/dist/chunks/css-definitions-ubrHP97m.js.map +1 -0
  15. package/dist/chunks/css-resources-P7IBUUZs.js +142 -0
  16. package/dist/chunks/css-resources-P7IBUUZs.js.map +1 -0
  17. package/dist/chunks/debug-CJ31U_WE.js +502 -0
  18. package/dist/chunks/debug-CJ31U_WE.js.map +1 -0
  19. package/dist/chunks/dsl-DYSJc1-T.js +1383 -0
  20. package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
  21. package/dist/chunks/hydration-TRcz5vnk.js +41 -0
  22. package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
  23. package/dist/chunks/react-runtime-CyyCI02N.js +689 -0
  24. package/dist/chunks/react-runtime-CyyCI02N.js.map +1 -0
  25. package/dist/chunks/runtime-engine-BQmOu5-W.js +2432 -0
  26. package/dist/chunks/runtime-engine-BQmOu5-W.js.map +1 -0
  27. package/dist/chunks/runtime-state-DEsyUMU-.js +152 -0
  28. package/dist/chunks/runtime-state-DEsyUMU-.js.map +1 -0
  29. package/dist/{utils/merge-styles.js → chunks/shared-utils-y-HjvPN4.js} +61 -58
  30. package/dist/chunks/shared-utils-y-HjvPN4.js.map +1 -0
  31. package/dist/chunks/style-engine-BX2LshDZ.js +5466 -0
  32. package/dist/chunks/style-engine-BX2LshDZ.js.map +1 -0
  33. package/dist/chunks/zero-engine-Dw0P4yGM.js +310 -0
  34. package/dist/chunks/zero-engine-Dw0P4yGM.js.map +1 -0
  35. package/dist/collector-CTU-nl0g.d.ts +164 -0
  36. package/dist/config-CcxqSZI2.d.ts +1368 -0
  37. package/dist/core/index.d.ts +5 -33
  38. package/dist/core/index.js +9 -26
  39. package/dist/hydrate-CRyjjAyl.d.ts +22 -0
  40. package/dist/index-xtey-KDu.d.ts +1950 -0
  41. package/dist/index.d.ts +5 -48
  42. package/dist/index.js +11 -33
  43. package/dist/{utils/merge-styles.d.ts → merge-styles-DSkVc6CD.d.ts} +3 -4
  44. package/dist/ssr/astro-client.d.ts +1 -0
  45. package/dist/ssr/astro-client.js +6 -0
  46. package/dist/ssr/astro-client.js.map +1 -0
  47. package/dist/ssr/astro-middleware-extract-static.d.ts +10 -0
  48. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  49. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  50. package/dist/ssr/astro-middleware-extract.d.ts +10 -0
  51. package/dist/ssr/astro-middleware-extract.js +9 -0
  52. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  53. package/dist/ssr/astro-middleware-static.d.ts +17 -0
  54. package/dist/ssr/astro-middleware-static.js +7 -0
  55. package/dist/ssr/astro-middleware-static.js.map +1 -0
  56. package/dist/ssr/astro-middleware.d.ts +18 -0
  57. package/dist/ssr/astro-middleware.js +7 -0
  58. package/dist/ssr/astro-middleware.js.map +1 -0
  59. package/dist/ssr/astro.d.ts +127 -0
  60. package/dist/ssr/astro.js +221 -0
  61. package/dist/ssr/astro.js.map +1 -0
  62. package/dist/ssr/client.d.ts +2 -0
  63. package/dist/ssr/client.js +2 -0
  64. package/dist/ssr/index.d.ts +17 -0
  65. package/dist/ssr/index.js +10 -0
  66. package/dist/ssr/index.js.map +1 -0
  67. package/dist/ssr/next-config.d.ts +64 -0
  68. package/dist/ssr/next-config.js +123 -0
  69. package/dist/ssr/next-config.js.map +1 -0
  70. package/dist/ssr/next.d.ts +46 -0
  71. package/dist/ssr/next.js +60 -0
  72. package/dist/ssr/next.js.map +1 -0
  73. package/dist/static/index.d.ts +90 -5
  74. package/dist/static/index.js +31 -4
  75. package/dist/static/index.js.map +1 -0
  76. package/dist/static/inject.d.ts +4 -0
  77. package/dist/static/inject.js +17 -0
  78. package/dist/static/inject.js.map +1 -0
  79. package/dist/types-DJEM_BOJ.d.ts +1567 -0
  80. package/dist/zero/babel.d.ts +49 -90
  81. package/dist/zero/babel.js +252 -119
  82. package/dist/zero/babel.js.map +1 -1
  83. package/dist/zero/index.d.ts +135 -3
  84. package/dist/zero/index.js +23 -3
  85. package/dist/zero/index.js.map +1 -0
  86. package/dist/zero/next.d.ts +58 -33
  87. package/dist/zero/next.js +89 -59
  88. package/dist/zero/next.js.map +1 -1
  89. package/docs/README.md +42 -0
  90. package/docs/adoption.md +331 -0
  91. package/docs/ai-agents.md +253 -0
  92. package/docs/comparison.md +449 -0
  93. package/docs/configuration.md +954 -0
  94. package/docs/debug.md +333 -0
  95. package/docs/design-system.md +557 -0
  96. package/docs/dsl.md +1106 -0
  97. package/docs/getting-started.md +297 -0
  98. package/docs/injector.md +633 -0
  99. package/docs/methodology.md +673 -0
  100. package/docs/migration-v3.md +285 -0
  101. package/docs/pipeline.md +768 -0
  102. package/docs/plugins.md +333 -0
  103. package/docs/react-api.md +703 -0
  104. package/docs/runtime-benchmarks.md +388 -0
  105. package/docs/ssr.md +630 -0
  106. package/docs/styles.md +725 -0
  107. package/docs/tasty-static.md +669 -0
  108. package/package.json +198 -59
  109. package/tasty.config.ts +6 -0
  110. package/dist/_virtual/_rolldown/runtime.js +0 -8
  111. package/dist/chunks/cacheKey.js +0 -70
  112. package/dist/chunks/cacheKey.js.map +0 -1
  113. package/dist/chunks/definitions.d.ts +0 -37
  114. package/dist/chunks/definitions.js +0 -260
  115. package/dist/chunks/definitions.js.map +0 -1
  116. package/dist/chunks/renderChunk.js +0 -61
  117. package/dist/chunks/renderChunk.js.map +0 -1
  118. package/dist/config.d.ts +0 -280
  119. package/dist/config.js +0 -403
  120. package/dist/config.js.map +0 -1
  121. package/dist/debug.d.ts +0 -204
  122. package/dist/debug.js +0 -733
  123. package/dist/debug.js.map +0 -1
  124. package/dist/hooks/useGlobalStyles.d.ts +0 -27
  125. package/dist/hooks/useGlobalStyles.js +0 -56
  126. package/dist/hooks/useGlobalStyles.js.map +0 -1
  127. package/dist/hooks/useKeyframes.d.ts +0 -56
  128. package/dist/hooks/useKeyframes.js +0 -54
  129. package/dist/hooks/useKeyframes.js.map +0 -1
  130. package/dist/hooks/useProperty.d.ts +0 -79
  131. package/dist/hooks/useProperty.js +0 -91
  132. package/dist/hooks/useProperty.js.map +0 -1
  133. package/dist/hooks/useRawCSS.d.ts +0 -53
  134. package/dist/hooks/useRawCSS.js +0 -28
  135. package/dist/hooks/useRawCSS.js.map +0 -1
  136. package/dist/hooks/useStyles.d.ts +0 -40
  137. package/dist/hooks/useStyles.js +0 -169
  138. package/dist/hooks/useStyles.js.map +0 -1
  139. package/dist/injector/index.d.ts +0 -157
  140. package/dist/injector/index.js +0 -154
  141. package/dist/injector/index.js.map +0 -1
  142. package/dist/injector/injector.d.ts +0 -139
  143. package/dist/injector/injector.js +0 -404
  144. package/dist/injector/injector.js.map +0 -1
  145. package/dist/injector/sheet-manager.d.ts +0 -127
  146. package/dist/injector/sheet-manager.js +0 -714
  147. package/dist/injector/sheet-manager.js.map +0 -1
  148. package/dist/injector/types.d.ts +0 -135
  149. package/dist/keyframes/index.js +0 -206
  150. package/dist/keyframes/index.js.map +0 -1
  151. package/dist/parser/classify.js +0 -319
  152. package/dist/parser/classify.js.map +0 -1
  153. package/dist/parser/const.js +0 -33
  154. package/dist/parser/const.js.map +0 -1
  155. package/dist/parser/lru.js +0 -109
  156. package/dist/parser/lru.js.map +0 -1
  157. package/dist/parser/parser.d.ts +0 -25
  158. package/dist/parser/parser.js +0 -116
  159. package/dist/parser/parser.js.map +0 -1
  160. package/dist/parser/tokenizer.js +0 -69
  161. package/dist/parser/tokenizer.js.map +0 -1
  162. package/dist/parser/types.d.ts +0 -51
  163. package/dist/parser/types.js +0 -46
  164. package/dist/parser/types.js.map +0 -1
  165. package/dist/pipeline/conditions.d.ts +0 -134
  166. package/dist/pipeline/conditions.js +0 -400
  167. package/dist/pipeline/conditions.js.map +0 -1
  168. package/dist/pipeline/exclusive.js +0 -231
  169. package/dist/pipeline/exclusive.js.map +0 -1
  170. package/dist/pipeline/index.d.ts +0 -53
  171. package/dist/pipeline/index.js +0 -645
  172. package/dist/pipeline/index.js.map +0 -1
  173. package/dist/pipeline/materialize.js +0 -864
  174. package/dist/pipeline/materialize.js.map +0 -1
  175. package/dist/pipeline/parseStateKey.d.ts +0 -15
  176. package/dist/pipeline/parseStateKey.js +0 -440
  177. package/dist/pipeline/parseStateKey.js.map +0 -1
  178. package/dist/pipeline/simplify.js +0 -542
  179. package/dist/pipeline/simplify.js.map +0 -1
  180. package/dist/pipeline/warnings.js +0 -18
  181. package/dist/pipeline/warnings.js.map +0 -1
  182. package/dist/plugins/okhsl-plugin.d.ts +0 -35
  183. package/dist/plugins/okhsl-plugin.js +0 -371
  184. package/dist/plugins/okhsl-plugin.js.map +0 -1
  185. package/dist/plugins/types.d.ts +0 -69
  186. package/dist/properties/index.js +0 -158
  187. package/dist/properties/index.js.map +0 -1
  188. package/dist/states/index.d.ts +0 -49
  189. package/dist/states/index.js +0 -416
  190. package/dist/states/index.js.map +0 -1
  191. package/dist/static/tastyStatic.d.ts +0 -46
  192. package/dist/static/tastyStatic.js +0 -31
  193. package/dist/static/tastyStatic.js.map +0 -1
  194. package/dist/static/types.d.ts +0 -49
  195. package/dist/static/types.js +0 -24
  196. package/dist/static/types.js.map +0 -1
  197. package/dist/styles/align.d.ts +0 -15
  198. package/dist/styles/align.js +0 -14
  199. package/dist/styles/align.js.map +0 -1
  200. package/dist/styles/border.d.ts +0 -25
  201. package/dist/styles/border.js +0 -114
  202. package/dist/styles/border.js.map +0 -1
  203. package/dist/styles/color.d.ts +0 -14
  204. package/dist/styles/color.js +0 -23
  205. package/dist/styles/color.js.map +0 -1
  206. package/dist/styles/createStyle.js +0 -77
  207. package/dist/styles/createStyle.js.map +0 -1
  208. package/dist/styles/dimension.js +0 -97
  209. package/dist/styles/dimension.js.map +0 -1
  210. package/dist/styles/display.d.ts +0 -37
  211. package/dist/styles/display.js +0 -67
  212. package/dist/styles/display.js.map +0 -1
  213. package/dist/styles/fade.d.ts +0 -15
  214. package/dist/styles/fade.js +0 -58
  215. package/dist/styles/fade.js.map +0 -1
  216. package/dist/styles/fill.d.ts +0 -42
  217. package/dist/styles/fill.js +0 -51
  218. package/dist/styles/fill.js.map +0 -1
  219. package/dist/styles/flow.d.ts +0 -16
  220. package/dist/styles/flow.js +0 -12
  221. package/dist/styles/flow.js.map +0 -1
  222. package/dist/styles/gap.d.ts +0 -31
  223. package/dist/styles/gap.js +0 -37
  224. package/dist/styles/gap.js.map +0 -1
  225. package/dist/styles/height.d.ts +0 -17
  226. package/dist/styles/height.js +0 -20
  227. package/dist/styles/height.js.map +0 -1
  228. package/dist/styles/index.d.ts +0 -2
  229. package/dist/styles/index.js +0 -9
  230. package/dist/styles/index.js.map +0 -1
  231. package/dist/styles/inset.d.ts +0 -52
  232. package/dist/styles/inset.js +0 -150
  233. package/dist/styles/inset.js.map +0 -1
  234. package/dist/styles/justify.d.ts +0 -15
  235. package/dist/styles/justify.js +0 -14
  236. package/dist/styles/justify.js.map +0 -1
  237. package/dist/styles/list.d.ts +0 -16
  238. package/dist/styles/list.js +0 -98
  239. package/dist/styles/list.js.map +0 -1
  240. package/dist/styles/margin.d.ts +0 -24
  241. package/dist/styles/margin.js +0 -104
  242. package/dist/styles/margin.js.map +0 -1
  243. package/dist/styles/outline.d.ts +0 -29
  244. package/dist/styles/outline.js +0 -65
  245. package/dist/styles/outline.js.map +0 -1
  246. package/dist/styles/padding.d.ts +0 -24
  247. package/dist/styles/padding.js +0 -104
  248. package/dist/styles/padding.js.map +0 -1
  249. package/dist/styles/predefined.d.ts +0 -73
  250. package/dist/styles/predefined.js +0 -241
  251. package/dist/styles/predefined.js.map +0 -1
  252. package/dist/styles/preset.d.ts +0 -47
  253. package/dist/styles/preset.js +0 -126
  254. package/dist/styles/preset.js.map +0 -1
  255. package/dist/styles/radius.d.ts +0 -14
  256. package/dist/styles/radius.js +0 -51
  257. package/dist/styles/radius.js.map +0 -1
  258. package/dist/styles/scrollbar.d.ts +0 -21
  259. package/dist/styles/scrollbar.js +0 -112
  260. package/dist/styles/scrollbar.js.map +0 -1
  261. package/dist/styles/shadow.d.ts +0 -14
  262. package/dist/styles/shadow.js +0 -24
  263. package/dist/styles/shadow.js.map +0 -1
  264. package/dist/styles/styledScrollbar.d.ts +0 -47
  265. package/dist/styles/styledScrollbar.js +0 -38
  266. package/dist/styles/styledScrollbar.js.map +0 -1
  267. package/dist/styles/transition.d.ts +0 -14
  268. package/dist/styles/transition.js +0 -158
  269. package/dist/styles/transition.js.map +0 -1
  270. package/dist/styles/types.d.ts +0 -498
  271. package/dist/styles/width.d.ts +0 -17
  272. package/dist/styles/width.js +0 -20
  273. package/dist/styles/width.js.map +0 -1
  274. package/dist/tasty.d.ts +0 -982
  275. package/dist/tasty.js +0 -191
  276. package/dist/tasty.js.map +0 -1
  277. package/dist/tokens/typography.d.ts +0 -19
  278. package/dist/tokens/typography.js +0 -237
  279. package/dist/tokens/typography.js.map +0 -1
  280. package/dist/types.d.ts +0 -184
  281. package/dist/utils/cache-wrapper.js +0 -26
  282. package/dist/utils/cache-wrapper.js.map +0 -1
  283. package/dist/utils/case-converter.js +0 -8
  284. package/dist/utils/case-converter.js.map +0 -1
  285. package/dist/utils/colors.d.ts +0 -5
  286. package/dist/utils/colors.js +0 -9
  287. package/dist/utils/colors.js.map +0 -1
  288. package/dist/utils/css-types.d.ts +0 -7
  289. package/dist/utils/dotize.d.ts +0 -26
  290. package/dist/utils/dotize.js +0 -122
  291. package/dist/utils/dotize.js.map +0 -1
  292. package/dist/utils/filter-base-props.d.ts +0 -15
  293. package/dist/utils/filter-base-props.js +0 -45
  294. package/dist/utils/filter-base-props.js.map +0 -1
  295. package/dist/utils/get-display-name.d.ts +0 -7
  296. package/dist/utils/get-display-name.js +0 -10
  297. package/dist/utils/get-display-name.js.map +0 -1
  298. package/dist/utils/hsl-to-rgb.js +0 -38
  299. package/dist/utils/hsl-to-rgb.js.map +0 -1
  300. package/dist/utils/is-dev-env.js +0 -19
  301. package/dist/utils/is-dev-env.js.map +0 -1
  302. package/dist/utils/is-valid-element-type.js +0 -15
  303. package/dist/utils/is-valid-element-type.js.map +0 -1
  304. package/dist/utils/merge-styles.js.map +0 -1
  305. package/dist/utils/mod-attrs.d.ts +0 -8
  306. package/dist/utils/mod-attrs.js +0 -21
  307. package/dist/utils/mod-attrs.js.map +0 -1
  308. package/dist/utils/okhsl-to-rgb.js +0 -296
  309. package/dist/utils/okhsl-to-rgb.js.map +0 -1
  310. package/dist/utils/process-tokens.d.ts +0 -31
  311. package/dist/utils/process-tokens.js +0 -171
  312. package/dist/utils/process-tokens.js.map +0 -1
  313. package/dist/utils/resolve-recipes.d.ts +0 -17
  314. package/dist/utils/resolve-recipes.js +0 -147
  315. package/dist/utils/resolve-recipes.js.map +0 -1
  316. package/dist/utils/string.js +0 -8
  317. package/dist/utils/string.js.map +0 -1
  318. package/dist/utils/styles.d.ts +0 -178
  319. package/dist/utils/styles.js +0 -590
  320. package/dist/utils/styles.js.map +0 -1
  321. package/dist/utils/typography.d.ts +0 -36
  322. package/dist/utils/typography.js +0 -53
  323. package/dist/utils/typography.js.map +0 -1
  324. package/dist/utils/warnings.d.ts +0 -16
  325. package/dist/utils/warnings.js +0 -16
  326. package/dist/utils/warnings.js.map +0 -1
  327. package/dist/zero/css-writer.d.ts +0 -45
  328. package/dist/zero/css-writer.js +0 -74
  329. package/dist/zero/css-writer.js.map +0 -1
  330. package/dist/zero/extractor.d.ts +0 -24
  331. package/dist/zero/extractor.js +0 -150
  332. package/dist/zero/extractor.js.map +0 -1
package/README.md CHANGED
@@ -1,38 +1,54 @@
1
1
  <p align="center">
2
- <img src="assets/tasty.svg" width="128" height="128" alt="Tasty logo">
2
+ <img src="assets/tasty.svg" width="128" height="128" alt="Tasty logo" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">Tasty</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>The styling engine built for design systems.</strong><br>
9
- Deterministic CSS generation. State-aware DSL. Zero specificity conflicts. Ever.
8
+ <strong>CSS-in-JS for React design systems.</strong><br />
9
+ Build components whose styles don’t fight.
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <a href="https://www.npmjs.com/package/@tenphi/tasty"><img src="https://img.shields.io/npm/v/@tenphi/tasty.svg" alt="npm version"></a>
14
- <a href="https://github.com/tenphi/tasty/actions/workflows/ci.yml"><img src="https://github.com/tenphi/tasty/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
15
- <a href="https://github.com/tenphi/tasty/blob/main/LICENSE"><img src="https://img.shields.io/github/license/tenphi/tasty" alt="license"></a>
13
+ <a href="https://www.npmjs.com/package/@tenphi/tasty"><img src="https://img.shields.io/npm/v/@tenphi/tasty.svg" alt="npm version" /></a>
14
+ <a href="https://github.com/tenphi/tasty/actions/workflows/ci.yml"><img src="https://github.com/tenphi/tasty/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI" /></a>
15
+ <a href="https://github.com/tenphi/tasty/blob/main/LICENSE"><img src="https://img.shields.io/github/license/tenphi/tasty" alt="license" /></a>
16
16
  </p>
17
17
 
18
18
  ---
19
19
 
20
- Most CSS-in-JS libraries emit rules that compete through cascade and specificity. Tasty emits **mutually exclusive CSS selectors** — for any component state combination, exactly one selector matches each property at a time. No cascade conflicts, no specificity wars, no `!important` escapes. Components compose and extend without breaking each other.
20
+ Tasty is a styling engine for teams that build component libraries. You describe hover, disabled, variants, themes, and breakpoints as **state maps**, in a CSS-like language built from your design system's own tokens and conventions. Tasty compiles each map into selectors that never match at the same time, so the result never depends on specificity or source order.
21
21
 
22
- That guarantee unlocks a concise, CSS-like DSL where design tokens, custom units, responsive states, container queries, sub-element styling, and theming all compose without surprises — one coherent system that scales from a single component to an enterprise design system.
22
+ ```tsx
23
+ import { tasty } from '@tenphi/tasty';
24
+
25
+ const Button = tasty({
26
+ as: 'button',
27
+ styles: {
28
+ fill: {
29
+ '': '#primary',
30
+ ':hover': '#primary-hover',
31
+ ':active': '#primary-pressed',
32
+ '[disabled]': '#surface',
33
+ },
34
+ },
35
+ });
36
+ ```
37
+
38
+ Later keys have higher priority. If this button is both hovered and disabled, `[disabled]` wins and the hover rule does not apply. That decision is compiled into the selectors themselves, so you can add states, variants, themes, and overrides without reopening selector logic by hand.
39
+
40
+ - **Ready to try it?** Follow [Getting Started](docs/getting-started.md).
41
+ - **Evaluating fit?** Read [Comparison](docs/comparison.md) and the [Adoption Guide](docs/adoption.md).
42
+ - **Building a component library?** See [Methodology](docs/methodology.md) and [Building a Design System](docs/design-system.md).
23
43
 
24
44
  ## Why Tasty
25
45
 
26
- - **Deterministic at any scale** — Exclusive selector generation eliminates the entire class of cascade/specificity bugs. Every state combination resolves to exactly one CSS rule per property. Refactor freely.
27
- - **AI-friendly by design** — Style definitions are declarative, self-contained, and structurally consistent. AI tools can read, understand, and refactor even advanced state bindings as confidently as a human — because there's no hidden cascade logic or implicit ordering to second-guess.
28
- - **DSL that feels like CSS** — Property names you already know (`padding`, `color`, `display`) with syntax sugar that removes boilerplate. Learn the DSL in minutes, not days.
29
- - **Design-system native** — Color tokens (`#primary`), spacing units (`2x`), typography presets (`h1`, `t2`), border radius (`1r`), and recipes are first-class primitives, not afterthoughts.
30
- - **Near-complete modern CSS coverage** — Media queries, container queries, `@supports`, `:has()`, `@starting-style`, `@property`, `@keyframes`, etc. Some features that don't fit Tasty's component model (such as `@layer` and `!important`) are intentionally omitted, but real-world use cases are covered almost completely.
31
- - **Runtime or zero-runtime — your call** — Use `tasty()` for dynamic React components with runtime injection, or `tastyStatic()` with the Babel plugin for zero-runtime CSS extraction. Same DSL, same tokens, same output.
32
- - **Only generate what is used** — In runtime mode, Tasty injects CSS on demand for mounted components/variants, so your app avoids shipping style rules for UI states that are never rendered.
33
- - **Runtime performance that holds at scale** — The runtime path is tested against enterprise-scale applications and tuned with multi-level caching, chunk-level style reuse, style garbage collection, and a dedicated injector.
34
- - **Composable and extensible by design** — Extend any component's styles with proper merge semantics, and evolve built-in behavior through configuration and plugins.
35
- - **TypeScript-first** — Full type definitions, module augmentation for custom properties, and autocomplete for tokens, presets, and themes.
46
+ - **States that can't conflict** — Every state map compiles into mutually exclusive selectors, and the last matching key wins by construction. Adding a state, variant, or override never means re-deriving selector interactions. [How it works →](#how-it-works)
47
+ - **Your design system's language** — Define color tokens, units, state aliases like `@mobile` and `@dark`, typography presets, and recipes once with `configure()`. Components then read like the spec: `padding: '2x'`, `fill: '#surface'`, `preset: 'h3'`. [Design-system integration →](#design-system-integration)
48
+ - **Typed component APIs** — Decide exactly what consumers can change. `styleProps`, `modProps`, `tokenProps`, `variants`, and sub-elements become typed React props and components with full autocomplete. [Component API →](#component-api)
49
+ - **Guardrails in the editor and CI** — An ESLint and oxlint plugin with 40+ rules checks property names, values, tokens, state keys, presets, and recipes against your project's config. TypeScript augmentation autocompletes your own token, preset, and theme names. [Types and linting →](#types-and-linting)
50
+ - **Modern CSS, one model** — Media and container queries, `@supports`, `:has()`, root and parent states, and `@starting-style` are all state keys. `@keyframes`, `@property`, `@font-face`, `@counter-style`, and `@function` are built in. [CSS coverage →](#css-coverage)
51
+ - **Render anywhere** — The same styles work in client React, React Server Components, and streaming SSR (Next.js, Astro). Render only on the server to ship no styling runtime at all, or extract CSS at build time with `tastyStatic()`. [Rendering modes →](#rendering-modes)
36
52
 
37
53
  ## Installation
38
54
 
@@ -40,9 +56,13 @@ That guarantee unlocks a concise, CSS-like DSL where design tokens, custom units
40
56
  pnpm add @tenphi/tasty
41
57
  ```
42
58
 
59
+ Requires Node.js 20+ and React 18+ (a peer dependency of the React entry points). `npm add @tenphi/tasty` and `yarn add @tenphi/tasty` work the same way.
60
+
43
61
  ## Quick Start
44
62
 
45
- ### Create a styled component
63
+ ### 1. Write a component
64
+
65
+ Plain CSS values work as-is, so a first component looks like CSS you already know:
46
66
 
47
67
  ```tsx
48
68
  import { tasty } from '@tenphi/tasty';
@@ -51,96 +71,154 @@ const Card = tasty({
51
71
  as: 'div',
52
72
  styles: {
53
73
  display: 'flex',
54
- flow: 'column',
55
- padding: '4x',
56
- gap: '2x',
57
- fill: '#surface',
58
- border: '#border bottom',
59
- radius: '1r',
74
+ flow: 'column', // flex-flow
75
+ padding: '24px',
76
+ gap: '12px',
77
+ fill: 'white', // background-color
78
+ color: '#222',
79
+ border: '1px solid #ddd',
80
+ radius: '12px', // border-radius
60
81
  },
61
82
  });
62
83
 
63
- // Just a React component
64
- <Card>Hello World</Card>
84
+ <Card>Hello World</Card>;
65
85
  ```
66
86
 
67
- Every value maps to CSS you'd recognize — but with tokens and units that keep your design system consistent by default.
87
+ ### 2. Bring in your design system
68
88
 
69
- ### Add state-driven styles
89
+ Call `configure()` once, before your app renders, to define the vocabulary every component shares:
90
+
91
+ ```tsx
92
+ // tasty.setup.ts — import it at the top of your entry point
93
+ import { configure } from '@tenphi/tasty';
94
+
95
+ configure({
96
+ tokens: {
97
+ '#surface': { '': '#fff', '@dark': '#1c1c1e' },
98
+ '#text': { '': '#222', '@dark': '#f2f2f7' },
99
+ '#border': '#ddd',
100
+ },
101
+ units: { x: '8px', r: '12px' }, // spacing step and radius
102
+ states: {
103
+ '@mobile': '@media(w < 768px)',
104
+ '@dark':
105
+ '@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
106
+ },
107
+ });
108
+ ```
109
+
110
+ The same card, written in that vocabulary:
111
+
112
+ ```tsx
113
+ const Card = tasty({
114
+ styles: {
115
+ display: 'flex',
116
+ flow: 'column',
117
+ padding: { '': '3x', '@mobile': '2x' }, // 24px, 16px on mobile
118
+ gap: '1.5x', // 12px
119
+ fill: '#surface', // var(--surface-color)
120
+ color: '#text',
121
+ border: true, // 1bw solid #border
122
+ radius: '1r', // 12px
123
+ },
124
+ });
125
+ ```
126
+
127
+ The component never mentions dark mode, yet it follows the `@dark` tokens. Change a token or a unit once and every component follows.
128
+
129
+ ### 3. Add states and a typed API
70
130
 
71
131
  ```tsx
72
132
  const Button = tasty({
73
133
  as: 'button',
134
+ modProps: { isLoading: Boolean, size: ['sm', 'md', 'lg'] as const },
74
135
  styles: {
75
- padding: '1.5x 3x',
136
+ padding: { '': '1.5x 3x', 'size=sm': '1x 2x', 'size=lg': '2x 4x' },
137
+ radius: '1r',
76
138
  fill: {
77
139
  '': '#primary',
78
140
  ':hover': '#primary-hover',
79
141
  ':active': '#primary-pressed',
80
- '[disabled]': '#surface',
81
- },
82
- color: {
83
- '': '#on-primary',
84
- '[disabled]': '#text.40',
85
- },
86
- cursor: {
87
- '': 'pointer',
88
- '[disabled]': 'not-allowed',
142
+ disabled: '#surface',
89
143
  },
144
+ color: { '': '#on-primary', disabled: '#text.40' },
145
+ cursor: { '': 'pointer', isLoading: 'wait', disabled: 'not-allowed' },
90
146
  transition: 'theme',
91
147
  },
92
148
  });
149
+
150
+ <Button size="lg" isLoading>
151
+ Save
152
+ </Button>;
153
+ // renders <button data-is-loading data-size="lg" class="…">
93
154
  ```
94
155
 
95
- State keys support pseudo-classes first (`:hover`, `:active`), then modifiers (`theme=danger`), attributes (`[disabled]`), media/container queries, root states, and more. Tasty compiles them into exclusive selectors automatically.
156
+ `size` is typed as `'sm' | 'md' | 'lg'`. Mod props become `data-*` attributes, which state keys like `size=lg` and `isLoading` select. `disabled` tracks the native `disabled` prop, `#text.40` is the text color at 40% opacity, and `transition: 'theme'` is a named group that transitions color, background, border, shadow, outline, and opacity together.
96
157
 
97
- ### Extend any component
158
+ ### 4. Extend a component
98
159
 
99
160
  ```tsx
100
- import { Button } from 'my-ui-lib';
101
-
102
161
  const DangerButton = tasty(Button, {
103
162
  styles: {
104
163
  fill: {
105
164
  '': '#danger',
106
165
  ':hover': '#danger-hover',
166
+ ':active': '#danger-pressed',
167
+ disabled: '@inherit', // keep the parent's disabled branch
107
168
  },
108
169
  },
109
170
  });
110
171
  ```
111
172
 
112
- Child styles merge with parent styles intelligently — state maps can extend or replace parent states per-property.
173
+ A state map with a `''` key replaces the parent's map; a map without one extends it. `@inherit` pulls a single branch from the parent, `null` removes one, and `false` blocks it entirely. See [Extending vs. replacing state maps](docs/dsl.md#extending-vs-replacing-state-maps).
113
174
 
114
- ### Configure once, use everywhere
175
+ [Getting Started](docs/getting-started.md) continues from here with setup, linting, editor tooling, and choosing a rendering mode.
115
176
 
116
- ```tsx
117
- import { configure } from '@tenphi/tasty';
177
+ ## How It Works
118
178
 
119
- configure({
120
- states: {
121
- '@mobile': '@media(w < 768px)',
122
- '@tablet': '@media(w < 1024px)',
123
- '@dark': '@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
124
- },
125
- recipes: {
126
- card: { padding: '4x', fill: '#surface', radius: '1r', border: true },
127
- },
128
- });
179
+ The cascade resolves conflicts by specificity, then by source order. Take two rules for a button's background:
180
+
181
+ ```css
182
+ .btn:hover {
183
+ background: dodgerblue;
184
+ }
185
+ .btn[disabled] {
186
+ background: gray;
187
+ }
129
188
  ```
130
189
 
131
- Predefined states turn complex selector logic into single tokens. Use `@mobile` instead of writing media query expressions in every component.
190
+ Both selectors have specificity `(0, 2, 0)`. When the button is hovered **and** disabled, both match and the later rule wins. Swap the two rules and a hovered disabled button silently turns blue. The logic is correct; only the order is wrong, which is why this class of bug survives code review.
191
+
192
+ Tasty removes the competition. For the same intent:
132
193
 
133
- ## How It Actually Works
194
+ ```tsx
195
+ fill: {
196
+ '': '#primary',
197
+ ':hover': '#primary-hover',
198
+ '[disabled]': '#surface',
199
+ }
200
+ ```
134
201
 
135
- This is the core idea that makes everything else possible.
202
+ it generates:
136
203
 
137
- Traditional CSS has two structural problems.
204
+ ```css
205
+ .t0.t0:where(:not([disabled]):not(:hover)) {
206
+ background-color: var(--primary-color);
207
+ }
208
+ .t0.t0:where(:not([disabled]):hover) {
209
+ background-color: var(--primary-hover-color);
210
+ }
211
+ .t0.t0:where([disabled]) {
212
+ background-color: var(--surface-color);
213
+ }
214
+ ```
138
215
 
139
- First, the **cascade** resolves conflicts by specificity and source order: when multiple selectors match, the one with the highest specificity wins, or — if specificity is equal — the last one in source order wins. That makes styles inherently fragile. Reordering imports, adding a media query, or composing components from different libraries can silently break styling.
216
+ Each branch is guarded by the negation of every higher-priority branch, so exactly one rule matches any element. Reorder the rules, split them across stylesheets, or extend the component: the outcome stays the same. Because the logic stays in CSS rather than JavaScript, the browser handles layout, painting, and transitions natively.
140
217
 
141
- Second, **authoring selectors that capture real-world state logic is fundamentally hard.** A single state like "dark mode" may depend on a root attribute, an OS preference, or both — each branch needing its own selector, proper negation of competing branches, and correct `@media` nesting. The example below shows the CSS you'd write by hand for just *one* property with *one* state. Scale that across dozens of properties, then add breakpoints and container queries, and the selector logic quickly becomes unmanageable.
218
+ <details>
219
+ <summary><strong>A harder case: dark mode from a root attribute or the OS preference</strong></summary>
142
220
 
143
- Tasty solves both problems at once: **every state mapping compiles into mutually exclusive selectors.**
221
+ Say `@dark` means "the root has `data-schema="dark"`, or no schema is set and the OS prefers dark":
144
222
 
145
223
  ```tsx
146
224
  const Text = tasty({
@@ -149,335 +227,477 @@ const Text = tasty({
149
227
  '': '#text',
150
228
  '@dark': '#text-on-dark',
151
229
  },
152
- padding: {
153
- '': '4x',
154
- '@mobile': '2x',
155
- },
156
230
  },
157
231
  });
158
232
  ```
159
233
 
160
- If `@dark` expands to `@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))`, Tasty generates:
234
+ A first attempt by hand:
161
235
 
162
236
  ```css
163
- /* Branch 1: Explicit dark schema */
164
- :root[data-schema="dark"] .t0.t0 {
237
+ .t0 {
238
+ color: var(--text-color);
239
+ }
240
+ :root[data-schema='dark'] .t0 {
241
+ color: var(--text-on-dark-color);
242
+ }
243
+ @media (prefers-color-scheme: dark) {
244
+ .t0 {
245
+ color: var(--text-on-dark-color);
246
+ }
247
+ }
248
+ ```
249
+
250
+ The `@media` branch fires even when `data-schema="light"` is set explicitly. Scoping it to `:root:not([data-schema])` fixes that, but the bare `.t0` default still matches unconditionally, and it can beat the attribute selector by source order if another rule re-declares it later. No single selector says "apply the default only when none of the dark branches win". The correct version negates every other branch, which is what Tasty generates:
251
+
252
+ ```css
253
+ /* Explicit dark schema */
254
+ :where(:root[data-schema='dark']) .t0.t0 {
165
255
  color: var(--text-on-dark-color);
166
256
  }
167
257
 
168
- /* Branch 2: No schema attribute + OS prefers dark */
258
+ /* No schema attribute, OS prefers dark */
169
259
  @media (prefers-color-scheme: dark) {
170
- :root:not([data-schema]) .t0.t0 {
260
+ :where(:root:not([data-schema])) .t0.t0 {
171
261
  color: var(--text-on-dark-color);
172
262
  }
173
263
  }
174
264
 
175
- /* Default: no schema + OS does not prefer dark */
265
+ /* Default: OS prefers light, schema is not dark */
176
266
  @media (not (prefers-color-scheme: dark)) {
177
- :root:not([data-schema="dark"]) .t0.t0 {
267
+ :where(:root:not([data-schema='dark'])) .t0.t0 {
178
268
  color: var(--text-color);
179
269
  }
180
270
  }
181
271
 
182
- /* Default: schema is set but not dark (any OS preference) */
183
- :root:not([data-schema="dark"])[data-schema] .t0.t0 {
184
- color: var(--text-color);
272
+ /* Default: OS prefers dark, but a non-dark schema is set */
273
+ @media (prefers-color-scheme: dark) {
274
+ :where(:root:not([data-schema='dark'])[data-schema]) .t0.t0 {
275
+ color: var(--text-color);
276
+ }
185
277
  }
186
278
  ```
187
279
 
188
- Every rule is guarded by the negation of higher-priority rules. No two rules can match at the same time. No specificity arithmetic. No source-order dependence. Components compose and extend without collisions.
280
+ That is one property with one state. Multiply it by dozens of properties, breakpoints, and container queries, and hand-written selectors stop being maintainable.
189
281
 
190
- By absorbing selector complexity, Tasty makes advanced CSS patterns practical again — nested container queries, multi-condition `@supports` gates, and combined root-state/media branches. You stay in pure CSS instead of relying on JavaScript workarounds, so the browser can optimize layout, painting, and transitions natively. Tasty doesn't limit CSS; it unlocks its full potential by removing the complexity that held teams back.
282
+ </details>
191
283
 
192
- ## Capabilities
284
+ For the full compiler model (parsing state keys, building exclusive conditions, merging by value, materializing selectors and at-rules), see the [Style rendering pipeline](docs/pipeline.md). [Try it in the playground →](https://tasty.style/playground)
193
285
 
194
- ### Design Tokens and Custom Units
286
+ ## Design-System Integration
195
287
 
196
- Tokens are first-class. Colors use `#name` syntax. Spacing, radius, and border width use multiplier units tied to CSS custom properties:
288
+ `configure()` is where a design system's vocabulary lives. Everything defined there is available to every component, and the same names feed the linter and TypeScript.
197
289
 
198
- ```tsx
199
- fill: '#surface', // → var(--surface-color)
200
- color: '#text.80', // → 80% opacity text token
201
- padding: '2x', // → calc(var(--gap) * 2)
202
- radius: '1r', // → var(--radius)
203
- border: '1bw solid #border',
204
- ```
290
+ | Define | With `configure({ … })` | Use in styles |
291
+ | ---------------------------- | ------------------------------------------------------- | -------------------------------------------------------- |
292
+ | Color tokens | `tokens: { '#surface': '#fff' }` | `fill: '#surface'`, `color: '#text.60'` |
293
+ | Value tokens | `tokens: { '$sidebar-width': '280px' }` | `width: '$sidebar-width'` |
294
+ | Units | `units: { x: '8px', r: '6px' }` | `padding: '2x'`, `radius: '1r'` |
295
+ | State aliases | `states: { '@mobile': '@media(w < 768px)' }` | `gap: { '': '2x', '@mobile': '1x' }` |
296
+ | Typography presets | `presets: { h1: { fontSize: '2rem', … } }` | `preset: 'h1'` |
297
+ | Recipes | `recipes: { card: { padding: '4x', … } }` | `recipe: 'card'` |
298
+ | Global at-rules | `keyframes`, `properties`, `fontFaces`, `counterStyles` | `animation: 'fade-in 200ms'` |
299
+ | Functions, handlers, plugins | `functions`, `handlers`, `propHandlers`, `plugins` | `$$name(...)`, new style properties, new component props |
205
300
 
206
- | Unit | Maps to | Example |
207
- |------|---------|---------|
208
- | `x` | `--gap` multiplier | `2x` → `calc(var(--gap) * 2)` |
209
- | `r` | `--radius` multiplier | `1r` → `var(--radius)` |
210
- | `bw` | `--border-width` multiplier | `1bw` → `var(--border-width)` |
211
- | `ow` | `--outline-width` multiplier | `1ow` → `var(--outline-width)` |
212
- | `cr` | `--card-radius` multiplier | `1cr` → `var(--card-radius)` |
301
+ Tokens accept state maps, so a theme is just more branches: `'#surface': { '': '#fff', '@dark': '#1c1c1e' }`. To generate whole light, dark, and high-contrast palettes with contrast solving, use [Glaze](#glaze).
213
302
 
214
- Define your own units via `configure({ units: { ... } })`.
303
+ ### Built-in units
215
304
 
216
- ### State System
305
+ | Unit | Default | Example |
306
+ | ---- | ------------------- | ------------------------ |
307
+ | `x` | `8px` spacing step | `2x` → `16px` |
308
+ | `r` | `6px` radius | `1r` → `6px` |
309
+ | `cr` | `10px` card radius | `1cr` → `10px` |
310
+ | `bw` | `1px` border width | `2bw` → `2px` |
311
+ | `ow` | `3px` outline width | `1ow` → `3px` |
312
+ | `sf` | stable fraction | `1sf` → `minmax(0, 1fr)` |
217
313
 
218
- Every style property accepts a state mapping object. Keys can be combined with boolean logic:
314
+ Units compile to plain values. Override them with `configure({ units })`. When the scale has to change at runtime, point a unit at a custom property and define it as a token: `units: { x: 'var(--gap)' }` with `tokens: { $gap: '8px' }` makes `2x` compile to `calc(2 * var(--gap))`. Parentheses become `calc()`: `width: '(100% - 4x)'`.
219
315
 
220
- | State type | Syntax | CSS output |
221
- |------------|--------|------------|
222
- | Data attribute (boolean modifier) | `disabled` | `[data-disabled]` |
223
- | Data attribute (value modifier) | `theme=danger` | `[data-theme="danger"]` |
224
- | Pseudo-class | `:hover` | `:hover` |
225
- | Attribute selector | `[role="tab"]` | `[role="tab"]` |
226
- | Class selector (supported) | `.is-active` | `.is-active` |
227
- | Media query | `@media(w < 768px)` | `@media (width < 768px)` |
228
- | Container query | `@(panel, w >= 300px)` | `@container panel (width >= 300px)` |
229
- | Root state | `@root(theme=dark)` | `:root[data-theme="dark"]` |
230
- | Parent state | `@parent(theme=danger)` | `:is([data-theme="danger"] *)` |
231
- | Feature query | `@supports(display: grid)` | `@supports (display: grid)` |
232
- | Entry animation | `@starting` | `@starting-style` |
316
+ ### Recipes
233
317
 
234
- Combine with `&` (AND), `|` (OR), `!` (NOT):
318
+ Recipes are reusable style bundles, state maps included:
235
319
 
236
320
  ```tsx
237
- fill: {
238
- '': '#surface',
239
- 'theme=danger & :hover': '#danger-hover',
240
- '[aria-selected="true"]': '#accent-subtle',
241
- }
321
+ configure({
322
+ recipes: {
323
+ card: { padding: '4x', fill: '#surface', radius: '1r', border: true },
324
+ elevated: { shadow: '0 1x 3x #shadow' },
325
+ },
326
+ });
327
+
328
+ const Panel = tasty({ styles: { recipe: 'card elevated', gap: '2x' } });
242
329
  ```
243
330
 
244
- ### Sub-Element Styling
331
+ Local styles merge over recipes with the same extend/replace rules as component extension. Recipes listed after `/` apply after local styles, and `none` skips base recipes. See [Style DSL — Recipes](docs/dsl.md#recipes).
332
+
333
+ For token vocabularies, state aliases, layout primitives, and override contracts, see [Building a Design System](docs/design-system.md). To add your own style properties, units, functions, or component props, see [Plugins & Extension Points](docs/plugins.md).
334
+
335
+ ## Component API
245
336
 
246
- Style inner elements from the parent component definition. No extra components, no CSS leakage:
337
+ A Tasty component's public API is something you design. Instead of handing consumers an open `styles` escape hatch, expose the knobs they need as typed props.
338
+
339
+ ### Style props
340
+
341
+ `styleProps` turns selected style properties into props. They accept state maps, so responsive values use the same syntax:
247
342
 
248
343
  ```tsx
249
- const Card = tasty({
250
- styles: {
251
- padding: '4x',
252
- Title: { preset: 'h3', color: '#primary' },
253
- Content: { color: '#text', preset: 't2' },
254
- },
255
- elements: { Title: 'h2', Content: 'div' },
344
+ import { tasty, FLOW_STYLES } from '@tenphi/tasty';
345
+
346
+ const Space = tasty({
347
+ styles: { display: 'flex', flow: 'column', gap: '1x' },
348
+ styleProps: FLOW_STYLES,
256
349
  });
257
350
 
258
- <Card>
259
- <Card.Title>Heading</Card.Title>
260
- <Card.Content>Body text</Card.Content>
261
- </Card>
351
+ <Space flow="row" gap={{ '': '2x', '@mobile': '1x' }} />;
262
352
  ```
263
353
 
264
- Sub-elements use `data-element` attributes — no extra class names, no naming conventions.
354
+ ### Mod props
355
+
356
+ `modProps` exposes modifiers as typed props (`<Button isLoading size="lg">`, as in the [Quick Start](#3-add-states-and-a-typed-api)). Use `Boolean`, `String`, `Number`, or a `const` array of allowed values.
265
357
 
266
- By default, sub-elements participate in the same state context as the root component. That means mappings like `:hover`, `theme=danger`, `[role="button"]`, and other keys are evaluated as one unified block, which keeps styling logic predictable across the whole markup tree.
358
+ ### Token props
267
359
 
268
- Use `@own(...)` when a sub-element should react to its own state instead of the root state context.
360
+ `tokenProps` handles values that change often, such as progress, user-picked colors, or measured sizes. They set custom properties inline, so the class name and the CSS stay the same while the value changes:
269
361
 
270
- Class selectors are also supported, but modifiers/pseudo-classes are usually the better default in design-system code.
362
+ ```tsx
363
+ const Progress = tasty({
364
+ tokenProps: ['progress', 'accentColor'] as const,
365
+ styles: { width: '$progress', fill: '#accent' },
366
+ });
271
367
 
272
- Use the sub-element selector `$` when you need precise descendant targeting to avoid leakage in deeply nested component trees.
368
+ <Progress progress="75%" accentColor="#purple" />;
369
+ // style="--progress:75%;--accent-color:var(--purple-color)"
370
+ ```
273
371
 
274
372
  ### Variants
275
373
 
276
- Variants are designed to keep single-component CSS lean. Instead of generating dozens of static button classes up front, define all versions once and let runtime usage decide what CSS is actually emitted.
374
+ Variants are named style sets. `variant` is typed from their keys, `default` applies when no `variant` is passed, and only the variants actually rendered emit CSS:
277
375
 
278
376
  ```tsx
279
- const Button = tasty({
280
- styles: { padding: '2x 4x', radius: '1r' },
377
+ const Badge = tasty({
378
+ styles: { padding: '.5x 1x', radius: '1r' },
281
379
  variants: {
282
- default: { fill: '#primary', color: '#on-primary' },
380
+ default: { fill: '#surface', color: '#text' },
283
381
  danger: { fill: '#danger', color: '#on-danger' },
284
- outline: { fill: 'transparent', border: '1bw solid #primary' },
285
382
  },
286
383
  });
287
384
 
288
- <Button variant="danger">Delete</Button>
385
+ <Badge variant="danger">Failed</Badge>;
289
386
  ```
290
387
 
291
- ### Recipes
388
+ ### Sub-elements
292
389
 
293
- Recipes are predefined style sets that work like composable styling classes for Tasty. They can be pre-applied or post-applied to current styles, which lets you add reusable state logic while still allowing local style overrides.
390
+ Compound components style their inner parts from one definition. `elements` produces typed sub-components:
294
391
 
295
392
  ```tsx
296
- configure({
297
- recipes: {
298
- card: { padding: '4x', fill: '#surface', radius: '1r', border: true },
299
- elevated: { shadow: '0 2x 4x #shadow' },
300
- },
301
- });
302
-
303
- const ProfileCard = tasty({
393
+ const Card = tasty({
304
394
  styles: {
305
- recipe: 'card elevated',
306
- color: '#text',
395
+ padding: '4x',
396
+ Title: { preset: 'h3', color: { '': '#text', ':hover': '#primary' } },
397
+ Content: { color: '#text' },
307
398
  },
399
+ elements: { Title: 'h3', Content: 'div' },
308
400
  });
401
+
402
+ <Card>
403
+ <Card.Title>Revenue</Card.Title>
404
+ <Card.Content>Up 12% this week</Card.Content>
405
+ </Card>;
309
406
  ```
310
407
 
311
- Use `/` to post-apply recipes after local styles when you need recipe states/styles to win the final merge order. Use `none` to skip base recipes: `recipe: 'none / disabled'`.
408
+ Sub-elements share the root's state context: `:hover` inside `Title` means "when the card is hovered". Use `@own(...)` when a part should react to its own state, and the `$` selector affix for precise descendant targeting.
409
+
410
+ All of these are covered in [React API](docs/react-api.md). For guidance on what to expose and when, see [Methodology](docs/methodology.md).
312
411
 
313
- ### Keyframes and `@property`
412
+ ## Types and Linting
314
413
 
315
- Modern CSS features are natively supported:
414
+ ### TypeScript
316
415
 
317
- Color tokens are automatically registered as typed properties (`<color>`), so token-based transitions work without extra setup.
416
+ Component props are typed from their definitions: `modProps` value lists become unions, `variant` is typed from the variant keys, style props are typed like the styles they expose, and sub-elements are typed components. Register your design system's names to get autocomplete inside style values too:
417
+
418
+ ```ts
419
+ // tasty.d.ts
420
+ import '@tenphi/tasty';
421
+
422
+ declare module '@tenphi/tasty' {
423
+ // '#surface', '#primary.50' in fill, color, and other color props
424
+ interface TastyNamedColors {
425
+ surface: true;
426
+ primary: true;
427
+ }
428
+ // preset: 'h1'
429
+ interface TastyPresetNames {
430
+ h1: true;
431
+ t2: true;
432
+ }
433
+ // <Button theme="danger" />
434
+ interface TastyThemeNames {
435
+ danger: true;
436
+ }
437
+ }
438
+ ```
439
+
440
+ `StylesInterface`, `TastyCustomProps`, and `TastyBaseStylePropNames` type the style properties, component props, and base style props your plugins add. See [Configuration → TypeScript](docs/configuration.md#typescript).
441
+
442
+ ### ESLint and oxlint
443
+
444
+ [`@tenphi/eslint-plugin-tasty`](https://github.com/tenphi/eslint-plugin-tasty) validates style objects and JSX style props at lint time:
445
+
446
+ ```bash
447
+ pnpm add -D @tenphi/eslint-plugin-tasty
448
+ ```
449
+
450
+ ```js
451
+ // eslint.config.js
452
+ import tasty from '@tenphi/eslint-plugin-tasty';
453
+
454
+ export default [tasty.configs.recommended]; // or tasty.configs.strict
455
+ ```
456
+
457
+ A `tasty.config.ts` at the project root tells the plugin what your design system defines:
458
+
459
+ ```ts
460
+ // tasty.config.ts
461
+ export default {
462
+ tokens: ['#surface', '#text', '#primary', '$sidebar-width'],
463
+ states: ['@mobile', '@dark'],
464
+ presets: ['h1', 'h2', 't1', 't2'],
465
+ recipes: ['card', 'elevated'],
466
+ importSources: ['@my-org/ui'], // modules that re-export tasty()
467
+ };
468
+ ```
469
+
470
+ With that in place, `recommended` catches:
471
+
472
+ - Typos in property names (checked against the full CSS property set) and malformed values
473
+ - Unknown color tokens, units, presets, and recipes (plus `$` tokens and state aliases in `strict`)
474
+ - Invalid state keys, missing `''` defaults, nested state maps, and `!important`
475
+ - Raw colors and pixel lengths where tokens belong, and non-idiomatic syntax such as `calc(...)` or `display: 'none'`, some with autofixes
476
+ - `style`, `className`, and instance `styles` on Tasty components, in favor of their typed API
477
+ - Bad values in JSX style props, such as `<Box fill="red" gap="17px" />`
478
+
479
+ The same rule sets run under oxlint. See the [plugin README](https://github.com/tenphi/eslint-plugin-tasty) for every rule and option.
480
+
481
+ ## CSS Coverage
482
+
483
+ Every style property accepts a state map, and every kind of condition is a state key:
484
+
485
+ | State type | Syntax | CSS output |
486
+ | ---------------- | -------------------------- | ----------------------------------- |
487
+ | Boolean modifier | `loading` | `[data-loading]` |
488
+ | Value modifier | `theme=danger` | `[data-theme="danger"]` |
489
+ | Pseudo-class | `:hover` | `:hover` |
490
+ | Attribute | `[aria-selected="true"]` | `[aria-selected="true"]` |
491
+ | Class | `.is-active` | `.is-active` |
492
+ | Media query | `@media(w < 768px)` | `@media (width < 768px)` |
493
+ | Container query | `@(panel, w >= 300px)` | `@container panel (width >= 300px)` |
494
+ | Root state | `@root(schema=dark)` | `:root[data-schema="dark"]` |
495
+ | Parent state | `@parent(theme=danger)` | `:is([data-theme="danger"] *)` |
496
+ | Structural | `:has(> Icon)` | `:has(> [data-element="Icon"])` |
497
+ | Feature query | `@supports(display: grid)` | `@supports (display: grid)` |
498
+ | Entry animation | `@starting` | `@starting-style` |
499
+
500
+ Combine them with `&` (and), `|` (or), `!` (not), and `^` (xor): `'theme=danger & :hover'`. The `_` key sets a fallback floor for queries a browser may evaluate as unknown. See [Style DSL](docs/dsl.md).
501
+
502
+ At-rules live next to the styles that use them:
318
503
 
319
504
  ```tsx
320
- const Pulse = tasty({
505
+ const Spinner = tasty({
321
506
  styles: {
322
- '@properties': {
323
- '$pulse-scale': {
324
- syntax: '<number>',
325
- inherits: false,
326
- initialValue: 1,
327
- },
328
- },
329
- animation: 'pulse 2s infinite',
330
- transform: 'scale($pulse-scale)',
331
- '@keyframes': {
332
- pulse: {
333
- '0%, 100%': { '$pulse-scale': 1 },
334
- '50%': { '$pulse-scale': 1.05 },
335
- },
336
- },
507
+ '@keyframes': { spin: { to: { transform: 'rotate(360deg)' } } },
508
+ animation: 'spin 1s linear infinite',
337
509
  },
338
510
  });
339
511
  ```
340
512
 
341
- ### React Hooks
513
+ - **`@keyframes`**: inline, as above, or global via `configure({ keyframes })`.
514
+ - **`@property`**: inferred automatically when a custom property gets a concrete value, so custom properties can transition and animate with no boilerplate. Declare one explicitly to change `inherits` or the initial value.
515
+ - **`@font-face`** and **`@counter-style`**: in styles, in `configure()`, or with the matching style function.
516
+ - **`@function`**: define parameterized CSS functions as `$$name` and call them anywhere a value is accepted. An opt-in polyfill inlines the calls for browsers that do not support it yet.
342
517
 
343
- For cases where you don't need a full component:
518
+ Tasty also ships its own style properties (`fill`, `flow`, `radius`, `preset`, `fade`, `scrollbar`, `hide`, and more), documented in [Style Properties](docs/styles.md). `@layer` and `!important` are intentionally left out: they would bring back the priority fights that state maps remove.
344
519
 
345
- ```tsx
346
- import { useStyles, useGlobalStyles, useRawCSS } from '@tenphi/tasty';
520
+ ### Style functions
347
521
 
348
- function App() {
349
- const { className } = useStyles({ padding: '2x', fill: '#surface' });
350
- useGlobalStyles(':root', { '#primary': 'purple', '$gap': '8px' });
351
- useRawCSS('body { margin: 0; }');
522
+ When you don't need a component, call the style functions directly: `useStyles` for a class name, `useGlobalStyles` for selector-scoped global CSS, `useRawCSS` for raw rules, plus `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`, and `useFunction`. Apart from `useStyles`, which reads React context, they are plain functions despite the `use` prefix, so they work in React Server Components and outside React; `computeStyles` is the hook-free equivalent of `useStyles`. See [React API — Style Functions](docs/react-api.md#style-functions).
352
523
 
353
- return <main className={className}>...</main>;
354
- }
355
- ```
524
+ ## Rendering Modes
356
525
 
357
- ### Zero-Runtime Mode
526
+ The DSL, tokens, units, state maps, and recipes are the same everywhere. What changes is where CSS is generated:
358
527
 
359
- Extract all CSS at build time. Zero JavaScript overhead in production:
528
+ | Approach | Authoring API | CSS is generated | Tasty runtime in the browser | Best for |
529
+ | ------------------------- | ------------------------------------------- | ----------------------------------- | ---------------------------- | ---------------------------------------------------------------- |
530
+ | **Server-only React** | `tasty()` plus `@tenphi/tasty/ssr/*` | During server or static rendering | None | Astro without islands, server-only RSC, SSG |
531
+ | **Hydrated React** | `tasty()` plus `@tenphi/tasty/ssr/*` | During SSR, then on demand in React | Only in hydrated components | Interactive React apps, Next.js client components, Astro islands |
532
+ | **Build-time extraction** | `tastyStatic()` from `@tenphi/tasty/static` | During the build | None in file mode | Non-React frameworks, CSS needed before rendering |
360
533
 
361
- ```tsx
362
- import { tastyStatic } from '@tenphi/tasty/static';
534
+ - **`tasty()` renders on the server.** Components are hook-free and render as React Server Components by default. Rendered only on the server, they keep the full React API (`styleProps`, sub-elements, variants) and ship no styling runtime. Astro's `tastyIntegration({ islands: false })` is the verified zero-JavaScript setup; server-only Next.js RSC follows the same architecture. Without an integration, each element inlines its own CSS and configured tokens and global styles are not emitted, so use one for anything that relies on `configure()`.
535
+ - **SSR integrations** add CSS batching, cross-tree deduplication, FOUC prevention, and client cache hydration for apps that also render on the client: `TastyRegistry` from `@tenphi/tasty/ssr/next` (plus `withTastyNext()` from `@tenphi/tasty/ssr/next-config` for a shared global stylesheet), `tastyIntegration()` from `@tenphi/tasty/ssr/astro`, and a core API for other React SSR setups. Streaming is supported.
536
+ - **`tastyStatic()`** returns class names and extracts CSS during the build through a Babel plugin, with no React dependency. File mode ships no runtime; inject mode ships a tiny CSS injector.
537
+
538
+ See [Server-Side Rendering](docs/ssr.md), [Build-Time Extraction](docs/tasty-static.md), and [Choosing a rendering mode](docs/getting-started.md#choosing-a-rendering-mode).
539
+
540
+ ## Tooling
541
+
542
+ ### ESLint plugin
543
+
544
+ Covered in [Types and Linting](#eslint-and-oxlint): 40+ rules for ESLint 9+ and oxlint, configured from `tasty.config.ts`.
545
+
546
+ ### VS Code extension
547
+
548
+ [Tasty for VS Code](https://github.com/tenphi/tasty-vscode-extension) highlights color tokens, units, state keys, presets, and style properties inside `tasty()`, `tastyStatic()`, `styles` objects, and style functions, and adds snippets for components, state maps, and `configure()`. Install the `.vsix` from the [latest release](https://github.com/tenphi/tasty-vscode-extension/releases/latest).
549
+
550
+ <p align="center">
551
+ <img src="assets/tasty-vscode-highlight.png" width="512" alt="Tasty VS Code syntax highlighting example" />
552
+ </p>
363
553
 
364
- const card = tastyStatic({
365
- padding: '4x',
366
- fill: '#surface',
367
- radius: '1r',
368
- color: { '': '#text', '@dark': '#text-on-dark' },
554
+ ### Glaze
555
+
556
+ [`@tenphi/glaze`](https://github.com/tenphi/glaze) generates light, dark, and high-contrast themes from one hue in the OKHST color space, solves WCAG and APCA contrast, and exports Tasty tokens directly (as well as CSS, DTCG, and Tailwind):
557
+
558
+ ```ts
559
+ import { glaze } from '@tenphi/glaze';
560
+ import { configure } from '@tenphi/tasty';
561
+
562
+ const theme = glaze(280, 80); // hue, saturation
563
+ theme.colors({
564
+ surface: { tone: 97, saturation: 0.15 },
565
+ text: { base: 'surface', tone: '-1', contrast: 'AA' },
369
566
  });
370
567
 
371
- // card is a CSS class name string
372
- <div className={card}>Static styles, zero runtime</div>
568
+ configure({ tokens: theme.tasty() }); // #surface and #text, light and dark
373
569
  ```
374
570
 
375
- Configure the Babel plugin:
571
+ ### Debugging
376
572
 
377
- ```js
378
- module.exports = {
379
- plugins: [
380
- ['@tenphi/tasty/babel-plugin', {
381
- output: 'public/tasty.css',
382
- config: {
383
- states: { '@dark': '@root(theme=dark)' },
384
- },
385
- }],
386
- ],
387
- };
573
+ `tastyDebug` inspects what Tasty injected, straight from the browser console:
574
+
575
+ ```ts
576
+ import { tastyDebug } from '@tenphi/tasty';
577
+
578
+ tastyDebug.install(); // exposes window.tastyDebug
579
+ tastyDebug.inspect('.my-button'); // classes, chunks, and the CSS that applies
388
580
  ```
389
581
 
390
- ### `tasty` vs `tastyStatic`
582
+ See [Debug Utilities](docs/debug.md).
391
583
 
392
- | | `tasty` (runtime) | `tastyStatic` (zero-runtime) |
393
- |---|---|---|
394
- | **Output** | React component | CSS class name |
395
- | **CSS injection** | Runtime `<style>` tags | Build-time extraction |
396
- | **Runtime cost** | Style generation on mount | None |
397
- | **Generated CSS scope** | Only styles/variants used at runtime | All extracted static styles at build time |
398
- | **Dynamic values** | Fully supported | Via CSS custom properties |
399
- | **Sub-elements** | Built-in (`<C.Title>`) | Manual (`data-element`) |
400
- | **Variants** | Built-in (`variants` option) | Separate static styles |
401
- | **Framework** | React | Any (requires Babel) |
402
- | **Best for** | Interactive apps, design systems | Static sites, SSG, landing pages |
584
+ ### AI coding agents
403
585
 
404
- Both share the same DSL, tokens, units, state mappings, and recipes.
586
+ Style definitions are declarative and fully typed, so agents can read and write them reliably. [Style Rules for AI Agents](docs/ai-agents.md) is a one-page brief to add to an agent's context.
405
587
 
406
- ### Runtime Performance
588
+ ## Performance
407
589
 
408
- If you choose the runtime approach, performance is usually a non-issue in practice:
590
+ ### Bundle size
409
591
 
410
- - CSS is generated and injected only when styles are actually used.
411
- - Multi-level caching avoids repeated parsing and style recomputation.
412
- - Styles are split into reusable chunks and applied as multiple class names, so matching chunks can be reused across components instead of re-injected.
413
- - Style normalization guarantees equivalent style input resolves to the same chunks, improving deduplication hit rates.
414
- - A style garbage collector removes unused styles/chunks over time.
415
- - A dedicated style injector minimizes DOM/style-tag overhead.
416
- - This approach is validated in enterprise-scale apps where runtime styling overhead is not noticeable in normal UI flows.
592
+ Measured with [size-limit](https://github.com/ai/size-limit): minified and brotli-compressed, including all dependencies.
417
593
 
418
- ## Entry Points
594
+ | Entry point | Size |
595
+ | ------------------------------------------------- | -------- |
596
+ | `@tenphi/tasty`, importing `tasty` | 50.79 kB |
597
+ | `@tenphi/tasty`, everything | 57.54 kB |
598
+ | `@tenphi/tasty/core`, everything (no SSR) | 54.45 kB |
599
+ | `@tenphi/tasty/static` (build-time authoring API) | 920 B |
600
+ | `@tenphi/tasty/ssr/astro-client` | 283 B |
601
+ | `@tenphi/tasty/zero` (Node, build time) | 37.57 kB |
602
+ | `@tenphi/tasty/babel-plugin` (Node, build time) | 41.65 kB |
419
603
 
420
- | Import | Description | Platform |
421
- |--------|-------------|----------|
422
- | `@tenphi/tasty` | Runtime style engine (`tasty`, hooks, `configure`) | Browser |
423
- | `@tenphi/tasty/static` | Zero-runtime static styles (`tastyStatic`) | Browser |
424
- | `@tenphi/tasty/core` | Lower-level internals (config, parser, pipeline, injector, style handlers) for tooling and advanced use | Browser / Node |
425
- | `@tenphi/tasty/babel-plugin` | Babel plugin for zero-runtime CSS extraction | Node |
426
- | `@tenphi/tasty/zero` | Programmatic extraction API | Node |
427
- | `@tenphi/tasty/next` | Next.js integration wrapper | Node |
604
+ Run `pnpm size` to reproduce; numbers shift slightly between releases.
428
605
 
429
- ## Ecosystem
606
+ ### Runtime
430
607
 
431
- Tasty is the core of a production-ready styling platform. These companion tools complete the picture:
608
+ When styles are generated in the browser:
432
609
 
433
- ### [ESLint Plugin](https://github.com/tenphi/eslint-plugin-tasty)
610
+ - CSS is generated and injected only for styles that are actually rendered.
611
+ - Styles are split into chunks and normalized, so equivalent styles resolve to the same classes and are reused across components instead of re-injected.
612
+ - Multi-level caching avoids re-parsing and recomputation.
613
+ - Unused styles can be garbage-collected over time with `configure({ gc })`, or on demand with `gc()` and `cleanup()`.
614
+ - Optional [batched injection](docs/configuration.md#batched-injection) groups stylesheet writes before layout reads.
615
+ - The approach runs in enterprise-scale apps where runtime styling overhead is not noticeable in normal UI flows.
434
616
 
435
- `@tenphi/eslint-plugin-tasty` — 27 lint rules that validate style property names, value syntax, token existence, state keys, and enforce best practices. Catch typos and invalid styles at lint time, not at runtime.
617
+ Reproducible benchmarks cover the core pipeline, the React overhead of an empty `tasty({})` wrapper, cold generation and injection against equivalent precompiled CSS, steady-state interactions, and page-load cold start. See [Runtime Benchmarks](docs/runtime-benchmarks.md) for methodology and results.
436
618
 
437
- ```bash
438
- pnpm add -D @tenphi/eslint-plugin-tasty
439
- ```
619
+ ## Browser Support
440
620
 
441
- ```js
442
- import tasty from '@tenphi/eslint-plugin-tasty';
443
- export default [tasty.configs.recommended];
444
- ```
621
+ Exclusive selectors rely on `:is()`, `:where()`, and level-4 `:not()` with selector lists: Chrome and Edge 88+, Firefox 84+, Safari 14+. Internet Explorer is not supported.
445
622
 
446
- ### [Glaze](https://github.com/tenphi/glaze)
623
+ ## Entry Points
447
624
 
448
- `@tenphi/glaze` — OKHSL-based color theme generator with automatic WCAG contrast solving. Generate light, dark, and high-contrast palettes from a single hue, and export them directly as Tasty color tokens.
625
+ | Import | Description | Platform |
626
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------- |
627
+ | `@tenphi/tasty` | React style engine (`tasty`, style functions, `configure`) | Browser / Node |
628
+ | `@tenphi/tasty/core` | Lower-level internals (config, parser, pipeline, injector, style handlers) for tooling and advanced use | Browser / Node |
629
+ | `@tenphi/tasty/static` | Build-time extraction authoring API (`tastyStatic`) | Build time |
630
+ | `@tenphi/tasty/static/inject` | Runtime helper the Babel plugin rewrites `tastyStatic` imports to in inject mode | Browser |
631
+ | `@tenphi/tasty/babel-plugin` | Babel plugin for build-time CSS extraction | Node |
632
+ | `@tenphi/tasty/zero` | Programmatic extraction API | Node |
633
+ | `@tenphi/tasty/zero/next` | Next.js wrapper for build-time extraction | Node |
634
+ | `@tenphi/tasty/ssr` | Core SSR API (collector, context, hydration) | Node |
635
+ | `@tenphi/tasty/ssr/next` | Next.js App Router SSR integration (`TastyRegistry`) | Node |
636
+ | `@tenphi/tasty/ssr/next-config` | Next.js config wrapper for a shared global stylesheet (`withTastyNext`) | Node |
637
+ | `@tenphi/tasty/ssr/astro` | Astro integration and middleware | Node |
638
+ | `@tenphi/tasty/ssr/astro-client` | Astro client-side cache hydration | Browser |
449
639
 
450
- ```tsx
451
- import { glaze } from '@tenphi/glaze';
640
+ ## Built with Tasty
452
641
 
453
- const theme = glaze(280, 80);
454
- theme.colors({
455
- surface: { lightness: 97 },
456
- text: { base: 'surface', lightness: '-52', contrast: 'AAA' },
457
- });
642
+ ### [tasty.style](https://tasty.style) ([source](https://github.com/tenphi/tasty.style))
458
643
 
459
- const tokens = theme.tasty(); // Ready-to-use Tasty tokens
460
- ```
644
+ The official Tasty documentation and landing page — itself built entirely with `tasty()`. A showcase for server-rendered Tasty with Next.js and OKHST color theming with Glaze.
461
645
 
462
- ### [VS Code Extension](https://github.com/tenphi/tasty-vscode-extension)
646
+ ### [Cookbook](https://cookbook.tenphi.me) ([source](https://github.com/tenphi/cookbook))
463
647
 
464
- Syntax highlighting for Tasty styles in TypeScript, TSX, JavaScript, and JSX. Highlights color tokens, custom units, state keys, presets, and style properties inside `tasty()`, `tastyStatic()`, and related APIs.
648
+ `@tenphi/cookbook` builds a static Astro documentation site from your repository or a published npm package. Brand colors, semantic palettes, fonts, typography, and individual component parts are configured through Tasty and Glaze, so the docs match your product.
465
649
 
466
- <p align="center">
467
- <img src="assets/tasty-vscode-highlight.png" width="512" alt="Tasty VS Code syntax highlighting example">
468
- </p>
650
+ ### [Cube Cloud](https://cube.dev/)
651
+
652
+ Enterprise universal semantic layer platform by Cube Dev, Inc. Cube Cloud unifies data modeling, caching, access control, and APIs (REST, GraphQL, SQL, AI) for analytics at scale. Tasty has powered its frontend for over 5 years in production.
653
+
654
+ ### [Cube Cloud for Excel and Google Sheets](https://cube.dev/)
469
655
 
470
- ### [Cube UI Kit](https://github.com/cube-js/cube-ui-kit)
656
+ A single spreadsheet add-in deployed to both [Microsoft Excel](https://marketplace.microsoft.com/en-us/product/office/WA200008486) and [Google Sheets](https://workspace.google.com/u/0/marketplace/app/cube_cloud_for_sheets/641460343379). Connects spreadsheets to any cloud data platform (BigQuery, Databricks, Snowflake, Redshift, and more) via Cube Cloud's universal semantic layer.
657
+
658
+ ### [Cube UI Kit](https://github.com/cube-js/cube-ui-kit) ([storybook](https://cube-ui-kit.vercel.app/))
471
659
 
472
660
  Open-source React UI kit built on Tasty + React Aria. 100+ production components proving Tasty works at design-system scale. A reference implementation and a ready-to-use component library.
473
661
 
474
662
  ## Documentation
475
663
 
476
- - **[Runtime API (tasty)](docs/tasty.md)** — Full runtime styling documentation: component creation, state mappings, sub-elements, variants, hooks, and configuration
477
- - **[Style Properties](docs/styles.md)** — Complete reference for all enhanced style properties: syntax, values, modifiers, and recommendations
478
- - **[Zero Runtime (tastyStatic)](docs/tasty-static.md)** — Build-time static styling: Babel plugin setup, Next.js integration, and static style patterns
479
- - **[Style Injector](docs/injector.md)** — Internal CSS injection engine: `inject()`, `injectGlobal()`, `injectRawCSS()`, `keyframes()`, deduplication, reference counting, cleanup, SSR support, and Shadow DOM
480
- - **[Debug Utilities](docs/debug.md)** — Runtime CSS inspection via `tastyDebug`: CSS extraction, element inspection, cache metrics, chunk breakdown, and performance monitoring
664
+ The [Docs Hub](docs/README.md) routes you by role, rendering mode, and task.
665
+
666
+ **Start here**
667
+
668
+ - [Getting Started](docs/getting-started.md) — Install, first component, shared configuration, ESLint, editor tooling, and choosing a rendering mode
669
+ - [Methodology](docs/methodology.md) — The recommended component model: root + sub-elements, `styleProps`, tokens, `styles` vs `style`, wrapping and extension
670
+
671
+ **Guides**
672
+
673
+ - [Building a Design System](docs/design-system.md) — Token vocabulary, state aliases, recipes, primitives, compound components, and override contracts
674
+ - [Adoption Guide](docs/adoption.md) — Where Tasty fits, who should adopt it, and how to introduce it incrementally
675
+ - [Comparison](docs/comparison.md) — Tasty next to Tailwind, Panda CSS, vanilla-extract, StyleX, Stitches, and Emotion
676
+
677
+ **Reference**
678
+
679
+ - [Style DSL](docs/dsl.md) — State maps, tokens, units, colors, extending semantics, recipes, and at-rules
680
+ - [Style Properties](docs/styles.md) — Every enhanced style property: syntax, values, and modifiers
681
+ - [React API](docs/react-api.md) — `tasty()`, component props, variants, sub-elements, and style functions
682
+ - [Configuration](docs/configuration.md) — Everything `configure()` accepts
683
+ - [Plugins & Extension Points](docs/plugins.md) — Custom style handlers, props middleware, and typing your extension
684
+ - [Style Rules for AI Agents](docs/ai-agents.md) — A condensed brief for writing correct styles
685
+
686
+ **Rendering modes**
687
+
688
+ - [Server-Side Rendering](docs/ssr.md) — Next.js, Astro, and generic React SSR: streaming, cache hydration, and troubleshooting
689
+ - [Build-Time Extraction (`tastyStatic`)](docs/tasty-static.md) — Babel plugin setup, Next.js integration, and static style patterns
690
+
691
+ **Internals**
692
+
693
+ - [Style rendering pipeline](docs/pipeline.md) — How state maps become mutually exclusive CSS rules
694
+ - [Style Injector](docs/injector.md) — Deduplication, reference counting, cleanup, SSR, and Shadow DOM
695
+ - [Debug Utilities](docs/debug.md) — Inspecting CSS, elements, caches, and chunks at runtime
696
+ - [Runtime Benchmarks](docs/runtime-benchmarks.md) — Methodology and results
697
+
698
+ **Upgrading**
699
+
700
+ - [Migration Guide (v2 → v3)](docs/migration-v3.md) — Every breaking change in 3.0, with a search-and-replace cheat sheet
481
701
 
482
702
  ## License
483
703