@tenphi/tasty 0.0.0-snapshot.05c1c22

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 (314) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +629 -0
  3. package/dist/_virtual/_rolldown/runtime.js +7 -0
  4. package/dist/chunks/cacheKey.d.ts +1 -0
  5. package/dist/chunks/cacheKey.js +77 -0
  6. package/dist/chunks/cacheKey.js.map +1 -0
  7. package/dist/chunks/definitions.d.ts +37 -0
  8. package/dist/chunks/definitions.js +258 -0
  9. package/dist/chunks/definitions.js.map +1 -0
  10. package/dist/chunks/index.d.ts +1 -0
  11. package/dist/chunks/renderChunk.d.ts +1 -0
  12. package/dist/chunks/renderChunk.js +59 -0
  13. package/dist/chunks/renderChunk.js.map +1 -0
  14. package/dist/config.d.ts +366 -0
  15. package/dist/config.js +503 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/core/index.d.ts +33 -0
  18. package/dist/core/index.js +26 -0
  19. package/dist/counter-style/index.js +51 -0
  20. package/dist/counter-style/index.js.map +1 -0
  21. package/dist/debug.d.ts +89 -0
  22. package/dist/debug.js +453 -0
  23. package/dist/debug.js.map +1 -0
  24. package/dist/font-face/index.js +63 -0
  25. package/dist/font-face/index.js.map +1 -0
  26. package/dist/hooks/index.d.ts +7 -0
  27. package/dist/hooks/resolve-ssr-collector.js +14 -0
  28. package/dist/hooks/resolve-ssr-collector.js.map +1 -0
  29. package/dist/hooks/useCounterStyle.d.ts +50 -0
  30. package/dist/hooks/useCounterStyle.js +46 -0
  31. package/dist/hooks/useCounterStyle.js.map +1 -0
  32. package/dist/hooks/useFontFace.d.ts +43 -0
  33. package/dist/hooks/useFontFace.js +70 -0
  34. package/dist/hooks/useFontFace.js.map +1 -0
  35. package/dist/hooks/useGlobalStyles.d.ts +30 -0
  36. package/dist/hooks/useGlobalStyles.js +78 -0
  37. package/dist/hooks/useGlobalStyles.js.map +1 -0
  38. package/dist/hooks/useKeyframes.d.ts +56 -0
  39. package/dist/hooks/useKeyframes.js +64 -0
  40. package/dist/hooks/useKeyframes.js.map +1 -0
  41. package/dist/hooks/useProperty.d.ts +79 -0
  42. package/dist/hooks/useProperty.js +109 -0
  43. package/dist/hooks/useProperty.js.map +1 -0
  44. package/dist/hooks/useRawCSS.d.ts +53 -0
  45. package/dist/hooks/useRawCSS.js +35 -0
  46. package/dist/hooks/useRawCSS.js.map +1 -0
  47. package/dist/hooks/useStyles.d.ts +45 -0
  48. package/dist/hooks/useStyles.js +237 -0
  49. package/dist/hooks/useStyles.js.map +1 -0
  50. package/dist/index.d.ts +50 -0
  51. package/dist/index.js +35 -0
  52. package/dist/injector/index.d.ts +183 -0
  53. package/dist/injector/index.js +179 -0
  54. package/dist/injector/index.js.map +1 -0
  55. package/dist/injector/injector.d.ts +166 -0
  56. package/dist/injector/injector.js +464 -0
  57. package/dist/injector/injector.js.map +1 -0
  58. package/dist/injector/sheet-manager.d.ts +136 -0
  59. package/dist/injector/sheet-manager.js +733 -0
  60. package/dist/injector/sheet-manager.js.map +1 -0
  61. package/dist/injector/types.d.ts +204 -0
  62. package/dist/keyframes/index.js +206 -0
  63. package/dist/keyframes/index.js.map +1 -0
  64. package/dist/parser/classify.js +319 -0
  65. package/dist/parser/classify.js.map +1 -0
  66. package/dist/parser/const.js +49 -0
  67. package/dist/parser/const.js.map +1 -0
  68. package/dist/parser/lru.js +109 -0
  69. package/dist/parser/lru.js.map +1 -0
  70. package/dist/parser/parser.d.ts +25 -0
  71. package/dist/parser/parser.js +115 -0
  72. package/dist/parser/parser.js.map +1 -0
  73. package/dist/parser/tokenizer.js +69 -0
  74. package/dist/parser/tokenizer.js.map +1 -0
  75. package/dist/parser/types.d.ts +51 -0
  76. package/dist/parser/types.js +46 -0
  77. package/dist/parser/types.js.map +1 -0
  78. package/dist/pipeline/conditions.d.ts +134 -0
  79. package/dist/pipeline/conditions.js +406 -0
  80. package/dist/pipeline/conditions.js.map +1 -0
  81. package/dist/pipeline/exclusive.js +230 -0
  82. package/dist/pipeline/exclusive.js.map +1 -0
  83. package/dist/pipeline/index.d.ts +55 -0
  84. package/dist/pipeline/index.js +708 -0
  85. package/dist/pipeline/index.js.map +1 -0
  86. package/dist/pipeline/materialize.js +1103 -0
  87. package/dist/pipeline/materialize.js.map +1 -0
  88. package/dist/pipeline/parseStateKey.d.ts +15 -0
  89. package/dist/pipeline/parseStateKey.js +446 -0
  90. package/dist/pipeline/parseStateKey.js.map +1 -0
  91. package/dist/pipeline/simplify.js +515 -0
  92. package/dist/pipeline/simplify.js.map +1 -0
  93. package/dist/pipeline/warnings.js +18 -0
  94. package/dist/pipeline/warnings.js.map +1 -0
  95. package/dist/plugins/index.d.ts +2 -0
  96. package/dist/plugins/okhsl-plugin.d.ts +35 -0
  97. package/dist/plugins/okhsl-plugin.js +97 -0
  98. package/dist/plugins/okhsl-plugin.js.map +1 -0
  99. package/dist/plugins/types.d.ts +76 -0
  100. package/dist/properties/index.js +222 -0
  101. package/dist/properties/index.js.map +1 -0
  102. package/dist/properties/property-type-resolver.d.ts +24 -0
  103. package/dist/properties/property-type-resolver.js +90 -0
  104. package/dist/properties/property-type-resolver.js.map +1 -0
  105. package/dist/ssr/astro.d.ts +29 -0
  106. package/dist/ssr/astro.js +64 -0
  107. package/dist/ssr/astro.js.map +1 -0
  108. package/dist/ssr/async-storage.d.ts +17 -0
  109. package/dist/ssr/async-storage.js +34 -0
  110. package/dist/ssr/async-storage.js.map +1 -0
  111. package/dist/ssr/collect-auto-properties.js +39 -0
  112. package/dist/ssr/collect-auto-properties.js.map +1 -0
  113. package/dist/ssr/collector.d.ts +102 -0
  114. package/dist/ssr/collector.js +226 -0
  115. package/dist/ssr/collector.js.map +1 -0
  116. package/dist/ssr/context.d.ts +8 -0
  117. package/dist/ssr/context.js +13 -0
  118. package/dist/ssr/context.js.map +1 -0
  119. package/dist/ssr/format-global-rules.js +22 -0
  120. package/dist/ssr/format-global-rules.js.map +1 -0
  121. package/dist/ssr/format-keyframes.js +69 -0
  122. package/dist/ssr/format-keyframes.js.map +1 -0
  123. package/dist/ssr/format-property.js +49 -0
  124. package/dist/ssr/format-property.js.map +1 -0
  125. package/dist/ssr/format-rules.js +73 -0
  126. package/dist/ssr/format-rules.js.map +1 -0
  127. package/dist/ssr/hydrate.d.ts +22 -0
  128. package/dist/ssr/hydrate.js +49 -0
  129. package/dist/ssr/hydrate.js.map +1 -0
  130. package/dist/ssr/index.d.ts +5 -0
  131. package/dist/ssr/index.js +11 -0
  132. package/dist/ssr/index.js.map +1 -0
  133. package/dist/ssr/next.d.ts +45 -0
  134. package/dist/ssr/next.js +69 -0
  135. package/dist/ssr/next.js.map +1 -0
  136. package/dist/ssr/ssr-collector-ref.js +12 -0
  137. package/dist/ssr/ssr-collector-ref.js.map +1 -0
  138. package/dist/states/index.d.ts +49 -0
  139. package/dist/states/index.js +170 -0
  140. package/dist/states/index.js.map +1 -0
  141. package/dist/static/index.d.ts +5 -0
  142. package/dist/static/index.js +4 -0
  143. package/dist/static/inject.d.ts +5 -0
  144. package/dist/static/inject.js +17 -0
  145. package/dist/static/inject.js.map +1 -0
  146. package/dist/static/tastyStatic.d.ts +46 -0
  147. package/dist/static/tastyStatic.js +30 -0
  148. package/dist/static/tastyStatic.js.map +1 -0
  149. package/dist/static/types.d.ts +49 -0
  150. package/dist/static/types.js +24 -0
  151. package/dist/static/types.js.map +1 -0
  152. package/dist/styles/align.d.ts +15 -0
  153. package/dist/styles/align.js +14 -0
  154. package/dist/styles/align.js.map +1 -0
  155. package/dist/styles/border.d.ts +25 -0
  156. package/dist/styles/border.js +113 -0
  157. package/dist/styles/border.js.map +1 -0
  158. package/dist/styles/color.d.ts +14 -0
  159. package/dist/styles/color.js +26 -0
  160. package/dist/styles/color.js.map +1 -0
  161. package/dist/styles/createStyle.js +79 -0
  162. package/dist/styles/createStyle.js.map +1 -0
  163. package/dist/styles/dimension.js +96 -0
  164. package/dist/styles/dimension.js.map +1 -0
  165. package/dist/styles/display.d.ts +37 -0
  166. package/dist/styles/display.js +66 -0
  167. package/dist/styles/display.js.map +1 -0
  168. package/dist/styles/fade.d.ts +15 -0
  169. package/dist/styles/fade.js +57 -0
  170. package/dist/styles/fade.js.map +1 -0
  171. package/dist/styles/fill.d.ts +42 -0
  172. package/dist/styles/fill.js +51 -0
  173. package/dist/styles/fill.js.map +1 -0
  174. package/dist/styles/flow.d.ts +16 -0
  175. package/dist/styles/flow.js +12 -0
  176. package/dist/styles/flow.js.map +1 -0
  177. package/dist/styles/gap.d.ts +31 -0
  178. package/dist/styles/gap.js +36 -0
  179. package/dist/styles/gap.js.map +1 -0
  180. package/dist/styles/height.d.ts +17 -0
  181. package/dist/styles/height.js +19 -0
  182. package/dist/styles/height.js.map +1 -0
  183. package/dist/styles/index.d.ts +1 -0
  184. package/dist/styles/index.js +8 -0
  185. package/dist/styles/index.js.map +1 -0
  186. package/dist/styles/inset.d.ts +52 -0
  187. package/dist/styles/inset.js +149 -0
  188. package/dist/styles/inset.js.map +1 -0
  189. package/dist/styles/justify.d.ts +15 -0
  190. package/dist/styles/justify.js +14 -0
  191. package/dist/styles/justify.js.map +1 -0
  192. package/dist/styles/list.d.ts +16 -0
  193. package/dist/styles/list.js +98 -0
  194. package/dist/styles/list.js.map +1 -0
  195. package/dist/styles/margin.d.ts +24 -0
  196. package/dist/styles/margin.js +103 -0
  197. package/dist/styles/margin.js.map +1 -0
  198. package/dist/styles/outline.d.ts +29 -0
  199. package/dist/styles/outline.js +64 -0
  200. package/dist/styles/outline.js.map +1 -0
  201. package/dist/styles/padding.d.ts +24 -0
  202. package/dist/styles/padding.js +103 -0
  203. package/dist/styles/padding.js.map +1 -0
  204. package/dist/styles/predefined.d.ts +71 -0
  205. package/dist/styles/predefined.js +237 -0
  206. package/dist/styles/predefined.js.map +1 -0
  207. package/dist/styles/preset.d.ts +52 -0
  208. package/dist/styles/preset.js +126 -0
  209. package/dist/styles/preset.js.map +1 -0
  210. package/dist/styles/radius.d.ts +12 -0
  211. package/dist/styles/radius.js +71 -0
  212. package/dist/styles/radius.js.map +1 -0
  213. package/dist/styles/scrollbar.d.ts +25 -0
  214. package/dist/styles/scrollbar.js +46 -0
  215. package/dist/styles/scrollbar.js.map +1 -0
  216. package/dist/styles/shadow.d.ts +14 -0
  217. package/dist/styles/shadow.js +23 -0
  218. package/dist/styles/shadow.js.map +1 -0
  219. package/dist/styles/transition.d.ts +14 -0
  220. package/dist/styles/transition.js +157 -0
  221. package/dist/styles/transition.js.map +1 -0
  222. package/dist/styles/types.d.ts +549 -0
  223. package/dist/styles/width.d.ts +17 -0
  224. package/dist/styles/width.js +19 -0
  225. package/dist/styles/width.js.map +1 -0
  226. package/dist/tasty.d.ts +119 -0
  227. package/dist/tasty.js +231 -0
  228. package/dist/tasty.js.map +1 -0
  229. package/dist/types.d.ts +184 -0
  230. package/dist/utils/cache-wrapper.js +21 -0
  231. package/dist/utils/cache-wrapper.js.map +1 -0
  232. package/dist/utils/case-converter.js +8 -0
  233. package/dist/utils/case-converter.js.map +1 -0
  234. package/dist/utils/color-math.d.ts +46 -0
  235. package/dist/utils/color-math.js +749 -0
  236. package/dist/utils/color-math.js.map +1 -0
  237. package/dist/utils/color-space.d.ts +5 -0
  238. package/dist/utils/color-space.js +228 -0
  239. package/dist/utils/color-space.js.map +1 -0
  240. package/dist/utils/colors.d.ts +5 -0
  241. package/dist/utils/colors.js +10 -0
  242. package/dist/utils/colors.js.map +1 -0
  243. package/dist/utils/css-types.d.ts +7 -0
  244. package/dist/utils/dotize.d.ts +26 -0
  245. package/dist/utils/dotize.js +122 -0
  246. package/dist/utils/dotize.js.map +1 -0
  247. package/dist/utils/filter-base-props.d.ts +15 -0
  248. package/dist/utils/filter-base-props.js +45 -0
  249. package/dist/utils/filter-base-props.js.map +1 -0
  250. package/dist/utils/get-display-name.d.ts +7 -0
  251. package/dist/utils/get-display-name.js +10 -0
  252. package/dist/utils/get-display-name.js.map +1 -0
  253. package/dist/utils/has-keys.js +13 -0
  254. package/dist/utils/has-keys.js.map +1 -0
  255. package/dist/utils/is-dev-env.js +19 -0
  256. package/dist/utils/is-dev-env.js.map +1 -0
  257. package/dist/utils/is-valid-element-type.js +15 -0
  258. package/dist/utils/is-valid-element-type.js.map +1 -0
  259. package/dist/utils/merge-styles.d.ts +7 -0
  260. package/dist/utils/merge-styles.js +145 -0
  261. package/dist/utils/merge-styles.js.map +1 -0
  262. package/dist/utils/mod-attrs.d.ts +6 -0
  263. package/dist/utils/mod-attrs.js +20 -0
  264. package/dist/utils/mod-attrs.js.map +1 -0
  265. package/dist/utils/process-tokens.d.ts +21 -0
  266. package/dist/utils/process-tokens.js +90 -0
  267. package/dist/utils/process-tokens.js.map +1 -0
  268. package/dist/utils/resolve-recipes.d.ts +17 -0
  269. package/dist/utils/resolve-recipes.js +146 -0
  270. package/dist/utils/resolve-recipes.js.map +1 -0
  271. package/dist/utils/selector-transform.js +32 -0
  272. package/dist/utils/selector-transform.js.map +1 -0
  273. package/dist/utils/string.js +8 -0
  274. package/dist/utils/string.js.map +1 -0
  275. package/dist/utils/styles.d.ts +99 -0
  276. package/dist/utils/styles.js +220 -0
  277. package/dist/utils/styles.js.map +1 -0
  278. package/dist/utils/typography.d.ts +47 -0
  279. package/dist/utils/typography.js +51 -0
  280. package/dist/utils/typography.js.map +1 -0
  281. package/dist/utils/warnings.d.ts +16 -0
  282. package/dist/utils/warnings.js +16 -0
  283. package/dist/utils/warnings.js.map +1 -0
  284. package/dist/zero/babel.d.ts +182 -0
  285. package/dist/zero/babel.js +438 -0
  286. package/dist/zero/babel.js.map +1 -0
  287. package/dist/zero/css-writer.d.ts +45 -0
  288. package/dist/zero/css-writer.js +73 -0
  289. package/dist/zero/css-writer.js.map +1 -0
  290. package/dist/zero/extractor.d.ts +24 -0
  291. package/dist/zero/extractor.js +266 -0
  292. package/dist/zero/extractor.js.map +1 -0
  293. package/dist/zero/index.d.ts +3 -0
  294. package/dist/zero/index.js +3 -0
  295. package/dist/zero/next.d.ts +86 -0
  296. package/dist/zero/next.js +143 -0
  297. package/dist/zero/next.js.map +1 -0
  298. package/docs/PIPELINE.md +519 -0
  299. package/docs/README.md +31 -0
  300. package/docs/adoption.md +296 -0
  301. package/docs/comparison.md +420 -0
  302. package/docs/configuration.md +326 -0
  303. package/docs/debug.md +318 -0
  304. package/docs/design-system.md +424 -0
  305. package/docs/dsl.md +673 -0
  306. package/docs/getting-started.md +217 -0
  307. package/docs/injector.md +528 -0
  308. package/docs/methodology.md +567 -0
  309. package/docs/runtime.md +485 -0
  310. package/docs/ssr.md +384 -0
  311. package/docs/styles.md +582 -0
  312. package/docs/tasty-static.md +520 -0
  313. package/package.json +215 -0
  314. package/tasty.config.ts +14 -0
@@ -0,0 +1,485 @@
1
+ # Runtime API
2
+
3
+ The React-specific `tasty()` component factory, component props, and hooks. 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
+ ---
6
+
7
+ ## Component Creation
8
+
9
+ ### Create a new component
10
+
11
+ ```jsx
12
+ import { tasty } from '@tenphi/tasty';
13
+
14
+ const Card = tasty({
15
+ as: 'div',
16
+ styles: {
17
+ padding: '4x',
18
+ fill: '#white',
19
+ border: true,
20
+ radius: true,
21
+ },
22
+ styleProps: ['padding', 'fill'],
23
+ });
24
+
25
+ <Card>Hello World</Card>
26
+ <Card padding="6x" fill="#gray.05">Custom Card</Card>
27
+ ```
28
+
29
+ ### Extend an existing component
30
+
31
+ ```jsx
32
+ const PrimaryButton = tasty(Button, {
33
+ styles: {
34
+ fill: '#purple',
35
+ color: '#white',
36
+ padding: '2x 4x',
37
+ },
38
+ });
39
+ ```
40
+
41
+ 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.
42
+
43
+ ---
44
+
45
+ ## Style Props
46
+
47
+ Use `styleProps` to expose style properties as direct component props:
48
+
49
+ ```jsx
50
+ const FlexibleBox = tasty({
51
+ as: 'div',
52
+ styles: {
53
+ display: 'flex',
54
+ padding: '2x',
55
+ },
56
+ styleProps: ['gap', 'align', 'placeContent', 'fill'],
57
+ });
58
+
59
+ <FlexibleBox gap="2x" align="center" fill="#surface">
60
+ Content
61
+ </FlexibleBox>
62
+ ```
63
+
64
+ Style props accept state maps, so responsive values work through the same API:
65
+
66
+ ```jsx
67
+ <FlexibleBox
68
+ gap={{ '': '2x', '@tablet': '4x' }}
69
+ fill={{ '': '#surface', '@dark': '#surface-dark' }}
70
+ >
71
+ ```
72
+
73
+ 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).
74
+
75
+ ---
76
+
77
+ ## Mod Props
78
+
79
+ Use `modProps` to expose modifier keys as direct component props instead of requiring the `mods` object:
80
+
81
+ ```jsx
82
+ // Before: mods object
83
+ <Button mods={{ isLoading: true, size: 'large' }}>Submit</Button>
84
+
85
+ // After: mod props
86
+ <Button isLoading size="large">Submit</Button>
87
+ ```
88
+
89
+ ### Array form
90
+
91
+ List modifier key names. Types default to `ModValue` (`boolean | string | number | undefined | null`):
92
+
93
+ ```jsx
94
+ const Button = tasty({
95
+ modProps: ['isLoading', 'isSelected'] as const,
96
+ styles: {
97
+ fill: { '': '#surface', isLoading: '#surface.5' },
98
+ border: { '': '1bw solid #outline', isSelected: '2bw solid #primary' },
99
+ },
100
+ });
101
+
102
+ <Button isLoading isSelected>Submit</Button>
103
+ // Renders: <button data-is-loading="" data-is-selected="">Submit</button>
104
+ ```
105
+
106
+ ### Object form (typed)
107
+
108
+ Map modifier names to type descriptors for precise TypeScript types:
109
+
110
+ ```tsx
111
+ const Button = tasty({
112
+ modProps: {
113
+ isLoading: Boolean, // isLoading?: boolean
114
+ isSelected: Boolean, // isSelected?: boolean
115
+ size: ['small', 'medium', 'large'] as const, // size?: 'small' | 'medium' | 'large'
116
+ },
117
+ styles: {
118
+ padding: { '': '2x 4x', 'size=small': '1x 2x', 'size=large': '3x 6x' },
119
+ fill: { '': '#surface', isLoading: '#surface.5' },
120
+ },
121
+ });
122
+
123
+ <Button isLoading size="large">Submit</Button>
124
+ // Renders: <button data-is-loading="" data-size="large">Submit</button>
125
+ ```
126
+
127
+ Available type descriptors:
128
+
129
+ | Descriptor | TypeScript type | Example |
130
+ |---|---|---|
131
+ | `Boolean` | `boolean` | `isLoading: Boolean` |
132
+ | `String` | `string` | `label: String` |
133
+ | `Number` | `number` | `count: Number` |
134
+ | `['a', 'b'] as const` | `'a' \| 'b'` | `size: ['sm', 'md', 'lg'] as const` |
135
+
136
+ ### Merge with `mods`
137
+
138
+ Mod props and the `mods` object can be used together. Mod props take precedence:
139
+
140
+ ```jsx
141
+ <Button mods={{ isLoading: false, extra: true }} isLoading>
142
+ // isLoading=true wins (from mod prop), extra=true preserved from mods
143
+ ```
144
+
145
+ ### When to use `modProps` vs `mods`
146
+
147
+ | Use case | Recommendation |
148
+ |---|---|
149
+ | Component has a fixed set of known modifiers | `modProps` — cleaner API, better TypeScript autocomplete |
150
+ | Component needs arbitrary/dynamic modifiers | `mods` — open-ended `Record<string, ModValue>` |
151
+ | Both fixed and dynamic | Combine: `modProps` for known keys, `mods` for ad-hoc |
152
+
153
+ For architecture guidance on when to use modifiers vs `styleProps`, see [Methodology — modProps and mods](methodology.md#modprops-and-mods).
154
+
155
+ ---
156
+
157
+ ## Variants
158
+
159
+ Define named style variations. Only CSS for variants actually used at runtime is injected:
160
+
161
+ ```jsx
162
+ const Button = tasty({
163
+ styles: {
164
+ padding: '2x 4x',
165
+ border: true,
166
+ },
167
+ variants: {
168
+ default: { fill: '#blue', color: '#white' },
169
+ danger: { fill: '#red', color: '#white' },
170
+ outline: { fill: 'transparent', color: '#blue', border: '1bw solid #blue' },
171
+ },
172
+ });
173
+
174
+ <Button variant="danger">Delete</Button>
175
+ ```
176
+
177
+ ### Extending Variants with Base State Maps
178
+
179
+ 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:
180
+
181
+ ```jsx
182
+ const Badge = tasty({
183
+ styles: {
184
+ padding: '1x 2x',
185
+ border: {
186
+ 'type=primary': '#clear',
187
+ },
188
+ },
189
+ variants: {
190
+ primary: {
191
+ border: { '': '#white.2', pressed: '#primary-text', disabled: '#clear' },
192
+ fill: { '': '#white #primary', hovered: '#white #primary-text' },
193
+ },
194
+ secondary: {
195
+ border: { '': '#primary.15', pressed: '#primary.3' },
196
+ fill: '#primary.10',
197
+ },
198
+ },
199
+ });
200
+
201
+ // Both variants get 'type=primary': '#clear' appended to their border map
202
+ ```
203
+
204
+ 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.
205
+
206
+ ---
207
+
208
+ ## Sub-element Styling
209
+
210
+ Sub-elements are inner parts of a compound component, styled via capitalized keys in `styles` and identified by `data-element` attributes in the DOM.
211
+
212
+ > 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.
213
+
214
+ ```jsx
215
+ const Card = tasty({
216
+ styles: {
217
+ padding: '4x',
218
+ Title: { preset: 'h3', color: '#primary' },
219
+ Content: { color: '#text' },
220
+ },
221
+ elements: {
222
+ Title: 'h3',
223
+ Content: 'div',
224
+ },
225
+ });
226
+
227
+ <Card>
228
+ <Card.Title>Card Title</Card.Title>
229
+ <Card.Content>Card content</Card.Content>
230
+ </Card>
231
+ ```
232
+
233
+ Each entry in `elements` can be a tag name string or a config object:
234
+
235
+ ```jsx
236
+ elements: {
237
+ Title: 'h3', // shorthand: tag name only
238
+ Icon: { as: 'span', qa: 'card-icon' }, // full form: tag + QA attribute
239
+ }
240
+ ```
241
+
242
+ The sub-components produced by `elements` support `mods`, `tokens`, `isDisabled`, `isHidden`, and `isChecked` props — the same modifier interface as the root component.
243
+
244
+ 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:
245
+
246
+ ```jsx
247
+ const Card = tasty({
248
+ styles: {
249
+ padding: '4x',
250
+ Title: { preset: 'h3', color: '#primary' },
251
+ },
252
+ });
253
+
254
+ <Card>
255
+ <div data-element="Title">Card Title</div>
256
+ </Card>
257
+ ```
258
+
259
+ ### Selector Affix (`$`)
260
+
261
+ 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-).
262
+
263
+ 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).
264
+
265
+ ---
266
+
267
+ ## Hooks
268
+
269
+ ### useStyles
270
+
271
+ Generate a className from a style object:
272
+
273
+ ```tsx
274
+ import { useStyles } from '@tenphi/tasty';
275
+
276
+ function MyComponent() {
277
+ const { className } = useStyles({
278
+ padding: '2x',
279
+ fill: '#surface',
280
+ radius: '1r',
281
+ });
282
+
283
+ return <div className={className}>Styled content</div>;
284
+ }
285
+ ```
286
+
287
+ ### useGlobalStyles
288
+
289
+ Inject global styles for a CSS selector:
290
+
291
+ ```tsx
292
+ import { useGlobalStyles } from '@tenphi/tasty';
293
+
294
+ function ThemeStyles() {
295
+ useGlobalStyles('.card', {
296
+ padding: '4x',
297
+ fill: '#surface',
298
+ radius: '1r',
299
+ });
300
+
301
+ return null;
302
+ }
303
+ ```
304
+
305
+ ### useRawCSS
306
+
307
+ Inject raw CSS strings:
308
+
309
+ ```tsx
310
+ import { useRawCSS } from '@tenphi/tasty';
311
+
312
+ function GlobalReset() {
313
+ useRawCSS(`
314
+ body { margin: 0; padding: 0; }
315
+ `);
316
+
317
+ return null;
318
+ }
319
+ ```
320
+
321
+ ### useKeyframes
322
+
323
+ Inject `@keyframes` rules and return the generated animation name:
324
+
325
+ ```tsx
326
+ import { useKeyframes } from '@tenphi/tasty';
327
+
328
+ function Spinner() {
329
+ const spin = useKeyframes(
330
+ {
331
+ from: { transform: 'rotate(0deg)' },
332
+ to: { transform: 'rotate(360deg)' },
333
+ },
334
+ { name: 'spin' }
335
+ );
336
+
337
+ return <div style={{ animation: `${spin} 1s linear infinite` }} />;
338
+ }
339
+ ```
340
+
341
+ `useKeyframes()` also supports a factory function with dependencies:
342
+
343
+ ```tsx
344
+ function Pulse({ scale }: { scale: number }) {
345
+ const pulse = useKeyframes(
346
+ () => ({
347
+ '0%': { transform: 'scale(1)' },
348
+ '100%': { transform: `scale(${scale})` },
349
+ }),
350
+ [scale]
351
+ );
352
+
353
+ return <div style={{ animation: `${pulse} 500ms ease-in-out alternate infinite` }} />;
354
+ }
355
+ ```
356
+
357
+ ### useProperty
358
+
359
+ Register a CSS `@property` rule so a custom property can animate smoothly:
360
+
361
+ ```tsx
362
+ import { useProperty } from '@tenphi/tasty';
363
+
364
+ function Spinner() {
365
+ useProperty('$rotation', {
366
+ syntax: '<angle>',
367
+ inherits: false,
368
+ initialValue: '0deg',
369
+ });
370
+
371
+ return <div style={{ transform: 'rotate(var(--rotation))' }} />;
372
+ }
373
+ ```
374
+
375
+ `useProperty()` accepts Tasty token syntax for the property name:
376
+
377
+ - `$name` defines `--name`
378
+ - `#name` defines `--name-color` and auto-infers `<color>`
379
+ - `--name` is also supported for existing CSS variables
380
+
381
+ ### useFontFace
382
+
383
+ Inject `@font-face` rules for custom fonts. Permanent — no cleanup on unmount. Deduplicates by content.
384
+
385
+ ```tsx
386
+ import { useFontFace } from '@tenphi/tasty';
387
+
388
+ function App() {
389
+ useFontFace('Brand Sans', {
390
+ src: 'url("/fonts/brand-sans.woff2") format("woff2")',
391
+ fontWeight: '400 700',
392
+ fontDisplay: 'swap',
393
+ });
394
+
395
+ return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
396
+ }
397
+ ```
398
+
399
+ For multiple weights/styles, pass an array:
400
+
401
+ ```tsx
402
+ useFontFace('Brand Sans', [
403
+ { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
404
+ { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
405
+ ]);
406
+ ```
407
+
408
+ Signature:
409
+
410
+ ```ts
411
+ function useFontFace(family: string, input: FontFaceInput): void;
412
+ ```
413
+
414
+ ### useCounterStyle
415
+
416
+ Inject a `@counter-style` rule and get back the counter style name. Permanent — no cleanup on unmount. Deduplicates by name.
417
+
418
+ ```tsx
419
+ import { useCounterStyle } from '@tenphi/tasty';
420
+
421
+ function EmojiList() {
422
+ const styleName = useCounterStyle({
423
+ system: 'cyclic',
424
+ symbols: '"👍"',
425
+ suffix: '" "',
426
+ }, { name: 'thumbs' });
427
+
428
+ return (
429
+ <ol style={{ listStyleType: styleName }}>
430
+ <li>First</li>
431
+ <li>Second</li>
432
+ </ol>
433
+ );
434
+ }
435
+ ```
436
+
437
+ Factory form with dependencies:
438
+
439
+ ```tsx
440
+ function DynamicList({ marker }: { marker: string }) {
441
+ const styleName = useCounterStyle(
442
+ () => ({
443
+ system: 'cyclic',
444
+ symbols: `"${marker}"`,
445
+ suffix: '" "',
446
+ }),
447
+ [marker],
448
+ );
449
+
450
+ return <ol style={{ listStyleType: styleName }}>...</ol>;
451
+ }
452
+ ```
453
+
454
+ Signatures:
455
+
456
+ ```ts
457
+ function useCounterStyle(
458
+ descriptors: CounterStyleDescriptors,
459
+ options?: { name?: string; root?: Document | ShadowRoot },
460
+ ): string;
461
+
462
+ function useCounterStyle(
463
+ factory: () => CounterStyleDescriptors,
464
+ deps: readonly unknown[],
465
+ options?: { name?: string; root?: Document | ShadowRoot },
466
+ ): string;
467
+ ```
468
+
469
+ ### Troubleshooting
470
+
471
+ - 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).
472
+ - SSR output looks wrong: check the [SSR guide](ssr.md) because the hooks integrate with SSR collectors differently than the client-only runtime path.
473
+ - Animation/custom property issues: prefer `useKeyframes()` and `useProperty()` over raw CSS when you want Tasty to manage injection and SSR collection for you.
474
+
475
+ ---
476
+
477
+ ## Learn more
478
+
479
+ - **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
480
+ - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
481
+ - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
482
+ - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
483
+ - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin
484
+ - **[Server-Side Rendering](ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks
485
+ - **[Debug Utilities](debug.md)** — Inspect injected CSS, cache state, and active styles at runtime