@tenphi/tasty 0.0.0-snapshot.c8bdaeb → 0.0.0-snapshot.cae4fee

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 (303) hide show
  1. package/README.md +314 -159
  2. package/dist/async-storage-B7_o6FKt.js +44 -0
  3. package/dist/async-storage-B7_o6FKt.js.map +1 -0
  4. package/dist/collector-C-keQH9m.js +243 -0
  5. package/dist/collector-C-keQH9m.js.map +1 -0
  6. package/dist/collector-osfWTeRd.d.ts +108 -0
  7. package/dist/config-BBiyxMCe.js +10559 -0
  8. package/dist/config-BBiyxMCe.js.map +1 -0
  9. package/dist/config-BoZDUHW5.d.ts +945 -0
  10. package/dist/context-CkSg-kDT.js +24 -0
  11. package/dist/context-CkSg-kDT.js.map +1 -0
  12. package/dist/core/index.d.ts +5 -33
  13. package/dist/core/index.js +6 -26
  14. package/dist/core-BO4319td.js +1598 -0
  15. package/dist/core-BO4319td.js.map +1 -0
  16. package/dist/css-writer-BWvwQzz0.js +351 -0
  17. package/dist/css-writer-BWvwQzz0.js.map +1 -0
  18. package/dist/format-global-rules-Dbc_1tc3.js +22 -0
  19. package/dist/format-global-rules-Dbc_1tc3.js.map +1 -0
  20. package/dist/format-rules-BSjeH4Z7.js +143 -0
  21. package/dist/format-rules-BSjeH4Z7.js.map +1 -0
  22. package/dist/hydrate-CcvrP4qJ.js +45 -0
  23. package/dist/hydrate-CcvrP4qJ.js.map +1 -0
  24. package/dist/index-B_k47mc_.d.ts +1655 -0
  25. package/dist/index-tcHuMPFt.d.ts +1286 -0
  26. package/dist/index.d.ts +5 -48
  27. package/dist/index.js +731 -32
  28. package/dist/index.js.map +1 -0
  29. package/dist/keyframes-BUQhdOSJ.js +588 -0
  30. package/dist/keyframes-BUQhdOSJ.js.map +1 -0
  31. package/dist/{utils/merge-styles.d.ts → merge-styles-BMWcH6MF.d.ts} +3 -3
  32. package/dist/{utils/merge-styles.js → merge-styles-Cd2vBl9b.js} +4 -6
  33. package/dist/merge-styles-Cd2vBl9b.js.map +1 -0
  34. package/dist/{utils/resolve-recipes.js → resolve-recipes-C1nrvnYh.js} +5 -8
  35. package/dist/resolve-recipes-C1nrvnYh.js.map +1 -0
  36. package/dist/ssr/astro-client.d.ts +1 -0
  37. package/dist/ssr/astro-client.js +19 -0
  38. package/dist/ssr/astro-client.js.map +1 -0
  39. package/dist/ssr/astro-middleware.d.ts +15 -0
  40. package/dist/ssr/astro-middleware.js +19 -0
  41. package/dist/ssr/astro-middleware.js.map +1 -0
  42. package/dist/ssr/astro.d.ts +97 -0
  43. package/dist/ssr/astro.js +149 -0
  44. package/dist/ssr/astro.js.map +1 -0
  45. package/dist/ssr/index.d.ts +44 -0
  46. package/dist/ssr/index.js +10 -0
  47. package/dist/ssr/index.js.map +1 -0
  48. package/dist/ssr/next.d.ts +46 -0
  49. package/dist/ssr/next.js +75 -0
  50. package/dist/ssr/next.js.map +1 -0
  51. package/dist/static/index.d.ts +91 -5
  52. package/dist/static/index.js +49 -4
  53. package/dist/static/index.js.map +1 -0
  54. package/dist/static/inject.d.ts +5 -0
  55. package/dist/static/inject.js +17 -0
  56. package/dist/static/inject.js.map +1 -0
  57. package/dist/zero/babel.d.ts +57 -84
  58. package/dist/zero/babel.js +232 -40
  59. package/dist/zero/babel.js.map +1 -1
  60. package/dist/zero/index.d.ts +67 -3
  61. package/dist/zero/index.js +2 -4
  62. package/dist/zero/next.d.ts +56 -30
  63. package/dist/zero/next.js +105 -40
  64. package/dist/zero/next.js.map +1 -1
  65. package/docs/README.md +31 -0
  66. package/docs/adoption.md +298 -0
  67. package/docs/comparison.md +419 -0
  68. package/docs/configuration.md +438 -0
  69. package/docs/debug.md +320 -0
  70. package/docs/design-system.md +436 -0
  71. package/docs/dsl.md +690 -0
  72. package/docs/getting-started.md +217 -0
  73. package/docs/injector.md +544 -0
  74. package/docs/methodology.md +642 -0
  75. package/docs/pipeline.md +699 -0
  76. package/docs/react-api.md +581 -0
  77. package/docs/ssr.md +444 -0
  78. package/docs/styles.md +598 -0
  79. package/docs/tasty-static.md +547 -0
  80. package/package.json +70 -37
  81. package/tasty.config.ts +1 -0
  82. package/dist/_virtual/_rolldown/runtime.js +0 -8
  83. package/dist/chunks/cacheKey.js +0 -70
  84. package/dist/chunks/cacheKey.js.map +0 -1
  85. package/dist/chunks/definitions.d.ts +0 -37
  86. package/dist/chunks/definitions.js +0 -260
  87. package/dist/chunks/definitions.js.map +0 -1
  88. package/dist/chunks/renderChunk.js +0 -61
  89. package/dist/chunks/renderChunk.js.map +0 -1
  90. package/dist/config.d.ts +0 -280
  91. package/dist/config.js +0 -403
  92. package/dist/config.js.map +0 -1
  93. package/dist/debug.d.ts +0 -204
  94. package/dist/debug.js +0 -733
  95. package/dist/debug.js.map +0 -1
  96. package/dist/hooks/useGlobalStyles.d.ts +0 -27
  97. package/dist/hooks/useGlobalStyles.js +0 -56
  98. package/dist/hooks/useGlobalStyles.js.map +0 -1
  99. package/dist/hooks/useKeyframes.d.ts +0 -56
  100. package/dist/hooks/useKeyframes.js +0 -54
  101. package/dist/hooks/useKeyframes.js.map +0 -1
  102. package/dist/hooks/useProperty.d.ts +0 -79
  103. package/dist/hooks/useProperty.js +0 -91
  104. package/dist/hooks/useProperty.js.map +0 -1
  105. package/dist/hooks/useRawCSS.d.ts +0 -53
  106. package/dist/hooks/useRawCSS.js +0 -28
  107. package/dist/hooks/useRawCSS.js.map +0 -1
  108. package/dist/hooks/useStyles.d.ts +0 -40
  109. package/dist/hooks/useStyles.js +0 -169
  110. package/dist/hooks/useStyles.js.map +0 -1
  111. package/dist/injector/index.d.ts +0 -157
  112. package/dist/injector/index.js +0 -154
  113. package/dist/injector/index.js.map +0 -1
  114. package/dist/injector/injector.d.ts +0 -139
  115. package/dist/injector/injector.js +0 -404
  116. package/dist/injector/injector.js.map +0 -1
  117. package/dist/injector/sheet-manager.d.ts +0 -127
  118. package/dist/injector/sheet-manager.js +0 -714
  119. package/dist/injector/sheet-manager.js.map +0 -1
  120. package/dist/injector/types.d.ts +0 -135
  121. package/dist/keyframes/index.js +0 -206
  122. package/dist/keyframes/index.js.map +0 -1
  123. package/dist/parser/classify.js +0 -319
  124. package/dist/parser/classify.js.map +0 -1
  125. package/dist/parser/const.js +0 -33
  126. package/dist/parser/const.js.map +0 -1
  127. package/dist/parser/lru.js +0 -109
  128. package/dist/parser/lru.js.map +0 -1
  129. package/dist/parser/parser.d.ts +0 -25
  130. package/dist/parser/parser.js +0 -116
  131. package/dist/parser/parser.js.map +0 -1
  132. package/dist/parser/tokenizer.js +0 -69
  133. package/dist/parser/tokenizer.js.map +0 -1
  134. package/dist/parser/types.d.ts +0 -51
  135. package/dist/parser/types.js +0 -46
  136. package/dist/parser/types.js.map +0 -1
  137. package/dist/pipeline/conditions.d.ts +0 -134
  138. package/dist/pipeline/conditions.js +0 -406
  139. package/dist/pipeline/conditions.js.map +0 -1
  140. package/dist/pipeline/exclusive.js +0 -231
  141. package/dist/pipeline/exclusive.js.map +0 -1
  142. package/dist/pipeline/index.d.ts +0 -53
  143. package/dist/pipeline/index.js +0 -660
  144. package/dist/pipeline/index.js.map +0 -1
  145. package/dist/pipeline/materialize.js +0 -844
  146. package/dist/pipeline/materialize.js.map +0 -1
  147. package/dist/pipeline/parseStateKey.d.ts +0 -15
  148. package/dist/pipeline/parseStateKey.js +0 -438
  149. package/dist/pipeline/parseStateKey.js.map +0 -1
  150. package/dist/pipeline/simplify.js +0 -516
  151. package/dist/pipeline/simplify.js.map +0 -1
  152. package/dist/pipeline/warnings.js +0 -18
  153. package/dist/pipeline/warnings.js.map +0 -1
  154. package/dist/plugins/okhsl-plugin.d.ts +0 -35
  155. package/dist/plugins/okhsl-plugin.js +0 -371
  156. package/dist/plugins/okhsl-plugin.js.map +0 -1
  157. package/dist/plugins/types.d.ts +0 -69
  158. package/dist/properties/index.js +0 -158
  159. package/dist/properties/index.js.map +0 -1
  160. package/dist/states/index.d.ts +0 -49
  161. package/dist/states/index.js +0 -416
  162. package/dist/states/index.js.map +0 -1
  163. package/dist/static/tastyStatic.d.ts +0 -46
  164. package/dist/static/tastyStatic.js +0 -31
  165. package/dist/static/tastyStatic.js.map +0 -1
  166. package/dist/static/types.d.ts +0 -49
  167. package/dist/static/types.js +0 -24
  168. package/dist/static/types.js.map +0 -1
  169. package/dist/styles/align.d.ts +0 -15
  170. package/dist/styles/align.js +0 -14
  171. package/dist/styles/align.js.map +0 -1
  172. package/dist/styles/border.d.ts +0 -25
  173. package/dist/styles/border.js +0 -114
  174. package/dist/styles/border.js.map +0 -1
  175. package/dist/styles/color.d.ts +0 -14
  176. package/dist/styles/color.js +0 -23
  177. package/dist/styles/color.js.map +0 -1
  178. package/dist/styles/createStyle.js +0 -77
  179. package/dist/styles/createStyle.js.map +0 -1
  180. package/dist/styles/dimension.js +0 -97
  181. package/dist/styles/dimension.js.map +0 -1
  182. package/dist/styles/display.d.ts +0 -37
  183. package/dist/styles/display.js +0 -67
  184. package/dist/styles/display.js.map +0 -1
  185. package/dist/styles/fade.d.ts +0 -15
  186. package/dist/styles/fade.js +0 -58
  187. package/dist/styles/fade.js.map +0 -1
  188. package/dist/styles/fill.d.ts +0 -42
  189. package/dist/styles/fill.js +0 -51
  190. package/dist/styles/fill.js.map +0 -1
  191. package/dist/styles/flow.d.ts +0 -16
  192. package/dist/styles/flow.js +0 -12
  193. package/dist/styles/flow.js.map +0 -1
  194. package/dist/styles/gap.d.ts +0 -31
  195. package/dist/styles/gap.js +0 -37
  196. package/dist/styles/gap.js.map +0 -1
  197. package/dist/styles/height.d.ts +0 -17
  198. package/dist/styles/height.js +0 -20
  199. package/dist/styles/height.js.map +0 -1
  200. package/dist/styles/index.d.ts +0 -2
  201. package/dist/styles/index.js +0 -9
  202. package/dist/styles/index.js.map +0 -1
  203. package/dist/styles/inset.d.ts +0 -52
  204. package/dist/styles/inset.js +0 -150
  205. package/dist/styles/inset.js.map +0 -1
  206. package/dist/styles/justify.d.ts +0 -15
  207. package/dist/styles/justify.js +0 -14
  208. package/dist/styles/justify.js.map +0 -1
  209. package/dist/styles/list.d.ts +0 -16
  210. package/dist/styles/list.js +0 -98
  211. package/dist/styles/list.js.map +0 -1
  212. package/dist/styles/margin.d.ts +0 -24
  213. package/dist/styles/margin.js +0 -104
  214. package/dist/styles/margin.js.map +0 -1
  215. package/dist/styles/outline.d.ts +0 -29
  216. package/dist/styles/outline.js +0 -65
  217. package/dist/styles/outline.js.map +0 -1
  218. package/dist/styles/padding.d.ts +0 -24
  219. package/dist/styles/padding.js +0 -104
  220. package/dist/styles/padding.js.map +0 -1
  221. package/dist/styles/predefined.d.ts +0 -73
  222. package/dist/styles/predefined.js +0 -241
  223. package/dist/styles/predefined.js.map +0 -1
  224. package/dist/styles/preset.d.ts +0 -47
  225. package/dist/styles/preset.js +0 -126
  226. package/dist/styles/preset.js.map +0 -1
  227. package/dist/styles/radius.d.ts +0 -14
  228. package/dist/styles/radius.js +0 -51
  229. package/dist/styles/radius.js.map +0 -1
  230. package/dist/styles/scrollbar.d.ts +0 -21
  231. package/dist/styles/scrollbar.js +0 -112
  232. package/dist/styles/scrollbar.js.map +0 -1
  233. package/dist/styles/shadow.d.ts +0 -14
  234. package/dist/styles/shadow.js +0 -24
  235. package/dist/styles/shadow.js.map +0 -1
  236. package/dist/styles/styledScrollbar.d.ts +0 -47
  237. package/dist/styles/styledScrollbar.js +0 -38
  238. package/dist/styles/styledScrollbar.js.map +0 -1
  239. package/dist/styles/transition.d.ts +0 -14
  240. package/dist/styles/transition.js +0 -158
  241. package/dist/styles/transition.js.map +0 -1
  242. package/dist/styles/types.d.ts +0 -498
  243. package/dist/styles/width.d.ts +0 -17
  244. package/dist/styles/width.js +0 -20
  245. package/dist/styles/width.js.map +0 -1
  246. package/dist/tasty.d.ts +0 -982
  247. package/dist/tasty.js +0 -206
  248. package/dist/tasty.js.map +0 -1
  249. package/dist/tokens/typography.d.ts +0 -19
  250. package/dist/tokens/typography.js +0 -237
  251. package/dist/tokens/typography.js.map +0 -1
  252. package/dist/types.d.ts +0 -184
  253. package/dist/utils/cache-wrapper.js +0 -26
  254. package/dist/utils/cache-wrapper.js.map +0 -1
  255. package/dist/utils/case-converter.js +0 -8
  256. package/dist/utils/case-converter.js.map +0 -1
  257. package/dist/utils/colors.d.ts +0 -5
  258. package/dist/utils/colors.js +0 -9
  259. package/dist/utils/colors.js.map +0 -1
  260. package/dist/utils/css-types.d.ts +0 -7
  261. package/dist/utils/dotize.d.ts +0 -26
  262. package/dist/utils/dotize.js +0 -122
  263. package/dist/utils/dotize.js.map +0 -1
  264. package/dist/utils/filter-base-props.d.ts +0 -15
  265. package/dist/utils/filter-base-props.js +0 -45
  266. package/dist/utils/filter-base-props.js.map +0 -1
  267. package/dist/utils/get-display-name.d.ts +0 -7
  268. package/dist/utils/get-display-name.js +0 -10
  269. package/dist/utils/get-display-name.js.map +0 -1
  270. package/dist/utils/hsl-to-rgb.js +0 -38
  271. package/dist/utils/hsl-to-rgb.js.map +0 -1
  272. package/dist/utils/is-dev-env.js +0 -19
  273. package/dist/utils/is-dev-env.js.map +0 -1
  274. package/dist/utils/is-valid-element-type.js +0 -15
  275. package/dist/utils/is-valid-element-type.js.map +0 -1
  276. package/dist/utils/merge-styles.js.map +0 -1
  277. package/dist/utils/mod-attrs.d.ts +0 -8
  278. package/dist/utils/mod-attrs.js +0 -21
  279. package/dist/utils/mod-attrs.js.map +0 -1
  280. package/dist/utils/okhsl-to-rgb.js +0 -296
  281. package/dist/utils/okhsl-to-rgb.js.map +0 -1
  282. package/dist/utils/process-tokens.d.ts +0 -31
  283. package/dist/utils/process-tokens.js +0 -171
  284. package/dist/utils/process-tokens.js.map +0 -1
  285. package/dist/utils/resolve-recipes.d.ts +0 -17
  286. package/dist/utils/resolve-recipes.js.map +0 -1
  287. package/dist/utils/string.js +0 -8
  288. package/dist/utils/string.js.map +0 -1
  289. package/dist/utils/styles.d.ts +0 -178
  290. package/dist/utils/styles.js +0 -590
  291. package/dist/utils/styles.js.map +0 -1
  292. package/dist/utils/typography.d.ts +0 -36
  293. package/dist/utils/typography.js +0 -53
  294. package/dist/utils/typography.js.map +0 -1
  295. package/dist/utils/warnings.d.ts +0 -16
  296. package/dist/utils/warnings.js +0 -16
  297. package/dist/utils/warnings.js.map +0 -1
  298. package/dist/zero/css-writer.d.ts +0 -45
  299. package/dist/zero/css-writer.js +0 -74
  300. package/dist/zero/css-writer.js.map +0 -1
  301. package/dist/zero/extractor.d.ts +0 -24
  302. package/dist/zero/extractor.js +0 -150
  303. package/dist/zero/extractor.js.map +0 -1
@@ -0,0 +1,581 @@
1
+ # React API
2
+
3
+ The React-specific `tasty()` component factory, component props, and style functions. All Tasty style functions — `tasty()` components, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `useCounterStyle()` — are hook-free and compatible with React Server Components. No `'use client'` directive needed. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
4
+
5
+ > **Note:** This file was previously named `runtime.md`. All functionality documented here works in both server and client contexts — "runtime" referred to style computation during React rendering, not to client-side JavaScript.
6
+
7
+ ---
8
+
9
+ ## Component Creation
10
+
11
+ ### Create a new component
12
+
13
+ ```jsx
14
+ import { tasty } from '@tenphi/tasty';
15
+
16
+ const Card = tasty({
17
+ as: 'div',
18
+ styles: {
19
+ padding: '4x',
20
+ fill: '#white',
21
+ border: true,
22
+ radius: true,
23
+ },
24
+ styleProps: ['padding', 'fill'],
25
+ });
26
+
27
+ <Card>Hello World</Card>
28
+ <Card padding="6x" fill="#gray.05">Custom Card</Card>
29
+ ```
30
+
31
+ ### Extend an existing component
32
+
33
+ ```jsx
34
+ const PrimaryButton = tasty(Button, {
35
+ styles: {
36
+ fill: '#purple',
37
+ color: '#white',
38
+ padding: '2x 4x',
39
+ },
40
+ });
41
+ ```
42
+
43
+ Style maps merge intelligently — see [Style DSL — Extending vs. Replacing State Maps](dsl.md#extending-vs-replacing-state-maps) for extend mode, replace mode, `@inherit`, `null`, and `false` tombstones.
44
+
45
+ `tasty(Component, ...)` always wraps `Component` and forwards every prop to it, so `Component` must be Tasty-aware (i.e. it accepts `styles`, `mods`, `qa`, etc. and renders them through its own Tasty pipeline). To apply styles to a third-party component or a string DOM tag via `className`, use the options-only form with `as`:
46
+
47
+ ```jsx
48
+ import NextLink from 'next/link';
49
+
50
+ const Link = tasty({
51
+ as: NextLink,
52
+ styles: {
53
+ color: { '': '#accent-text', ':hover': '#text' },
54
+ textDecoration: 'underline',
55
+ },
56
+ styleProps: ['padding'],
57
+ });
58
+
59
+ const Span = tasty({
60
+ as: 'span',
61
+ styles: { fontWeight: 'bold' },
62
+ });
63
+
64
+ <Link href="/blog" padding="1x">Blog</Link>;
65
+ ```
66
+
67
+ The wrapped component only needs to forward `className` (and ideally `style`/`ref`). Tasty-specific props (`qa`, `qaVal`, `mods`, `tokens`, `styleProps`, `modProps`, `tokenProps`) are consumed by Tasty and never leak to the DOM.
68
+
69
+ ---
70
+
71
+ ## Style Props
72
+
73
+ Use `styleProps` to expose style properties as direct component props:
74
+
75
+ ```jsx
76
+ const FlexibleBox = tasty({
77
+ as: 'div',
78
+ styles: {
79
+ display: 'flex',
80
+ padding: '2x',
81
+ },
82
+ styleProps: ['gap', 'align', 'placeContent', 'fill'],
83
+ });
84
+
85
+ <FlexibleBox gap="2x" align="center" fill="#surface">
86
+ Content
87
+ </FlexibleBox>
88
+ ```
89
+
90
+ Style props accept state maps, so responsive values work through the same API:
91
+
92
+ ```jsx
93
+ <FlexibleBox
94
+ gap={{ '': '2x', '@tablet': '4x' }}
95
+ fill={{ '': '#surface', '@dark': '#surface-dark' }}
96
+ >
97
+ ```
98
+
99
+ For predefined style prop lists (`FLOW_STYLES`, `POSITION_STYLES`, `DIMENSION_STYLES`, etc.) and guidance on which props to expose per component category, see [Methodology — styleProps as the public API](methodology.md#styleprops-as-the-public-api).
100
+
101
+ ---
102
+
103
+ ## Mod Props
104
+
105
+ Use `modProps` to expose modifier keys as direct component props instead of requiring the `mods` object:
106
+
107
+ ```jsx
108
+ // Before: mods object
109
+ <Button mods={{ isLoading: true, size: 'large' }}>Submit</Button>
110
+
111
+ // After: mod props
112
+ <Button isLoading size="large">Submit</Button>
113
+ ```
114
+
115
+ ### Array form
116
+
117
+ List modifier key names. Types default to `ModValue` (`boolean | string | number | undefined | null`):
118
+
119
+ ```jsx
120
+ const Button = tasty({
121
+ modProps: ['isLoading', 'isSelected'] as const,
122
+ styles: {
123
+ fill: { '': '#surface', isLoading: '#surface.5' },
124
+ border: { '': '1bw solid #outline', isSelected: '2bw solid #primary' },
125
+ },
126
+ });
127
+
128
+ <Button isLoading isSelected>Submit</Button>
129
+ // Renders: <button data-is-loading="" data-is-selected="">Submit</button>
130
+ ```
131
+
132
+ ### Object form (typed)
133
+
134
+ Map modifier names to type descriptors for precise TypeScript types:
135
+
136
+ ```tsx
137
+ const Button = tasty({
138
+ modProps: {
139
+ isLoading: Boolean, // isLoading?: boolean
140
+ isSelected: Boolean, // isSelected?: boolean
141
+ size: ['small', 'medium', 'large'] as const, // size?: 'small' | 'medium' | 'large'
142
+ },
143
+ styles: {
144
+ padding: { '': '2x 4x', 'size=small': '1x 2x', 'size=large': '3x 6x' },
145
+ fill: { '': '#surface', isLoading: '#surface.5' },
146
+ },
147
+ });
148
+
149
+ <Button isLoading size="large">Submit</Button>
150
+ // Renders: <button data-is-loading="" data-size="large">Submit</button>
151
+ ```
152
+
153
+ Available type descriptors:
154
+
155
+ | Descriptor | TypeScript type | Example |
156
+ |---|---|---|
157
+ | `Boolean` | `boolean` | `isLoading: Boolean` |
158
+ | `String` | `string` | `label: String` |
159
+ | `Number` | `number` | `count: Number` |
160
+ | `['a', 'b'] as const` | `'a' \| 'b'` | `size: ['sm', 'md', 'lg'] as const` |
161
+
162
+ ### Merge with `mods`
163
+
164
+ Mod props and the `mods` object can be used together. Mod props take precedence:
165
+
166
+ ```jsx
167
+ <Button mods={{ isLoading: false, extra: true }} isLoading>
168
+ // isLoading=true wins (from mod prop), extra=true preserved from mods
169
+ ```
170
+
171
+ ### When to use `modProps` vs `mods`
172
+
173
+ | Use case | Recommendation |
174
+ |---|---|
175
+ | Component has a fixed set of known modifiers | `modProps` — cleaner API, better TypeScript autocomplete |
176
+ | Component needs arbitrary/dynamic modifiers | `mods` — open-ended `Record<string, ModValue>` |
177
+ | Both fixed and dynamic | Combine: `modProps` for known keys, `mods` for ad-hoc |
178
+
179
+ For architecture guidance on when to use modifiers vs `styleProps`, see [Methodology — modProps and mods](methodology.md#modprops-and-mods).
180
+
181
+ ---
182
+
183
+ ## Token Props
184
+
185
+ Use `tokenProps` to expose token keys as direct component props instead of requiring the `tokens` object:
186
+
187
+ ```jsx
188
+ // Before: tokens object
189
+ <ProgressBar tokens={{ $progress: '75%', '#accent': '#purple' }} />
190
+
191
+ // After: token props
192
+ <ProgressBar progress="75%" accentColor="#purple" />
193
+ ```
194
+
195
+ ### Array form
196
+
197
+ List prop names. Names ending in `Color` map to `#` color tokens; everything else maps to `$` custom property tokens:
198
+
199
+ ```jsx
200
+ const ProgressBar = tasty({
201
+ tokenProps: ['progress', 'accentColor'] as const,
202
+ styles: { width: '$progress', fill: '#accent' },
203
+ });
204
+
205
+ <ProgressBar progress="75%" accentColor="#purple" />
206
+ // 'progress' → $progress → --progress
207
+ // 'accentColor' → #accent → --accent-color + --accent-color-oklch
208
+ ```
209
+
210
+ ### Object form
211
+
212
+ Map prop names to explicit `$`/`#`-prefixed token keys:
213
+
214
+ ```tsx
215
+ const Card = tasty({
216
+ tokenProps: {
217
+ size: '$card-size',
218
+ color: '#card-accent',
219
+ },
220
+ styles: { padding: '$card-size', fill: '#card-accent' },
221
+ });
222
+
223
+ <Card size="4x" color="#purple" />
224
+ ```
225
+
226
+ ### Merge with `tokens`
227
+
228
+ Token props and the `tokens` prop can be used together. Token props take precedence over `tokens`, which takes precedence over default `tokens` in `tasty({...})`:
229
+
230
+ ```jsx
231
+ const Bar = tasty({
232
+ tokenProps: ['progress'] as const,
233
+ tokens: { $progress: '0%' }, // default
234
+ });
235
+
236
+ <Bar tokens={{ $progress: '50%' }} progress="90%" />
237
+ // progress="90%" wins (from token prop)
238
+ ```
239
+
240
+ ### When to use `tokenProps` vs `tokens`
241
+
242
+ | Use case | Recommendation |
243
+ |---|---|
244
+ | Component has a fixed set of known token keys | `tokenProps` — cleaner API, better TypeScript autocomplete |
245
+ | Component needs arbitrary/dynamic token values | `tokens` — open-ended `Record<string, TokenValue>` |
246
+ | Both fixed and dynamic | Combine: `tokenProps` for known keys, `tokens` for ad-hoc |
247
+
248
+ For architecture guidance, see [Methodology — tokenProps](methodology.md#tokenprops).
249
+
250
+ ---
251
+
252
+ ## Variants
253
+
254
+ Define named style variations. Only CSS for variants actually used at runtime is injected:
255
+
256
+ ```jsx
257
+ const Button = tasty({
258
+ styles: {
259
+ padding: '2x 4x',
260
+ border: true,
261
+ },
262
+ variants: {
263
+ default: { fill: '#blue', color: '#white' },
264
+ danger: { fill: '#red', color: '#white' },
265
+ outline: { fill: 'transparent', color: '#blue', border: '1bw solid #blue' },
266
+ },
267
+ });
268
+
269
+ <Button variant="danger">Delete</Button>
270
+ ```
271
+
272
+ ### Extending Variants with Base State Maps
273
+
274
+ When base `styles` contain an extend-mode state map (an object **without** a `''` key), it is applied **after** the variant merge. This lets you add or override states across all variants without repeating yourself:
275
+
276
+ ```jsx
277
+ const Badge = tasty({
278
+ styles: {
279
+ padding: '1x 2x',
280
+ border: {
281
+ 'type=primary': '#clear',
282
+ },
283
+ },
284
+ variants: {
285
+ primary: {
286
+ border: { '': '#white.2', pressed: '#primary-text', disabled: '#clear' },
287
+ fill: { '': '#white #primary', hovered: '#white #primary-text' },
288
+ },
289
+ secondary: {
290
+ border: { '': '#primary.15', pressed: '#primary.3' },
291
+ fill: '#primary.10',
292
+ },
293
+ },
294
+ });
295
+
296
+ // Both variants get 'type=primary': '#clear' appended to their border map
297
+ ```
298
+
299
+ Properties that are **not** extend-mode (simple values, state maps with `''`, `null`, `false`, selectors, sub-elements) merge with variants as before — the variant can fully replace them.
300
+
301
+ ---
302
+
303
+ ## Sub-element Styling
304
+
305
+ Sub-elements are inner parts of a compound component, styled via capitalized keys in `styles` and identified by `data-element` attributes in the DOM.
306
+
307
+ > Use the `elements` prop to declare sub-element components. This gives you typed, reusable sub-components (`Card.Title`, `Card.Content`) instead of manually writing `data-element` attributes.
308
+
309
+ ```jsx
310
+ const Card = tasty({
311
+ styles: {
312
+ padding: '4x',
313
+ Title: { preset: 'h3', color: '#primary' },
314
+ Content: { color: '#text' },
315
+ },
316
+ elements: {
317
+ Title: 'h3',
318
+ Content: 'div',
319
+ },
320
+ });
321
+
322
+ <Card>
323
+ <Card.Title>Card Title</Card.Title>
324
+ <Card.Content>Card content</Card.Content>
325
+ </Card>
326
+ ```
327
+
328
+ Each entry in `elements` can be a tag name string or a config object:
329
+
330
+ ```jsx
331
+ elements: {
332
+ Title: 'h3', // shorthand: tag name only
333
+ Icon: { as: 'span', qa: 'card-icon' }, // full form: tag + QA attribute
334
+ }
335
+ ```
336
+
337
+ The sub-components produced by `elements` support `mods`, `tokens`, `isDisabled`, `isHidden`, and `isChecked` props — the same modifier interface as the root component.
338
+
339
+ If you don't need sub-components (e.g., the inner elements are already rendered by a third-party library), you can still style them by key alone — just omit `elements` and apply `data-element` manually:
340
+
341
+ ```jsx
342
+ const Card = tasty({
343
+ styles: {
344
+ padding: '4x',
345
+ Title: { preset: 'h3', color: '#primary' },
346
+ },
347
+ });
348
+
349
+ <Card>
350
+ <div data-element="Title">Card Title</div>
351
+ </Card>
352
+ ```
353
+
354
+ ### Selector Affix (`$`)
355
+
356
+ The `$` property inside a sub-element's styles controls how its selector attaches to the root selector — combinators, HTML tags, pseudo-elements, the `@` placeholder, and more. For the full reference table and injection rules, see [DSL — Selector Affix](dsl.md#selector-affix-).
357
+
358
+ For the mental model behind sub-elements — how they share root state context and how this differs from BEM — see [Methodology — Component architecture](methodology.md#component-architecture-root--sub-elements).
359
+
360
+ ---
361
+
362
+ ## computeStyles
363
+
364
+ Hook-free, synchronous style computation. Can be used anywhere — including React Server Components, plain functions, and non-React code:
365
+
366
+ ```tsx
367
+ import { computeStyles } from '@tenphi/tasty';
368
+
369
+ const { className } = computeStyles({
370
+ padding: '2x',
371
+ fill: '#surface',
372
+ radius: '1r',
373
+ });
374
+ ```
375
+
376
+ On the client, CSS is injected synchronously into the DOM (idempotent via the injector cache). On the server, CSS is collected via the SSR collector if one is available. This is the same function that `tasty()` components use internally.
377
+
378
+ ---
379
+
380
+ ## Style Functions
381
+
382
+ All style functions below are plain functions (not React hooks) and can be used in any environment: client components, SSR with a `ServerStyleCollector`, and React Server Components. They retain their `use` prefix for backward compatibility, but do not use any React hooks internally.
383
+
384
+ In server-only contexts (Next.js RSC without `'use client'`, Astro without `client:*` directives, SSG), components that use only Tasty style functions produce zero client JavaScript. Tasty never forces the `'use client'` boundary — that decision belongs to your component when it needs React interactivity (state, effects, event handlers).
385
+
386
+ ### useStyles
387
+
388
+ Generate a className from a style object. Thin wrapper around `computeStyles()`:
389
+
390
+ ```tsx
391
+ import { useStyles } from '@tenphi/tasty';
392
+
393
+ function MyComponent() {
394
+ const { className } = useStyles({
395
+ padding: '2x',
396
+ fill: '#surface',
397
+ radius: '1r',
398
+ });
399
+
400
+ return <div className={className}>Styled content</div>;
401
+ }
402
+ ```
403
+
404
+ ### useGlobalStyles
405
+
406
+ Inject global styles for a CSS selector. Accepts an optional third argument with an `id` for update tracking — when the styles change, the previous injection is disposed and the new one is injected:
407
+
408
+ ```tsx
409
+ import { useGlobalStyles } from '@tenphi/tasty';
410
+
411
+ function ThemeStyles() {
412
+ useGlobalStyles('.card', {
413
+ padding: '4x',
414
+ fill: '#surface',
415
+ radius: '1r',
416
+ });
417
+
418
+ return null;
419
+ }
420
+ ```
421
+
422
+ ### useRawCSS
423
+
424
+ Inject raw CSS strings. Accepts an optional `id` in the options for update tracking — when the CSS changes for the same id, the previous injection is replaced:
425
+
426
+ ```tsx
427
+ import { useRawCSS } from '@tenphi/tasty';
428
+
429
+ function GlobalReset() {
430
+ useRawCSS(`
431
+ body { margin: 0; padding: 0; }
432
+ `);
433
+
434
+ return null;
435
+ }
436
+ ```
437
+
438
+ ### useKeyframes
439
+
440
+ Inject `@keyframes` rules and return the generated animation name:
441
+
442
+ ```tsx
443
+ import { useKeyframes } from '@tenphi/tasty';
444
+
445
+ function Spinner() {
446
+ const spin = useKeyframes(
447
+ {
448
+ from: { transform: 'rotate(0deg)' },
449
+ to: { transform: 'rotate(360deg)' },
450
+ },
451
+ { name: 'spin' }
452
+ );
453
+
454
+ return <div style={{ animation: `${spin} 1s linear infinite` }} />;
455
+ }
456
+ ```
457
+
458
+ `useKeyframes()` also supports a factory function. The deps array is accepted for backward compatibility but the factory is called on every invocation — deduplication is handled internally by content hash:
459
+
460
+ ```tsx
461
+ function Pulse({ scale }: { scale: number }) {
462
+ const pulse = useKeyframes(
463
+ () => ({
464
+ '0%': { transform: 'scale(1)' },
465
+ '100%': { transform: `scale(${scale})` },
466
+ }),
467
+ [scale]
468
+ );
469
+
470
+ return <div style={{ animation: `${pulse} 500ms ease-in-out alternate infinite` }} />;
471
+ }
472
+ ```
473
+
474
+ ### useProperty
475
+
476
+ Register a CSS `@property` rule so a custom property can animate smoothly:
477
+
478
+ ```tsx
479
+ import { useProperty } from '@tenphi/tasty';
480
+
481
+ function Spinner() {
482
+ useProperty('$rotation', {
483
+ syntax: '<angle>',
484
+ inherits: false,
485
+ initialValue: '0deg',
486
+ });
487
+
488
+ return <div style={{ transform: 'rotate(var(--rotation))' }} />;
489
+ }
490
+ ```
491
+
492
+ `useProperty()` accepts Tasty token syntax for the property name:
493
+
494
+ - `$name` defines `--name`
495
+ - `#name` defines `--name-color` and auto-infers `<color>`
496
+ - `--name` is also supported for existing CSS variables
497
+
498
+ ### useFontFace
499
+
500
+ Inject `@font-face` rules for custom fonts. Permanent — no cleanup on unmount. Deduplicates by content.
501
+
502
+ ```tsx
503
+ import { useFontFace } from '@tenphi/tasty';
504
+
505
+ function App() {
506
+ useFontFace('Brand Sans', {
507
+ src: 'url("/fonts/brand-sans.woff2") format("woff2")',
508
+ fontWeight: '400 700',
509
+ fontDisplay: 'swap',
510
+ });
511
+
512
+ return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
513
+ }
514
+ ```
515
+
516
+ For multiple weights/styles, pass an array:
517
+
518
+ ```tsx
519
+ useFontFace('Brand Sans', [
520
+ { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
521
+ { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
522
+ ]);
523
+ ```
524
+
525
+ Signature:
526
+
527
+ ```ts
528
+ function useFontFace(family: string, input: FontFaceInput): void;
529
+ ```
530
+
531
+ ### useCounterStyle
532
+
533
+ Inject a `@counter-style` rule and get back the counter style name. Permanent — no cleanup on unmount. Deduplicates by name.
534
+
535
+ ```tsx
536
+ import { useCounterStyle } from '@tenphi/tasty';
537
+
538
+ function EmojiList() {
539
+ const styleName = useCounterStyle({
540
+ system: 'cyclic',
541
+ symbols: '"👍"',
542
+ suffix: '" "',
543
+ }, { name: 'thumbs' });
544
+
545
+ return (
546
+ <ol style={{ listStyleType: styleName }}>
547
+ <li>First</li>
548
+ <li>Second</li>
549
+ </ol>
550
+ );
551
+ }
552
+ ```
553
+
554
+ Signature:
555
+
556
+ ```ts
557
+ function useCounterStyle(
558
+ descriptors: CounterStyleDescriptors,
559
+ options?: { name?: string; root?: Document | ShadowRoot },
560
+ ): string;
561
+ ```
562
+
563
+ ### Troubleshooting
564
+
565
+ - Styles are not updating: make sure `configure()` runs before first render, and verify the generated class name or global rule with [Debug Utilities](debug.md).
566
+ - SSR output looks wrong: check the [SSR guide](ssr.md) for collector setup. All style functions discover the SSR collector via `AsyncLocalStorage` or the global getter registered by `TastyRegistry`.
567
+ - Animation/custom property issues: prefer `useKeyframes()` and `useProperty()` over raw CSS when you want Tasty to manage injection and SSR collection for you.
568
+ - For dynamic styles that change over the component lifecycle, use the `id` option in `useGlobalStyles()` and `useRawCSS()` to enable update tracking.
569
+ - RSC inline mode: CSS accumulated by standalone style functions (`useGlobalStyles`, `useRawCSS`, etc.) is flushed into inline `<style>` tags by the next `tasty()` component in the render tree. If your page uses only standalone style functions without any `tasty()` component, the CSS will not be emitted. Ensure at least one `tasty()` component is present in each RSC render tree.
570
+
571
+ ---
572
+
573
+ ## Learn more
574
+
575
+ - **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
576
+ - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
577
+ - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
578
+ - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
579
+ - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin
580
+ - **[Server-Side Rendering](ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks
581
+ - **[Debug Utilities](debug.md)** — Inspect injected CSS, cache state, and active styles at runtime