@tenphi/tasty 0.0.0-snapshot.fafcbfe → 0.0.0-snapshot.fdb16ea

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 (309) hide show
  1. package/README.md +397 -185
  2. package/dist/async-storage-DKK-wTD4.js +44 -0
  3. package/dist/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/babel-CMYf4JZ2.d.ts +83 -0
  5. package/dist/collector-Ctn5s74s.d.ts +145 -0
  6. package/dist/collector-DMSR6ANK.js +304 -0
  7. package/dist/collector-DMSR6ANK.js.map +1 -0
  8. package/dist/config-B23gZrfP.js +12317 -0
  9. package/dist/config-B23gZrfP.js.map +1 -0
  10. package/dist/config-CZx8_DCu.d.ts +1296 -0
  11. package/dist/context-CA8YKeMn.js +24 -0
  12. package/dist/context-CA8YKeMn.js.map +1 -0
  13. package/dist/core/index.d.ts +5 -33
  14. package/dist/core/index.js +6 -26
  15. package/dist/core-B1tyn69d.js +1573 -0
  16. package/dist/core-B1tyn69d.js.map +1 -0
  17. package/dist/css-writer-CvAxkR6S.js +390 -0
  18. package/dist/css-writer-CvAxkR6S.js.map +1 -0
  19. package/dist/format-global-rules-DklyaXv-.js +22 -0
  20. package/dist/format-global-rules-DklyaXv-.js.map +1 -0
  21. package/dist/format-rules-BCTWVvcS.js +130 -0
  22. package/dist/format-rules-BCTWVvcS.js.map +1 -0
  23. package/dist/hydrate-BCPvkJj3.js +37 -0
  24. package/dist/hydrate-BCPvkJj3.js.map +1 -0
  25. package/dist/index-B3o0aCYV.d.ts +1538 -0
  26. package/dist/index-CsvB0EXZ.d.ts +1909 -0
  27. package/dist/index.d.ts +5 -40
  28. package/dist/index.js +921 -32
  29. package/dist/index.js.map +1 -0
  30. package/dist/keyframes-Dxdx3KOo.js +493 -0
  31. package/dist/keyframes-Dxdx3KOo.js.map +1 -0
  32. package/dist/{utils/merge-styles.js → merge-styles-C-uwpoNW.js} +4 -6
  33. package/dist/merge-styles-C-uwpoNW.js.map +1 -0
  34. package/dist/{utils/merge-styles.d.ts → merge-styles-DJTSfz_M.d.ts} +3 -3
  35. package/dist/{utils/resolve-recipes.js → resolve-recipes-BuOvSEa2.js} +5 -8
  36. package/dist/resolve-recipes-BuOvSEa2.js.map +1 -0
  37. package/dist/ssr/astro-client.d.ts +1 -0
  38. package/dist/ssr/astro-client.js +19 -0
  39. package/dist/ssr/astro-client.js.map +1 -0
  40. package/dist/ssr/astro-middleware-static.d.ts +16 -0
  41. package/dist/ssr/astro-middleware-static.js +18 -0
  42. package/dist/ssr/astro-middleware-static.js.map +1 -0
  43. package/dist/ssr/astro-middleware.d.ts +17 -0
  44. package/dist/ssr/astro-middleware.js +19 -0
  45. package/dist/ssr/astro-middleware.js.map +1 -0
  46. package/dist/ssr/astro.d.ts +93 -0
  47. package/dist/ssr/astro.js +157 -0
  48. package/dist/ssr/astro.js.map +1 -0
  49. package/dist/ssr/index.d.ts +37 -0
  50. package/dist/ssr/index.js +10 -0
  51. package/dist/ssr/index.js.map +1 -0
  52. package/dist/ssr/next.d.ts +45 -0
  53. package/dist/ssr/next.js +74 -0
  54. package/dist/ssr/next.js.map +1 -0
  55. package/dist/static/index.d.ts +91 -5
  56. package/dist/static/index.js +49 -4
  57. package/dist/static/index.js.map +1 -0
  58. package/dist/static/inject.d.ts +5 -0
  59. package/dist/static/inject.js +17 -0
  60. package/dist/static/inject.js.map +1 -0
  61. package/dist/zero/babel.d.ts +2 -108
  62. package/dist/zero/babel.js +235 -39
  63. package/dist/zero/babel.js.map +1 -1
  64. package/dist/zero/index.d.ts +81 -3
  65. package/dist/zero/index.js +2 -4
  66. package/dist/zero/next.d.ts +56 -30
  67. package/dist/zero/next.js +106 -41
  68. package/dist/zero/next.js.map +1 -1
  69. package/docs/README.md +39 -0
  70. package/docs/adoption.md +323 -0
  71. package/docs/ai-agents.md +224 -0
  72. package/docs/comparison.md +448 -0
  73. package/docs/configuration.md +823 -0
  74. package/docs/debug.md +322 -0
  75. package/docs/design-system.md +455 -0
  76. package/docs/dsl.md +1021 -0
  77. package/docs/getting-started.md +238 -0
  78. package/docs/injector.md +614 -0
  79. package/docs/methodology.md +648 -0
  80. package/docs/migration-v3.md +285 -0
  81. package/docs/pipeline.md +741 -0
  82. package/docs/plugins.md +353 -0
  83. package/docs/react-api.md +681 -0
  84. package/docs/runtime-benchmarks.md +216 -0
  85. package/docs/ssr.md +451 -0
  86. package/docs/styles.md +645 -0
  87. package/docs/tasty-static.md +582 -0
  88. package/package.json +149 -40
  89. package/tasty.config.ts +6 -0
  90. package/dist/_virtual/_rolldown/runtime.js +0 -8
  91. package/dist/chunks/cacheKey.js +0 -70
  92. package/dist/chunks/cacheKey.js.map +0 -1
  93. package/dist/chunks/definitions.d.ts +0 -37
  94. package/dist/chunks/definitions.js +0 -260
  95. package/dist/chunks/definitions.js.map +0 -1
  96. package/dist/chunks/renderChunk.js +0 -61
  97. package/dist/chunks/renderChunk.js.map +0 -1
  98. package/dist/config.d.ts +0 -280
  99. package/dist/config.js +0 -403
  100. package/dist/config.js.map +0 -1
  101. package/dist/debug.d.ts +0 -204
  102. package/dist/debug.js +0 -733
  103. package/dist/debug.js.map +0 -1
  104. package/dist/hooks/useGlobalStyles.d.ts +0 -27
  105. package/dist/hooks/useGlobalStyles.js +0 -56
  106. package/dist/hooks/useGlobalStyles.js.map +0 -1
  107. package/dist/hooks/useKeyframes.d.ts +0 -56
  108. package/dist/hooks/useKeyframes.js +0 -54
  109. package/dist/hooks/useKeyframes.js.map +0 -1
  110. package/dist/hooks/useProperty.d.ts +0 -79
  111. package/dist/hooks/useProperty.js +0 -91
  112. package/dist/hooks/useProperty.js.map +0 -1
  113. package/dist/hooks/useRawCSS.d.ts +0 -53
  114. package/dist/hooks/useRawCSS.js +0 -28
  115. package/dist/hooks/useRawCSS.js.map +0 -1
  116. package/dist/hooks/useStyles.d.ts +0 -40
  117. package/dist/hooks/useStyles.js +0 -169
  118. package/dist/hooks/useStyles.js.map +0 -1
  119. package/dist/injector/index.d.ts +0 -157
  120. package/dist/injector/index.js +0 -154
  121. package/dist/injector/index.js.map +0 -1
  122. package/dist/injector/injector.d.ts +0 -139
  123. package/dist/injector/injector.js +0 -404
  124. package/dist/injector/injector.js.map +0 -1
  125. package/dist/injector/sheet-manager.d.ts +0 -127
  126. package/dist/injector/sheet-manager.js +0 -714
  127. package/dist/injector/sheet-manager.js.map +0 -1
  128. package/dist/injector/types.d.ts +0 -135
  129. package/dist/keyframes/index.js +0 -206
  130. package/dist/keyframes/index.js.map +0 -1
  131. package/dist/parser/classify.js +0 -319
  132. package/dist/parser/classify.js.map +0 -1
  133. package/dist/parser/const.js +0 -33
  134. package/dist/parser/const.js.map +0 -1
  135. package/dist/parser/lru.js +0 -109
  136. package/dist/parser/lru.js.map +0 -1
  137. package/dist/parser/parser.d.ts +0 -25
  138. package/dist/parser/parser.js +0 -116
  139. package/dist/parser/parser.js.map +0 -1
  140. package/dist/parser/tokenizer.js +0 -69
  141. package/dist/parser/tokenizer.js.map +0 -1
  142. package/dist/parser/types.d.ts +0 -51
  143. package/dist/parser/types.js +0 -46
  144. package/dist/parser/types.js.map +0 -1
  145. package/dist/pipeline/conditions.d.ts +0 -134
  146. package/dist/pipeline/conditions.js +0 -400
  147. package/dist/pipeline/conditions.js.map +0 -1
  148. package/dist/pipeline/exclusive.js +0 -231
  149. package/dist/pipeline/exclusive.js.map +0 -1
  150. package/dist/pipeline/index.d.ts +0 -53
  151. package/dist/pipeline/index.js +0 -641
  152. package/dist/pipeline/index.js.map +0 -1
  153. package/dist/pipeline/materialize.js +0 -908
  154. package/dist/pipeline/materialize.js.map +0 -1
  155. package/dist/pipeline/parseStateKey.d.ts +0 -15
  156. package/dist/pipeline/parseStateKey.js +0 -430
  157. package/dist/pipeline/parseStateKey.js.map +0 -1
  158. package/dist/pipeline/simplify.js +0 -557
  159. package/dist/pipeline/simplify.js.map +0 -1
  160. package/dist/plugins/okhsl-plugin.d.ts +0 -35
  161. package/dist/plugins/okhsl-plugin.js +0 -371
  162. package/dist/plugins/okhsl-plugin.js.map +0 -1
  163. package/dist/plugins/types.d.ts +0 -69
  164. package/dist/properties/index.js +0 -158
  165. package/dist/properties/index.js.map +0 -1
  166. package/dist/states/index.d.ts +0 -49
  167. package/dist/states/index.js +0 -416
  168. package/dist/states/index.js.map +0 -1
  169. package/dist/static/tastyStatic.d.ts +0 -46
  170. package/dist/static/tastyStatic.js +0 -31
  171. package/dist/static/tastyStatic.js.map +0 -1
  172. package/dist/static/types.d.ts +0 -49
  173. package/dist/static/types.js +0 -24
  174. package/dist/static/types.js.map +0 -1
  175. package/dist/styles/align.d.ts +0 -15
  176. package/dist/styles/align.js +0 -14
  177. package/dist/styles/align.js.map +0 -1
  178. package/dist/styles/border.d.ts +0 -25
  179. package/dist/styles/border.js +0 -114
  180. package/dist/styles/border.js.map +0 -1
  181. package/dist/styles/color.d.ts +0 -14
  182. package/dist/styles/color.js +0 -23
  183. package/dist/styles/color.js.map +0 -1
  184. package/dist/styles/createStyle.js +0 -77
  185. package/dist/styles/createStyle.js.map +0 -1
  186. package/dist/styles/dimension.js +0 -97
  187. package/dist/styles/dimension.js.map +0 -1
  188. package/dist/styles/display.d.ts +0 -37
  189. package/dist/styles/display.js +0 -67
  190. package/dist/styles/display.js.map +0 -1
  191. package/dist/styles/fade.d.ts +0 -15
  192. package/dist/styles/fade.js +0 -58
  193. package/dist/styles/fade.js.map +0 -1
  194. package/dist/styles/fill.d.ts +0 -44
  195. package/dist/styles/fill.js +0 -51
  196. package/dist/styles/fill.js.map +0 -1
  197. package/dist/styles/flow.d.ts +0 -16
  198. package/dist/styles/flow.js +0 -12
  199. package/dist/styles/flow.js.map +0 -1
  200. package/dist/styles/gap.d.ts +0 -31
  201. package/dist/styles/gap.js +0 -37
  202. package/dist/styles/gap.js.map +0 -1
  203. package/dist/styles/height.d.ts +0 -17
  204. package/dist/styles/height.js +0 -20
  205. package/dist/styles/height.js.map +0 -1
  206. package/dist/styles/index.d.ts +0 -2
  207. package/dist/styles/index.js +0 -9
  208. package/dist/styles/index.js.map +0 -1
  209. package/dist/styles/inset.d.ts +0 -52
  210. package/dist/styles/inset.js +0 -150
  211. package/dist/styles/inset.js.map +0 -1
  212. package/dist/styles/justify.d.ts +0 -15
  213. package/dist/styles/justify.js +0 -14
  214. package/dist/styles/justify.js.map +0 -1
  215. package/dist/styles/list.d.ts +0 -16
  216. package/dist/styles/list.js +0 -98
  217. package/dist/styles/list.js.map +0 -1
  218. package/dist/styles/margin.d.ts +0 -24
  219. package/dist/styles/margin.js +0 -104
  220. package/dist/styles/margin.js.map +0 -1
  221. package/dist/styles/outline.d.ts +0 -29
  222. package/dist/styles/outline.js +0 -65
  223. package/dist/styles/outline.js.map +0 -1
  224. package/dist/styles/padding.d.ts +0 -24
  225. package/dist/styles/padding.js +0 -104
  226. package/dist/styles/padding.js.map +0 -1
  227. package/dist/styles/predefined.d.ts +0 -73
  228. package/dist/styles/predefined.js +0 -241
  229. package/dist/styles/predefined.js.map +0 -1
  230. package/dist/styles/preset.d.ts +0 -47
  231. package/dist/styles/preset.js +0 -126
  232. package/dist/styles/preset.js.map +0 -1
  233. package/dist/styles/radius.d.ts +0 -14
  234. package/dist/styles/radius.js +0 -51
  235. package/dist/styles/radius.js.map +0 -1
  236. package/dist/styles/scrollbar.d.ts +0 -21
  237. package/dist/styles/scrollbar.js +0 -105
  238. package/dist/styles/scrollbar.js.map +0 -1
  239. package/dist/styles/shadow.d.ts +0 -14
  240. package/dist/styles/shadow.js +0 -24
  241. package/dist/styles/shadow.js.map +0 -1
  242. package/dist/styles/styledScrollbar.d.ts +0 -47
  243. package/dist/styles/styledScrollbar.js +0 -38
  244. package/dist/styles/styledScrollbar.js.map +0 -1
  245. package/dist/styles/transition.d.ts +0 -14
  246. package/dist/styles/transition.js +0 -158
  247. package/dist/styles/transition.js.map +0 -1
  248. package/dist/styles/types.d.ts +0 -498
  249. package/dist/styles/width.d.ts +0 -17
  250. package/dist/styles/width.js +0 -20
  251. package/dist/styles/width.js.map +0 -1
  252. package/dist/tasty.d.ts +0 -982
  253. package/dist/tasty.js +0 -191
  254. package/dist/tasty.js.map +0 -1
  255. package/dist/tokens/typography.d.ts +0 -19
  256. package/dist/tokens/typography.js +0 -237
  257. package/dist/tokens/typography.js.map +0 -1
  258. package/dist/types.d.ts +0 -184
  259. package/dist/utils/cache-wrapper.js +0 -26
  260. package/dist/utils/cache-wrapper.js.map +0 -1
  261. package/dist/utils/case-converter.js +0 -8
  262. package/dist/utils/case-converter.js.map +0 -1
  263. package/dist/utils/colors.d.ts +0 -5
  264. package/dist/utils/colors.js +0 -9
  265. package/dist/utils/colors.js.map +0 -1
  266. package/dist/utils/css-types.d.ts +0 -7
  267. package/dist/utils/dotize.d.ts +0 -26
  268. package/dist/utils/dotize.js +0 -122
  269. package/dist/utils/dotize.js.map +0 -1
  270. package/dist/utils/filter-base-props.d.ts +0 -15
  271. package/dist/utils/filter-base-props.js +0 -45
  272. package/dist/utils/filter-base-props.js.map +0 -1
  273. package/dist/utils/get-display-name.d.ts +0 -7
  274. package/dist/utils/get-display-name.js +0 -10
  275. package/dist/utils/get-display-name.js.map +0 -1
  276. package/dist/utils/hsl-to-rgb.js +0 -38
  277. package/dist/utils/hsl-to-rgb.js.map +0 -1
  278. package/dist/utils/is-dev-env.js +0 -19
  279. package/dist/utils/is-dev-env.js.map +0 -1
  280. package/dist/utils/is-valid-element-type.js +0 -15
  281. package/dist/utils/is-valid-element-type.js.map +0 -1
  282. package/dist/utils/merge-styles.js.map +0 -1
  283. package/dist/utils/mod-attrs.d.ts +0 -8
  284. package/dist/utils/mod-attrs.js +0 -21
  285. package/dist/utils/mod-attrs.js.map +0 -1
  286. package/dist/utils/okhsl-to-rgb.js +0 -296
  287. package/dist/utils/okhsl-to-rgb.js.map +0 -1
  288. package/dist/utils/process-tokens.d.ts +0 -31
  289. package/dist/utils/process-tokens.js +0 -171
  290. package/dist/utils/process-tokens.js.map +0 -1
  291. package/dist/utils/resolve-recipes.d.ts +0 -17
  292. package/dist/utils/resolve-recipes.js.map +0 -1
  293. package/dist/utils/string.js +0 -8
  294. package/dist/utils/string.js.map +0 -1
  295. package/dist/utils/styles.d.ts +0 -183
  296. package/dist/utils/styles.js +0 -585
  297. package/dist/utils/styles.js.map +0 -1
  298. package/dist/utils/typography.d.ts +0 -36
  299. package/dist/utils/typography.js +0 -53
  300. package/dist/utils/typography.js.map +0 -1
  301. package/dist/utils/warnings.d.ts +0 -16
  302. package/dist/utils/warnings.js +0 -16
  303. package/dist/utils/warnings.js.map +0 -1
  304. package/dist/zero/css-writer.d.ts +0 -45
  305. package/dist/zero/css-writer.js +0 -74
  306. package/dist/zero/css-writer.js.map +0 -1
  307. package/dist/zero/extractor.d.ts +0 -24
  308. package/dist/zero/extractor.js +0 -150
  309. package/dist/zero/extractor.js.map +0 -1
@@ -0,0 +1,648 @@
1
+ # Methodology
2
+
3
+ State maps keep a property’s branches from fighting. This guide applies the same goal to component architecture: keep state ownership explicit, expose intentional public APIs, and make extension predictable as a component system grows.
4
+
5
+ The patterns are recommended rather than required. Tasty works without them, but they help design-system teams carry its state-resolution guarantees across roots, sub-elements, typed props, tokens, and styled wrappers.
6
+
7
+ ---
8
+
9
+ ## Component architecture: root + sub-elements
10
+
11
+ This model matters most for design-system authors and platform teams building reusable, stateful components. It turns Tasty's selector guarantees into a component architecture that stays predictable as states, variants, and compound parts accumulate.
12
+
13
+ ### The model
14
+
15
+ Every Tasty component has a **root element** and zero or more **sub-elements**. The root owns the state context. Sub-elements participate in the same context by default.
16
+
17
+ ```tsx
18
+ const Alert = tasty({
19
+ styles: {
20
+ padding: '3x',
21
+ fill: { '': '#surface', 'type=danger': '#danger.10' },
22
+ border: { '': '1bw solid #border', 'type=danger': '1bw solid #danger' },
23
+ radius: '1r',
24
+
25
+ Icon: {
26
+ color: { '': '#text-secondary', 'type=danger': '#danger' },
27
+ width: '3x',
28
+ height: '3x',
29
+ },
30
+ Message: {
31
+ preset: 't2',
32
+ color: '#text',
33
+ },
34
+ },
35
+ elements: { Icon: 'span', Message: 'div' },
36
+ });
37
+ ```
38
+
39
+ When `<Alert mods={{ type: 'danger' }}>` is rendered, the root gets `data-type="danger"` and **all** sub-elements react to it through their state maps. The `Icon` turns `#danger`, the border changes — from a single modifier on the root.
40
+
41
+ ### How this differs from BEM
42
+
43
+ BEM organizes CSS around blocks, elements, and modifiers. Each element applies its own modifier classes independently:
44
+
45
+ ```html
46
+ <!-- BEM: each element carries its own modifier -->
47
+ <div class="alert alert--danger">
48
+ <span class="alert__icon alert__icon--danger">!</span>
49
+ <div class="alert__message">Something went wrong</div>
50
+ </div>
51
+ ```
52
+
53
+ In BEM, `alert__icon--danger` is a separate class that must be applied to the icon element explicitly. The block modifier `alert--danger` does not automatically propagate to elements — each element needs its own modifier class, and the CSS for each element+modifier combination is written separately.
54
+
55
+ In Tasty, sub-elements inherit the root's state context automatically:
56
+
57
+ ```tsx
58
+ <Alert mods={{ type: 'danger' }}>
59
+ <Alert.Icon>!</Alert.Icon>
60
+ <Alert.Message>Something went wrong</Alert.Message>
61
+ </Alert>
62
+ ```
63
+
64
+ One `mods` prop on the root. No modifier classes on sub-elements. The CSS for `type=danger` is declared once per property, and every sub-element that references that state key reacts to it.
65
+
66
+ This is the fundamental design choice: **state flows from root to sub-elements**, not from each element independently.
67
+
68
+ ### When sub-elements need their own state
69
+
70
+ Use `@own(...)` when a sub-element should react to its own state rather than the root's:
71
+
72
+ ```tsx
73
+ const Nav = tasty({
74
+ styles: {
75
+ NavItem: {
76
+ color: {
77
+ '': '#text',
78
+ '@own(:hover)': '#primary',
79
+ '@own(:focus-visible)': '#primary',
80
+ selected: '#primary',
81
+ },
82
+ },
83
+ },
84
+ elements: { NavItem: 'a' },
85
+ });
86
+ ```
87
+
88
+ Here, `:hover` and `:focus-visible` belong to the individual `NavItem` being hovered, not the root `Nav`. But `selected` is still a root-level modifier — a parent component controls which item is selected.
89
+
90
+ The default (root state context) is the right choice most of the time. Use `@own()` only when the sub-element has interactive states that are independent of the root.
91
+
92
+ ---
93
+
94
+ ## styleProps as the public API
95
+
96
+ `styleProps` define which CSS properties a component exposes as typed React props. They are the primary mechanism for product engineers to customize a component without breaking its design constraints.
97
+
98
+ ```tsx
99
+ const Space = tasty({
100
+ as: 'div',
101
+ styles: { display: 'flex', flow: 'column', gap: '1x' },
102
+ styleProps: ['flow', 'gap', 'padding', 'fill', 'placeItems', 'placeContent'],
103
+ });
104
+
105
+ // Product engineer uses it:
106
+ <Space flow="row" gap="2x" padding="4x" placeItems="center">
107
+ ```
108
+
109
+ Style props accept state maps, so responsive values work through the same API:
110
+
111
+ ```tsx
112
+ <Space
113
+ flow={{ '': 'column', '@tablet': 'row' }}
114
+ gap={{ '': '2x', '@tablet': '4x' }}
115
+ >
116
+ ```
117
+
118
+ ### Choosing what to expose
119
+
120
+ Tasty exports predefined style prop lists that group properties by role. Use them instead of hand-picking arrays:
121
+
122
+ | Preset | Properties | Typical use |
123
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
124
+ | `FLOW_STYLES` | flow, gap, columnGap, rowGap, align, justify, placeItems, placeContent, alignItems, alignContent, justifyItems, justifyContent, gridColumns, gridRows, gridTemplate, gridAreas | Layout containers (`Space`, `Grid`) |
125
+ | `POSITION_STYLES` | gridArea, gridColumn, gridRow, order, placeSelf, alignSelf, justifySelf, zIndex, margin, inset, position | Positioned elements (`Button`, `Badge`) |
126
+ | `DIMENSION_STYLES` | width, height, flexBasis, flexGrow, flexShrink, flex | Sized elements |
127
+ | `COLOR_STYLES` | color, fill, fade, image | Color-customizable elements |
128
+ | `BLOCK_STYLES` | padding, paddingInline, paddingBlock, overflow, scrollbar, textAlign, border, radius, shadow, outline | Block-level containers |
129
+ | `CONTAINER_STYLES` | All of the above combined (+ BASE_STYLES) | Fully flexible containers |
130
+ | `OUTER_STYLES` | POSITION_STYLES + DIMENSION_STYLES + block outer (border, radius, shadow, outline) | Components whose outer shell is customizable |
131
+ | `INNER_STYLES` | BASE_STYLES + COLOR_STYLES + block inner (padding, overflow, scrollbar) + FLOW_STYLES | Components whose inner layout is customizable |
132
+
133
+ ```tsx
134
+ import { tasty, FLOW_STYLES, POSITION_STYLES } from '@tenphi/tasty';
135
+
136
+ const Space = tasty({
137
+ as: 'div',
138
+ styles: { display: 'flex', flow: 'column', gap: '1x' },
139
+ styleProps: FLOW_STYLES,
140
+ });
141
+
142
+ const Button = tasty({
143
+ as: 'button',
144
+ styles: { padding: '1.5x 3x', fill: '#primary', radius: true },
145
+ styleProps: POSITION_STYLES,
146
+ });
147
+ ```
148
+
149
+ You can also combine presets or mix them with individual properties:
150
+
151
+ ```tsx
152
+ styleProps: [...FLOW_STYLES, ...DIMENSION_STYLES, 'fill'],
153
+ ```
154
+
155
+ Match the preset to the component's role:
156
+
157
+ - **Layout containers** (`Space`, `Box`, `Grid`) — `FLOW_STYLES`, optionally with `DIMENSION_STYLES`
158
+ - **Positioned elements** (`Button`, `Badge`) — `POSITION_STYLES`
159
+ - **Text elements** — custom: `['preset', 'color']`
160
+ - **Compound components** — typically none; styling happens via sub-elements and extension
161
+
162
+ ### The governance trade-off
163
+
164
+ Exposing every CSS property as a prop defeats the purpose of a design system. The more props a component exposes, the more ways product engineers can deviate from the intended design. A good rule of thumb: expose props that product engineers _need_ to adjust for layout and composition, and keep visual identity (colors, borders, typography) controlled through the component definition, variants, or styled wrappers.
165
+
166
+ ---
167
+
168
+ ## modProps and mods
169
+
170
+ `modProps` expose modifier keys as top-level component props — the modifier equivalent of `styleProps`. Use them when a component has a fixed set of known state modifiers.
171
+
172
+ ```tsx
173
+ const Card = tasty({
174
+ modProps: {
175
+ isLoading: Boolean,
176
+ isSelected: Boolean,
177
+ },
178
+ styles: {
179
+ fill: { '': '#surface', isLoading: '#surface.5' },
180
+ border: { '': '1bw solid #outline', isSelected: '2bw solid #primary' },
181
+ },
182
+ });
183
+
184
+ // Clean prop API — no mods object needed
185
+ <Card isLoading isSelected>
186
+ Content
187
+ </Card>;
188
+ ```
189
+
190
+ ### When to use which
191
+
192
+ | Pattern | Use when |
193
+ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
194
+ | `modProps` | The component has a fixed set of known boolean/string states that drive styles. Provides TypeScript autocomplete and a cleaner JSX API. |
195
+ | `mods` prop | The component needs arbitrary or dynamic modifiers that aren't known at definition time. |
196
+ | Both | Combine `modProps` for the known states and `mods` for ad-hoc overrides. Mod props take precedence. |
197
+ | `styleProps` | Exposing CSS properties (layout, sizing) for customization — different from modifiers. |
198
+
199
+ ### Typed modProps vs array form
200
+
201
+ The object form gives precise TypeScript types using JS constructors (`Boolean`, `String`, `Number`) or enum arrays:
202
+
203
+ ```tsx
204
+ const Button = tasty({
205
+ modProps: {
206
+ isLoading: Boolean,
207
+ size: ['small', 'medium', 'large'] as const,
208
+ },
209
+ // ...
210
+ });
211
+
212
+ // TypeScript knows: isLoading?: boolean, size?: 'small' | 'medium' | 'large'
213
+ ```
214
+
215
+ The array form is simpler but types all values as `ModValue`:
216
+
217
+ ```tsx
218
+ modProps: ['isLoading', 'isSelected'] as const,
219
+ ```
220
+
221
+ For the full API reference, see [React API — Mod Props](react-api.md#mod-props).
222
+
223
+ ---
224
+
225
+ ## tokens prop for dynamic values
226
+
227
+ Every Tasty component accepts a `tokens` prop that renders as inline CSS custom properties on the element. This is the mechanism for per-instance dynamic values.
228
+
229
+ ```tsx
230
+ const ProgressBar = tasty({
231
+ styles: {
232
+ width: '100%',
233
+ height: '1x',
234
+ fill: '#surface',
235
+ Bar: {
236
+ width: '$progress',
237
+ height: '100%',
238
+ fill: '#primary',
239
+ transition: 'width 0.3s',
240
+ },
241
+ },
242
+ elements: { Bar: 'div' },
243
+ });
244
+
245
+ // Usage: the progress value comes from a prop, not from styles
246
+ <ProgressBar tokens={{ $progress: `${percent}%` }} />;
247
+ ```
248
+
249
+ The `tokens` prop sets `style="--progress: 75%"` on the DOM element. The `$progress` reference in styles maps to `var(--progress)`, so the bar width updates without regenerating any CSS.
250
+
251
+ ### When to use tokens vs other mechanisms
252
+
253
+ | Need | Use |
254
+ | ----------------------------------------------------------------------------- | --------------------------------------------------------- |
255
+ | Value changes per instance at render time (progress, user color, avatar size) | `tokens` prop (on component) |
256
+ | Value is constant across all instances (card padding, border radius) | `configure({ tokens })` for `:root` CSS custom properties |
257
+ | Value should be inlined at parse time (alias for another token) | `configure({ replaceTokens })` |
258
+ | Value changes based on component state (hover, disabled, breakpoint) | State map in `styles` |
259
+ | Value changes based on a variant (primary, danger, outline) | `variants` option |
260
+
261
+ Design tokens (via `configure({ tokens })`) are injected as CSS custom properties on `:root`. Replace tokens (via `configure({ replaceTokens })`) are resolved at parse time and baked into the generated CSS. The `tokens` prop on components is resolved at render time via inline CSS custom properties. Use design tokens for design-system constants, replace tokens for value aliases, and the `tokens` prop for truly dynamic per-instance values.
262
+
263
+ ### tokenProps
264
+
265
+ `tokenProps` expose token keys as top-level component props — the token equivalent of `styleProps` and `modProps`. Use them when a component has a fixed set of known dynamic token values.
266
+
267
+ #### Array form
268
+
269
+ Prop names are plain camelCase identifiers. Names ending in `Color` map to `#` color tokens; everything else maps to `$` custom property tokens:
270
+
271
+ ```tsx
272
+ const ProgressBar = tasty({
273
+ tokenProps: ['progress', 'accentColor'] as const,
274
+ styles: { width: '$progress', fill: '#accent' },
275
+ });
276
+
277
+ // Clean prop API — no tokens object needed
278
+ <ProgressBar progress="75%" accentColor="#purple" />;
279
+
280
+ // Conversion:
281
+ // 'progress' → $progress → --progress
282
+ // 'accentColor' → #accent → --accent-color
283
+ ```
284
+
285
+ #### Object form
286
+
287
+ Keys are prop names; values are `$`/`#`-prefixed token keys. No suffix convention needed — the prefix in the value is explicit:
288
+
289
+ ```tsx
290
+ const Card = tasty({
291
+ tokenProps: {
292
+ size: '$card-size',
293
+ color: '#card-accent',
294
+ },
295
+ styles: { padding: '$card-size', fill: '#card-accent' },
296
+ });
297
+
298
+ <Card size="4x" color="#purple" />;
299
+ ```
300
+
301
+ #### Merge order
302
+
303
+ When all three token sources are present, values merge with increasing priority:
304
+
305
+ 1. `tokens` option in `tasty({...})` — default tokens (lowest)
306
+ 2. `tokens` prop on the component instance — runtime overrides
307
+ 3. `tokenProps`-derived values — highest priority (explicit named props win)
308
+
309
+ The `tokens` prop remains available for ad-hoc or dynamic tokens alongside `tokenProps`.
310
+
311
+ ---
312
+
313
+ ## styles prop vs style prop
314
+
315
+ Tasty components accept both `styles` and `style`, but they serve very different purposes.
316
+
317
+ ### styles — Tasty extension mechanism
318
+
319
+ The `styles` prop is processed through the full Tasty pipeline. Tokens, custom units, state maps, sub-element keys — everything works:
320
+
321
+ ```tsx
322
+ <Card styles={{ padding: '6x', Title: { color: '#danger' } }} />
323
+ ```
324
+
325
+ However, **using `styles` directly is discouraged in design-system code.** The recommended pattern is to create a styled wrapper instead:
326
+
327
+ ```tsx
328
+ // Preferred: create a styled wrapper
329
+ const LargeCard = tasty(Card, {
330
+ styles: { padding: '6x', Title: { color: '#danger' } },
331
+ });
332
+
333
+ <LargeCard />;
334
+ ```
335
+
336
+ Why? Styled wrappers are:
337
+
338
+ - **Faster** — styles are parsed and injected once at definition time, not on every render
339
+ - **Stable** — the style object is defined once, not recreated on every render
340
+ - **Composable** — another engineer can extend `LargeCard` further
341
+ - **Inspectable** — the component has a name in React DevTools
342
+ - **Lint-friendly** — the ESLint plugin's `no-styles-prop` rule flags direct usage
343
+
344
+ The `styles` prop exists as an escape hatch — for prototyping, one-off overrides during development, or cases where wrapping is impractical. It should not be the default way product engineers customize components.
345
+
346
+ ### style — React inline styles (escape hatch)
347
+
348
+ The `style` prop is standard React `CSSProperties`. It bypasses Tasty entirely — no tokens, no units, no state maps:
349
+
350
+ ```tsx
351
+ <Card style={{ marginTop: 16 }} />
352
+ ```
353
+
354
+ Reserve `style` for third-party library integration where you need to set CSS properties that Tasty does not control (e.g. a library that reads inline `style` for positioning). Never use `style` as a styling mechanism for your own components.
355
+
356
+ See [Best practices](#best-practices) below for the full list of do's and don'ts.
357
+
358
+ ---
359
+
360
+ ## Wrapping and extension
361
+
362
+ `tasty(Base, { styles })` is the primary extension mechanism. It creates a new component whose styles are merged with the base component's styles.
363
+
364
+ ```tsx
365
+ import { Button } from 'my-ds';
366
+
367
+ const DangerButton = tasty(Button, {
368
+ styles: {
369
+ fill: { '': '#danger', ':hover': '#danger-hover' },
370
+ color: '#danger-text',
371
+ },
372
+ });
373
+ ```
374
+
375
+ ### Extend mode vs replace mode
376
+
377
+ Merge behavior depends on whether the child provides a `''` (default) key in a state map:
378
+
379
+ - **No `''` key** — extend mode: parent states are preserved, child adds or overrides specific states
380
+ - **Has `''` key** — replace mode: child defines everything from scratch for that property
381
+
382
+ ```tsx
383
+ // Extend: adds `loading` state, overrides `disabled`, keeps parent's '' and ':hover'
384
+ tasty(Button, {
385
+ styles: {
386
+ fill: {
387
+ loading: '#yellow',
388
+ disabled: '#gray.20',
389
+ },
390
+ },
391
+ });
392
+
393
+ // Replace: provides '' key, so parent's fill states are dropped entirely
394
+ tasty(Button, {
395
+ styles: {
396
+ fill: {
397
+ '': '#danger',
398
+ ':hover': '#danger-hover',
399
+ },
400
+ },
401
+ });
402
+ ```
403
+
404
+ For full details on merge semantics, `@inherit`, `null`, and `false` tombstones, see [Style DSL — Extending vs. Replacing State Maps](dsl.md#extending-vs-replacing-state-maps).
405
+
406
+ ### Styling non-Tasty components or string tags
407
+
408
+ `tasty(Component, { ... })` always forwards every prop to `Component`, which means `Component` must be Tasty-aware (i.e. it knows how to consume `styles`, `mods`, `qa`, etc.). To apply styles to a third-party component (Next.js `Link`, `react-router`'s `Link`, Radix primitives, MUI, etc.) or to a plain DOM tag, use the options-only form with `as`:
409
+
410
+ ```tsx
411
+ import NextLink from 'next/link';
412
+
413
+ const Link = tasty({
414
+ as: NextLink,
415
+ styles: {
416
+ color: { '': '#accent-text', ':hover': '#text' },
417
+ textDecoration: 'underline',
418
+ },
419
+ styleProps: ['padding'],
420
+ });
421
+
422
+ const Span = tasty({
423
+ as: 'span',
424
+ styles: { preset: 'strong' },
425
+ });
426
+
427
+ <Link href="/blog" padding="1x">
428
+ Blog
429
+ </Link>;
430
+ ```
431
+
432
+ The wrapped component only needs to forward `className` (and ideally `style` and `ref`) to its underlying DOM node. Tasty-specific props (`qa`, `qaVal`, `mods`, `tokens`, `isDisabled`, `isHidden`, `isChecked`, and any `styleProps`/`modProps`/`tokenProps` you declared) are consumed by the wrapper and converted to `data-*` attributes or CSS custom properties — they never leak to the DOM.
433
+
434
+ ### When to use styleProps vs wrapping
435
+
436
+ If the component exposes the properties you need as `styleProps`, use them directly — that is what they are for:
437
+
438
+ ```tsx
439
+ // Card exposes padding and gap as styleProps — just use them
440
+ <Card padding="2x" gap="1x">
441
+ ```
442
+
443
+ Wrapping is for changes that go beyond what `styleProps` expose — overriding colors, adding state mappings, restyling sub-elements:
444
+
445
+ ```tsx
446
+ const DangerCard = tasty(Card, {
447
+ styles: {
448
+ border: '1bw solid #danger',
449
+ Title: { color: '#danger' },
450
+ },
451
+ });
452
+ ```
453
+
454
+ This is preferred over `<Card styles={{ border: '1bw solid #danger' }}>` because:
455
+
456
+ 1. Styles are parsed and injected once, not on every render
457
+ 2. `DangerCard` can be extended further by others
458
+ 3. It has a meaningful name in DevTools and code search
459
+ 4. The ESLint `no-styles-prop` rule encourages this pattern
460
+
461
+ ---
462
+
463
+ ## How configuration simplifies components
464
+
465
+ Tasty's `configure()` is not just setup — it directly reduces the complexity of every component in the system.
466
+
467
+ ### State aliases eliminate repetition
468
+
469
+ Without aliases, every component inlines the full query:
470
+
471
+ ```tsx
472
+ // Without aliases
473
+ padding: { '': '4x', '@media(w < 768px)': '2x' },
474
+ flow: { '': 'row', '@media(w < 768px)': 'column' },
475
+ ```
476
+
477
+ With aliases:
478
+
479
+ ```tsx
480
+ // With aliases
481
+ padding: { '': '4x', '@mobile': '2x' },
482
+ flow: { '': 'row', '@mobile': 'column' },
483
+ ```
484
+
485
+ The alias is defined once. If the breakpoint changes from `768px` to `640px`, you update one line in `configure()` and every component adjusts.
486
+
487
+ ### Recipes extract repeated patterns
488
+
489
+ Without recipes, every card-like component repeats the same base styles:
490
+
491
+ ```tsx
492
+ // Without recipes — repeated in Card, ProfileCard, SettingsPanel, ...
493
+ styles: {
494
+ padding: '4x',
495
+ fill: '#surface',
496
+ radius: '1r',
497
+ border: true,
498
+ // ...component-specific styles
499
+ }
500
+ ```
501
+
502
+ With recipes:
503
+
504
+ ```tsx
505
+ // With recipes
506
+ styles: {
507
+ recipe: 'card',
508
+ // ...component-specific styles only
509
+ }
510
+ ```
511
+
512
+ The recipe encapsulates the shared pattern. Change `card`'s radius from `1r` to `2r` and every component using it updates.
513
+
514
+ ### Design tokens enforce consistency
515
+
516
+ ```tsx
517
+ configure({
518
+ tokens: {
519
+ '$card-padding': '4x',
520
+ '$input-height': '5x',
521
+ },
522
+ });
523
+ ```
524
+
525
+ Components use `$card-padding` instead of hardcoding `4x`. If the DS team decides to change card padding, the token is the single source of truth. Tokens support state maps for theme-aware values. Token values are parsed through the Tasty DSL, so you can use units (`4x`), color syntax (`#purple`), and other DSL features in token definitions.
526
+
527
+ See [Configuration](configuration.md) for the full `configure()` API.
528
+
529
+ ---
530
+
531
+ ## Best practices
532
+
533
+ ### Do
534
+
535
+ - **Create styled wrappers** instead of passing `styles` directly — faster, composable, inspectable
536
+ - **Use design tokens and custom units** (`#text`, `2x`, `1r`) instead of raw CSS values
537
+ - **Use semantic transition names** (`transition: 'theme 0.3s'`) instead of listing CSS properties
538
+ - **Use `elements` prop** to declare typed sub-components for compound components
539
+ - **Use `styleProps`** to define what product engineers can customize
540
+ - **Use `modProps`** to expose known modifier states as clean component props
541
+ - **Use `tokenProps`** to expose known token keys as clean component props
542
+ - **Use `tokens` prop** for ad-hoc or dynamic per-instance token values (progress, user color)
543
+ - **Use modifiers** (`mods` or `modProps`) for state-driven style changes instead of runtime `styles` prop changes
544
+
545
+ ### Avoid
546
+
547
+ ### Using raw CSS values when tokens exist
548
+
549
+ ```tsx
550
+ // Bad: hardcoded color
551
+ fill: 'oklch(55% 0.25 265)',
552
+
553
+ // Good: token reference
554
+ fill: '#primary',
555
+ ```
556
+
557
+ Tokens ensure consistency across components and make theme changes a one-line update.
558
+
559
+ ### Using CSS property names when Tasty alternatives exist
560
+
561
+ ```tsx
562
+ // Bad: raw CSS properties
563
+ backgroundColor: '#fff',
564
+ borderRadius: '4px',
565
+ flexDirection: 'column',
566
+
567
+ // Good: Tasty shorthands
568
+ fill: '#surface',
569
+ radius: '1r',
570
+ flow: 'column',
571
+ ```
572
+
573
+ Tasty's enhanced properties provide concise syntax, better composability, and simpler overrides. See [recommended props](styles.md#recommended-props) for the full mapping.
574
+
575
+ ### Changing styles prop at runtime
576
+
577
+ ```tsx
578
+ // Bad: styles object changes every render
579
+ <Card styles={{ padding: isCompact ? '2x' : '4x' }} />
580
+
581
+ // Good: use modifiers via modProps
582
+ <Card isCompact={isCompact} />
583
+
584
+ // Or via mods object
585
+ <Card mods={{ isCompact }} />
586
+
587
+ // In the component definition:
588
+ const Card = tasty({
589
+ modProps: ['isCompact'] as const,
590
+ styles: {
591
+ padding: { '': '4x', isCompact: '2x' },
592
+ },
593
+ });
594
+ ```
595
+
596
+ Modifiers are compiled into exclusive selectors once. Changing `styles` at runtime forces Tasty to regenerate and re-inject CSS.
597
+
598
+ ### Overusing style prop
599
+
600
+ ```tsx
601
+ // Bad: bypassing Tasty for custom styling
602
+ <Button style={{ backgroundColor: 'red', padding: '12px 24px' }} />;
603
+
604
+ // Good: create a styled wrapper
605
+ const DangerButton = tasty(Button, {
606
+ styles: { fill: '#danger', padding: '1.5x 3x' },
607
+ });
608
+ ```
609
+
610
+ The `style` prop bypasses tokens, units, and state maps. It should only be used for third-party library integration.
611
+
612
+ ### Skipping elements for compound components
613
+
614
+ ```tsx
615
+ // Less ideal: manual data-element attributes
616
+ <Card>
617
+ <div data-element="Title">Card Title</div>
618
+ <div data-element="Content">Card content</div>
619
+ </Card>;
620
+
621
+ // Better: declare elements for typed sub-components
622
+ const Card = tasty({
623
+ styles: {
624
+ Title: { preset: 'h3', color: '#primary' },
625
+ Content: { preset: 't2', color: '#text' },
626
+ },
627
+ elements: { Title: 'h2', Content: 'div' },
628
+ });
629
+
630
+ <Card>
631
+ <Card.Title>Card Title</Card.Title>
632
+ <Card.Content>Card content</Card.Content>
633
+ </Card>;
634
+ ```
635
+
636
+ The `elements` prop gives you typed sub-components with automatic `data-element` attributes, `mods` support, and better discoverability.
637
+
638
+ ---
639
+
640
+ ## Learn more
641
+
642
+ - **[Getting Started](getting-started.md)** — Installation, first component, tooling setup
643
+ - **[Building a Design System](design-system.md)** — Practical guide to building a DS layer with Tasty
644
+ - **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
645
+ - **[React API](react-api.md)** — `tasty()` factory, component props, variants, sub-elements, style functions
646
+ - **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers
647
+ - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
648
+ - **[Adoption Guide](adoption.md)** — Who should adopt Tasty, incremental phases, what changes for product engineers