@tenphi/tasty 0.10.1 → 0.12.0
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.
- package/README.md +79 -5
- package/dist/config.d.ts +32 -9
- package/dist/config.js +51 -10
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/injector/injector.js +1 -7
- package/dist/injector/injector.js.map +1 -1
- package/dist/parser/classify.js.map +1 -1
- package/dist/plugins/types.d.ts +9 -2
- package/dist/ssr/collector.js +11 -1
- package/dist/ssr/collector.js.map +1 -1
- package/dist/styles/types.d.ts +14 -1
- package/dist/tasty.d.ts +58 -58
- package/dist/utils/typography.d.ts +24 -13
- package/dist/utils/typography.js +6 -16
- package/dist/utils/typography.js.map +1 -1
- package/dist/zero/babel.d.ts +24 -4
- package/dist/zero/babel.js +16 -4
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/next.d.ts +16 -1
- package/dist/zero/next.js +53 -12
- package/dist/zero/next.js.map +1 -1
- package/docs/adoption.md +286 -0
- package/docs/comparison.md +413 -0
- package/docs/configuration.md +41 -10
- package/docs/debug.md +1 -1
- package/docs/design-system.md +401 -0
- package/docs/{usage.md → dsl.md} +254 -409
- package/docs/getting-started.md +201 -0
- package/docs/injector.md +2 -2
- package/docs/methodology.md +501 -0
- package/docs/runtime.md +291 -0
- package/docs/ssr.md +11 -1
- package/docs/styles.md +2 -2
- package/docs/tasty-static.md +58 -13
- package/package.json +8 -5
- package/dist/tokens/typography.d.ts +0 -19
- package/dist/tokens/typography.js +0 -237
- package/dist/tokens/typography.js.map +0 -1
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# Methodology
|
|
2
|
+
|
|
3
|
+
Tasty has opinions about how components should be structured. The patterns described here are not mandatory — Tasty works without them — but following them gets the most out of the engine: better state resolution, cleaner component APIs, simpler overrides, and fewer surprises as the system grows.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Component architecture: root + sub-elements
|
|
8
|
+
|
|
9
|
+
### The model
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
const Alert = tasty({
|
|
15
|
+
styles: {
|
|
16
|
+
padding: '3x',
|
|
17
|
+
fill: { '': '#surface', 'type=danger': '#danger.10' },
|
|
18
|
+
border: { '': '1bw solid #border', 'type=danger': '1bw solid #danger' },
|
|
19
|
+
radius: '1r',
|
|
20
|
+
|
|
21
|
+
Icon: {
|
|
22
|
+
color: { '': '#text-secondary', 'type=danger': '#danger' },
|
|
23
|
+
width: '3x',
|
|
24
|
+
height: '3x',
|
|
25
|
+
},
|
|
26
|
+
Message: {
|
|
27
|
+
preset: 't2',
|
|
28
|
+
color: '#text',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
elements: { Icon: 'span', Message: 'div' },
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
### How this differs from BEM
|
|
38
|
+
|
|
39
|
+
BEM organizes CSS around blocks, elements, and modifiers. Each element applies its own modifier classes independently:
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<!-- BEM: each element carries its own modifier -->
|
|
43
|
+
<div class="alert alert--danger">
|
|
44
|
+
<span class="alert__icon alert__icon--danger">!</span>
|
|
45
|
+
<div class="alert__message">Something went wrong</div>
|
|
46
|
+
</div>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
In Tasty, sub-elements inherit the root's state context automatically:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Alert mods={{ type: 'danger' }}>
|
|
55
|
+
<Alert.Icon>!</Alert.Icon>
|
|
56
|
+
<Alert.Message>Something went wrong</Alert.Message>
|
|
57
|
+
</Alert>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
This is the fundamental design choice: **state flows from root to sub-elements**, not from each element independently.
|
|
63
|
+
|
|
64
|
+
### When sub-elements need their own state
|
|
65
|
+
|
|
66
|
+
Use `@own(...)` when a sub-element should react to its own state rather than the root's:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const Nav = tasty({
|
|
70
|
+
styles: {
|
|
71
|
+
NavItem: {
|
|
72
|
+
color: {
|
|
73
|
+
'': '#text',
|
|
74
|
+
'@own(:hover)': '#primary',
|
|
75
|
+
'@own(:focus-visible)': '#primary',
|
|
76
|
+
'selected': '#primary',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
elements: { NavItem: 'a' },
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## styleProps as the public API
|
|
91
|
+
|
|
92
|
+
`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.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const Space = tasty({
|
|
96
|
+
as: 'div',
|
|
97
|
+
styles: { display: 'flex', flow: 'column', gap: '1x' },
|
|
98
|
+
styleProps: ['flow', 'gap', 'padding', 'fill', 'placeItems', 'placeContent'],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Product engineer uses it:
|
|
102
|
+
<Space flow="row" gap="2x" padding="4x" placeItems="center">
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Style props accept state maps, so responsive values work through the same API:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<Space
|
|
109
|
+
flow={{ '': 'column', '@tablet': 'row' }}
|
|
110
|
+
gap={{ '': '2x', '@tablet': '4x' }}
|
|
111
|
+
>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Choosing what to expose
|
|
115
|
+
|
|
116
|
+
Tasty exports predefined style prop lists that group properties by role. Use them instead of hand-picking arrays:
|
|
117
|
+
|
|
118
|
+
| Preset | Properties | Typical use |
|
|
119
|
+
|--------|-----------|-------------|
|
|
120
|
+
| `FLOW_STYLES` | flow, gap, columnGap, rowGap, align, justify, placeItems, placeContent, alignItems, alignContent, justifyItems, justifyContent, gridColumns, gridRows, gridTemplate, gridAreas | Layout containers (`Space`, `Grid`) |
|
|
121
|
+
| `POSITION_STYLES` | gridArea, gridColumn, gridRow, order, placeSelf, alignSelf, justifySelf, zIndex, margin, inset, position | Positioned elements (`Button`, `Badge`) |
|
|
122
|
+
| `DIMENSION_STYLES` | width, height, flexBasis, flexGrow, flexShrink, flex | Sized elements |
|
|
123
|
+
| `COLOR_STYLES` | color, fill, fade, image | Color-customizable elements |
|
|
124
|
+
| `BLOCK_STYLES` | padding, paddingInline, paddingBlock, overflow, scrollbar, textAlign, border, radius, shadow, outline | Block-level containers |
|
|
125
|
+
| `CONTAINER_STYLES` | All of the above combined (+ BASE_STYLES) | Fully flexible containers |
|
|
126
|
+
| `OUTER_STYLES` | POSITION_STYLES + DIMENSION_STYLES + block outer (border, radius, shadow, outline) | Components whose outer shell is customizable |
|
|
127
|
+
| `INNER_STYLES` | BASE_STYLES + COLOR_STYLES + block inner (padding, overflow, scrollbar) + FLOW_STYLES | Components whose inner layout is customizable |
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { tasty, FLOW_STYLES, POSITION_STYLES } from '@tenphi/tasty';
|
|
131
|
+
|
|
132
|
+
const Space = tasty({
|
|
133
|
+
as: 'div',
|
|
134
|
+
styles: { display: 'flex', flow: 'column', gap: '1x' },
|
|
135
|
+
styleProps: FLOW_STYLES,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const Button = tasty({
|
|
139
|
+
as: 'button',
|
|
140
|
+
styles: { padding: '1.5x 3x', fill: '#primary', radius: true },
|
|
141
|
+
styleProps: POSITION_STYLES,
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
You can also combine presets or mix them with individual properties:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
styleProps: [...FLOW_STYLES, ...DIMENSION_STYLES, 'fill'],
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Match the preset to the component's role:
|
|
152
|
+
|
|
153
|
+
- **Layout containers** (`Space`, `Box`, `Grid`) — `FLOW_STYLES`, optionally with `DIMENSION_STYLES`
|
|
154
|
+
- **Positioned elements** (`Button`, `Badge`) — `POSITION_STYLES`
|
|
155
|
+
- **Text elements** — custom: `['preset', 'color']`
|
|
156
|
+
- **Compound components** — typically none; styling happens via sub-elements and extension
|
|
157
|
+
|
|
158
|
+
### The governance trade-off
|
|
159
|
+
|
|
160
|
+
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.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## tokens prop for dynamic values
|
|
165
|
+
|
|
166
|
+
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.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
const ProgressBar = tasty({
|
|
170
|
+
styles: {
|
|
171
|
+
width: '100%',
|
|
172
|
+
height: '1x',
|
|
173
|
+
fill: '#surface',
|
|
174
|
+
Bar: {
|
|
175
|
+
width: '$progress',
|
|
176
|
+
height: '100%',
|
|
177
|
+
fill: '#primary',
|
|
178
|
+
transition: 'width 0.3s',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
elements: { Bar: 'div' },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Usage: the progress value comes from a prop, not from styles
|
|
185
|
+
<ProgressBar tokens={{ '$progress': `${percent}%` }} />
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
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.
|
|
189
|
+
|
|
190
|
+
### When to use tokens vs other mechanisms
|
|
191
|
+
|
|
192
|
+
| Need | Use |
|
|
193
|
+
|------|-----|
|
|
194
|
+
| Value changes per instance at render time (progress, user color, avatar size) | `tokens` prop (on component) |
|
|
195
|
+
| Value is constant across all instances (card padding, border radius) | `configure({ tokens })` for `:root` CSS custom properties |
|
|
196
|
+
| Value should be inlined at parse time (alias for another token) | `configure({ replaceTokens })` |
|
|
197
|
+
| Value changes based on component state (hover, disabled, breakpoint) | State map in `styles` |
|
|
198
|
+
| Value changes based on a variant (primary, danger, outline) | `variants` option |
|
|
199
|
+
|
|
200
|
+
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.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## styles prop vs style prop
|
|
205
|
+
|
|
206
|
+
Tasty components accept both `styles` and `style`, but they serve very different purposes.
|
|
207
|
+
|
|
208
|
+
### styles — Tasty extension mechanism
|
|
209
|
+
|
|
210
|
+
The `styles` prop is processed through the full Tasty pipeline. Tokens, custom units, state maps, sub-element keys — everything works:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<Card styles={{ padding: '6x', Title: { color: '#danger' } }} />
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
However, **using `styles` directly is discouraged in design-system code.** The recommended pattern is to create a styled wrapper instead:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// Preferred: create a styled wrapper
|
|
220
|
+
const LargeCard = tasty(Card, {
|
|
221
|
+
styles: { padding: '6x', Title: { color: '#danger' } },
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
<LargeCard />
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Why? Styled wrappers are:
|
|
228
|
+
|
|
229
|
+
- **Faster** — styles are parsed and injected once at definition time, not on every render
|
|
230
|
+
- **Stable** — the style object is defined once, not recreated on every render
|
|
231
|
+
- **Composable** — another engineer can extend `LargeCard` further
|
|
232
|
+
- **Inspectable** — the component has a name in React DevTools
|
|
233
|
+
- **Lint-friendly** — the ESLint plugin's `no-styles-prop` rule flags direct usage
|
|
234
|
+
|
|
235
|
+
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.
|
|
236
|
+
|
|
237
|
+
### style — React inline styles (escape hatch)
|
|
238
|
+
|
|
239
|
+
The `style` prop is standard React `CSSProperties`. It bypasses Tasty entirely — no tokens, no units, no state maps:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
<Card style={{ marginTop: 16 }} />
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
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.
|
|
246
|
+
|
|
247
|
+
See [Best practices](#best-practices) below for the full list of do's and don'ts.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Wrapping and extension
|
|
252
|
+
|
|
253
|
+
`tasty(Base, { styles })` is the primary extension mechanism. It creates a new component whose styles are merged with the base component's styles.
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { Button } from 'my-ds';
|
|
257
|
+
|
|
258
|
+
const DangerButton = tasty(Button, {
|
|
259
|
+
styles: {
|
|
260
|
+
fill: { '': '#danger', ':hover': '#danger-hover' },
|
|
261
|
+
color: '#danger-text',
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Extend mode vs replace mode
|
|
267
|
+
|
|
268
|
+
Merge behavior depends on whether the child provides a `''` (default) key in a state map:
|
|
269
|
+
|
|
270
|
+
- **No `''` key** — extend mode: parent states are preserved, child adds or overrides specific states
|
|
271
|
+
- **Has `''` key** — replace mode: child defines everything from scratch for that property
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
// Extend: adds `loading` state, overrides `disabled`, keeps parent's '' and ':hover'
|
|
275
|
+
tasty(Button, {
|
|
276
|
+
styles: {
|
|
277
|
+
fill: {
|
|
278
|
+
loading: '#yellow',
|
|
279
|
+
disabled: '#gray.20',
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Replace: provides '' key, so parent's fill states are dropped entirely
|
|
285
|
+
tasty(Button, {
|
|
286
|
+
styles: {
|
|
287
|
+
fill: {
|
|
288
|
+
'': '#danger',
|
|
289
|
+
':hover': '#danger-hover',
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
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).
|
|
296
|
+
|
|
297
|
+
### When to use styleProps vs wrapping
|
|
298
|
+
|
|
299
|
+
If the component exposes the properties you need as `styleProps`, use them directly — that is what they are for:
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
// Card exposes padding and gap as styleProps — just use them
|
|
303
|
+
<Card padding="2x" gap="1x">
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Wrapping is for changes that go beyond what `styleProps` expose — overriding colors, adding state mappings, restyling sub-elements:
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
const DangerCard = tasty(Card, {
|
|
310
|
+
styles: {
|
|
311
|
+
border: '1bw solid #danger',
|
|
312
|
+
Title: { color: '#danger' },
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
This is preferred over `<Card styles={{ border: '1bw solid #danger' }}>` because:
|
|
318
|
+
|
|
319
|
+
1. Styles are parsed and injected once, not on every render
|
|
320
|
+
2. `DangerCard` can be extended further by others
|
|
321
|
+
3. It has a meaningful name in DevTools and code search
|
|
322
|
+
4. The ESLint `no-styles-prop` rule encourages this pattern
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## How configuration simplifies components
|
|
327
|
+
|
|
328
|
+
Tasty's `configure()` is not just setup — it directly reduces the complexity of every component in the system.
|
|
329
|
+
|
|
330
|
+
### State aliases eliminate repetition
|
|
331
|
+
|
|
332
|
+
Without aliases, every component inlines the full query:
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
// Without aliases
|
|
336
|
+
padding: { '': '4x', '@media(w < 768px)': '2x' },
|
|
337
|
+
flow: { '': 'row', '@media(w < 768px)': 'column' },
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
With aliases:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
// With aliases
|
|
344
|
+
padding: { '': '4x', '@mobile': '2x' },
|
|
345
|
+
flow: { '': 'row', '@mobile': 'column' },
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The alias is defined once. If the breakpoint changes from `768px` to `640px`, you update one line in `configure()` and every component adjusts.
|
|
349
|
+
|
|
350
|
+
### Recipes extract repeated patterns
|
|
351
|
+
|
|
352
|
+
Without recipes, every card-like component repeats the same base styles:
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
// Without recipes — repeated in Card, ProfileCard, SettingsPanel, ...
|
|
356
|
+
styles: {
|
|
357
|
+
padding: '4x',
|
|
358
|
+
fill: '#surface',
|
|
359
|
+
radius: '1r',
|
|
360
|
+
border: true,
|
|
361
|
+
// ...component-specific styles
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
With recipes:
|
|
366
|
+
|
|
367
|
+
```tsx
|
|
368
|
+
// With recipes
|
|
369
|
+
styles: {
|
|
370
|
+
recipe: 'card',
|
|
371
|
+
// ...component-specific styles only
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
The recipe encapsulates the shared pattern. Change `card`'s radius from `1r` to `2r` and every component using it updates.
|
|
376
|
+
|
|
377
|
+
### Design tokens enforce consistency
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
configure({
|
|
381
|
+
tokens: {
|
|
382
|
+
'$card-padding': '4x',
|
|
383
|
+
'$input-height': '5x',
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
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.
|
|
389
|
+
|
|
390
|
+
See [Configuration](configuration.md) for the full `configure()` API.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Best practices
|
|
395
|
+
|
|
396
|
+
### Do
|
|
397
|
+
|
|
398
|
+
- **Create styled wrappers** instead of passing `styles` directly — faster, composable, inspectable
|
|
399
|
+
- **Use design tokens and custom units** (`#text`, `2x`, `1r`) instead of raw CSS values
|
|
400
|
+
- **Use semantic transition names** (`transition: 'theme 0.3s'`) instead of listing CSS properties
|
|
401
|
+
- **Use `elements` prop** to declare typed sub-components for compound components
|
|
402
|
+
- **Use `styleProps`** to define what product engineers can customize
|
|
403
|
+
- **Use `tokens` prop** for per-instance dynamic values (progress, user color)
|
|
404
|
+
- **Use modifiers** (`mods`) for state-driven style changes instead of runtime `styles` prop changes
|
|
405
|
+
|
|
406
|
+
### Avoid
|
|
407
|
+
|
|
408
|
+
### Using raw CSS values when tokens exist
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
// Bad: hardcoded color
|
|
412
|
+
fill: 'oklch(55% 0.25 265)',
|
|
413
|
+
|
|
414
|
+
// Good: token reference
|
|
415
|
+
fill: '#primary',
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Tokens ensure consistency across components and make theme changes a one-line update.
|
|
419
|
+
|
|
420
|
+
### Using CSS property names when Tasty alternatives exist
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
// Bad: raw CSS properties
|
|
424
|
+
backgroundColor: '#fff',
|
|
425
|
+
borderRadius: '4px',
|
|
426
|
+
flexDirection: 'column',
|
|
427
|
+
|
|
428
|
+
// Good: Tasty shorthands
|
|
429
|
+
fill: '#surface',
|
|
430
|
+
radius: '1r',
|
|
431
|
+
flow: 'column',
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Tasty's enhanced properties provide concise syntax, better composability, and simpler overrides. See [recommended props](styles.md#recommended-props) for the full mapping.
|
|
435
|
+
|
|
436
|
+
### Changing styles prop at runtime
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
// Bad: styles object changes every render
|
|
440
|
+
<Card styles={{ padding: isCompact ? '2x' : '4x' }} />
|
|
441
|
+
|
|
442
|
+
// Good: use modifiers
|
|
443
|
+
<Card mods={{ compact: isCompact }} />
|
|
444
|
+
|
|
445
|
+
// In the component definition:
|
|
446
|
+
padding: { '': '4x', compact: '2x' },
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Modifiers are compiled into exclusive selectors once. Changing `styles` at runtime forces Tasty to regenerate and re-inject CSS.
|
|
450
|
+
|
|
451
|
+
### Overusing style prop
|
|
452
|
+
|
|
453
|
+
```tsx
|
|
454
|
+
// Bad: bypassing Tasty for custom styling
|
|
455
|
+
<Button style={{ backgroundColor: 'red', padding: '12px 24px' }} />
|
|
456
|
+
|
|
457
|
+
// Good: create a styled wrapper
|
|
458
|
+
const DangerButton = tasty(Button, {
|
|
459
|
+
styles: { fill: '#danger', padding: '1.5x 3x' },
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The `style` prop bypasses tokens, units, and state maps. It should only be used for third-party library integration.
|
|
464
|
+
|
|
465
|
+
### Skipping elements for compound components
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
// Less ideal: manual data-element attributes
|
|
469
|
+
<Card>
|
|
470
|
+
<div data-element="Title">Card Title</div>
|
|
471
|
+
<div data-element="Content">Card content</div>
|
|
472
|
+
</Card>
|
|
473
|
+
|
|
474
|
+
// Better: declare elements for typed sub-components
|
|
475
|
+
const Card = tasty({
|
|
476
|
+
styles: {
|
|
477
|
+
Title: { preset: 'h3', color: '#primary' },
|
|
478
|
+
Content: { preset: 't2', color: '#text' },
|
|
479
|
+
},
|
|
480
|
+
elements: { Title: 'h2', Content: 'div' },
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
<Card>
|
|
484
|
+
<Card.Title>Card Title</Card.Title>
|
|
485
|
+
<Card.Content>Card content</Card.Content>
|
|
486
|
+
</Card>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
The `elements` prop gives you typed sub-components with automatic `data-element` attributes, `mods` support, and better discoverability.
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Learn more
|
|
494
|
+
|
|
495
|
+
- **[Getting Started](getting-started.md)** — Installation, first component, tooling setup
|
|
496
|
+
- **[Building a Design System](design-system.md)** — Practical guide to building a DS layer with Tasty
|
|
497
|
+
- **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
|
|
498
|
+
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, hooks
|
|
499
|
+
- **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers
|
|
500
|
+
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
|
501
|
+
- **[Adoption Guide](adoption.md)** — Who should adopt Tasty, incremental phases, what changes for product engineers
|