@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,401 @@
|
|
|
1
|
+
# Building a Design System
|
|
2
|
+
|
|
3
|
+
This guide walks through building a design-system styling layer on top of Tasty — defining tokens, state aliases, recipes, primitive components, and compound components with sub-elements.
|
|
4
|
+
|
|
5
|
+
It assumes you have already decided to adopt Tasty. For evaluation criteria, audience fit, and incremental adoption phases, see the [Adoption Guide](adoption.md). For the recommended component patterns and mental model, see [Methodology](methodology.md).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Designing your token vocabulary
|
|
10
|
+
|
|
11
|
+
Tokens are the foundation of a design system. In Tasty, tokens are defined via `configure({ tokens })` and referenced in styles with `#name` (colors) or `$name` (values). See [Configuration — Design Tokens](configuration.md#design-tokens) for the API.
|
|
12
|
+
|
|
13
|
+
### Color tokens
|
|
14
|
+
|
|
15
|
+
Adopt a semantic naming convention that maps intent, not visual appearance. Define tokens via `configure({ tokens })`:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { configure } from '@tenphi/tasty';
|
|
19
|
+
|
|
20
|
+
configure({
|
|
21
|
+
tokens: {
|
|
22
|
+
// Surfaces
|
|
23
|
+
'#surface': '#fff',
|
|
24
|
+
'#surface-hover': '#f5f5f5',
|
|
25
|
+
'#surface-pressed': '#ebebeb',
|
|
26
|
+
|
|
27
|
+
// Primary action
|
|
28
|
+
'#primary': 'oklch(55% 0.25 265)',
|
|
29
|
+
'#primary-hover': 'oklch(50% 0.25 265)',
|
|
30
|
+
'#primary-pressed': 'oklch(45% 0.25 265)',
|
|
31
|
+
'#on-primary': '#fff',
|
|
32
|
+
|
|
33
|
+
// Semantic
|
|
34
|
+
'#danger': 'oklch(55% 0.22 25)',
|
|
35
|
+
'#danger-hover': 'oklch(50% 0.22 25)',
|
|
36
|
+
'#on-danger': '#fff',
|
|
37
|
+
|
|
38
|
+
// Text
|
|
39
|
+
'#text': '#111',
|
|
40
|
+
'#text-secondary': '#666',
|
|
41
|
+
|
|
42
|
+
// Borders
|
|
43
|
+
'#border': '#e0e0e0',
|
|
44
|
+
|
|
45
|
+
// Unit values
|
|
46
|
+
'$gap': '8px',
|
|
47
|
+
'$radius': '4px',
|
|
48
|
+
'$border-width': '1px',
|
|
49
|
+
'$outline-width': '2px',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tokens are injected as CSS custom properties on `:root` when the first style is rendered. They support state maps for theme-aware values:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
configure({
|
|
58
|
+
tokens: {
|
|
59
|
+
'#primary': {
|
|
60
|
+
'': 'oklch(55% 0.25 265)',
|
|
61
|
+
'@dark': 'oklch(75% 0.2 265)',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The `#on-*` convention names the text color that sits on top of a fill — `#on-primary` is the text color on a `#primary` background. This makes state maps self-documenting: `fill: '#primary'` and `color: '#on-primary'` clearly belong together.
|
|
68
|
+
|
|
69
|
+
For OKHSL-based palette generation with automatic WCAG contrast solving, see [Glaze](https://github.com/tenphi/glaze).
|
|
70
|
+
|
|
71
|
+
### Replace tokens for value aliases
|
|
72
|
+
|
|
73
|
+
`configure({ replaceTokens })` replaces tokens with literal values at parse time, baking them into the generated CSS. Use it for value aliases that should be inlined during style generation:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
configure({
|
|
77
|
+
replaceTokens: {
|
|
78
|
+
'$card-padding': '4x',
|
|
79
|
+
'$input-height': '5x',
|
|
80
|
+
'$sidebar-width': '280px',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Typography presets
|
|
86
|
+
|
|
87
|
+
Use `generateTypographyTokens()` to create typography tokens from your own presets, then pass them to `configure({ tokens })`:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { configure, generateTypographyTokens } from '@tenphi/tasty';
|
|
91
|
+
|
|
92
|
+
const typographyTokens = generateTypographyTokens({
|
|
93
|
+
h1: { fontSize: '2rem', lineHeight: '1.2', letterSpacing: '-0.02em', fontWeight: 700 },
|
|
94
|
+
t2: { fontSize: '0.875rem', lineHeight: '1.5', letterSpacing: 'normal', fontWeight: 400 },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
configure({
|
|
98
|
+
tokens: {
|
|
99
|
+
...typographyTokens,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Then use `preset: 'h1'` or `preset: 't2'` in any component's styles.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Defining state aliases
|
|
109
|
+
|
|
110
|
+
State aliases let you write `@mobile` instead of `@media(w < 768px)` in every component. Define them once in `configure()`:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
configure({
|
|
114
|
+
states: {
|
|
115
|
+
'@mobile': '@media(w < 768px)',
|
|
116
|
+
'@tablet': '@media(768px <= w < 1024px)',
|
|
117
|
+
'@desktop': '@media(w >= 1024px)',
|
|
118
|
+
'@dark': '@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
|
|
119
|
+
'@reduced-motion': '@media(prefers-reduced-motion: reduce)',
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Every component can now use these without repeating the query logic:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
const Card = tasty({
|
|
128
|
+
styles: {
|
|
129
|
+
padding: { '': '4x', '@mobile': '2x' },
|
|
130
|
+
flow: { '': 'row', '@mobile': 'column' },
|
|
131
|
+
fill: { '': '#surface', '@dark': '#surface-dark' },
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Without aliases, the same component would need the full media query expression inlined in every state map — across every property, in every component. Aliases eliminate that duplication.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Creating recipes
|
|
141
|
+
|
|
142
|
+
Recipes are named style bundles for patterns that repeat across components. Define them in `configure()`:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
configure({
|
|
146
|
+
recipes: {
|
|
147
|
+
card: {
|
|
148
|
+
padding: '4x',
|
|
149
|
+
fill: '#surface',
|
|
150
|
+
radius: '1r',
|
|
151
|
+
border: true,
|
|
152
|
+
},
|
|
153
|
+
elevated: {
|
|
154
|
+
shadow: '0 2x 4x #shadow',
|
|
155
|
+
},
|
|
156
|
+
'input-reset': {
|
|
157
|
+
border: 'none',
|
|
158
|
+
outline: 'none',
|
|
159
|
+
fill: 'transparent',
|
|
160
|
+
font: true,
|
|
161
|
+
preset: 't3',
|
|
162
|
+
},
|
|
163
|
+
interactive: {
|
|
164
|
+
cursor: { '': 'pointer', disabled: 'not-allowed' },
|
|
165
|
+
opacity: { '': '1', disabled: '.5' },
|
|
166
|
+
transition: 'theme',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Components reference recipes by name. This keeps component definitions lean while ensuring consistency:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
const ProfileCard = tasty({
|
|
176
|
+
styles: {
|
|
177
|
+
recipe: 'card elevated',
|
|
178
|
+
color: '#text',
|
|
179
|
+
Title: { preset: 'h3', color: '#primary' },
|
|
180
|
+
},
|
|
181
|
+
elements: { Title: 'h2' },
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Use the `/` separator when a recipe should be applied *after* local styles (post-merge), so recipe states take priority:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
const Input = tasty({
|
|
189
|
+
styles: {
|
|
190
|
+
recipe: 'input-reset / interactive',
|
|
191
|
+
padding: '1.5x 2x',
|
|
192
|
+
border: '1bw solid #border',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
See [Configuration — Recipes](configuration.md#recipes) for the API and [Style DSL — Recipes](dsl.md#recipes) for composition patterns.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Building primitive components
|
|
202
|
+
|
|
203
|
+
Primitives are the layout and utility components that product engineers compose. They expose a controlled set of style props and have minimal opinions about appearance.
|
|
204
|
+
|
|
205
|
+
### Layout primitives
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { tasty, FLOW_STYLES, POSITION_STYLES } from '@tenphi/tasty';
|
|
209
|
+
|
|
210
|
+
const Space = tasty({
|
|
211
|
+
as: 'div',
|
|
212
|
+
styles: {
|
|
213
|
+
display: 'flex',
|
|
214
|
+
flow: 'column',
|
|
215
|
+
gap: '1x',
|
|
216
|
+
},
|
|
217
|
+
styleProps: FLOW_STYLES,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const Box = tasty({
|
|
221
|
+
as: 'div',
|
|
222
|
+
styles: {
|
|
223
|
+
display: 'block',
|
|
224
|
+
},
|
|
225
|
+
styleProps: [...FLOW_STYLES, ...POSITION_STYLES, 'padding', 'fill', 'radius'],
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const Grid = tasty({
|
|
229
|
+
as: 'div',
|
|
230
|
+
styles: {
|
|
231
|
+
display: 'grid',
|
|
232
|
+
gap: '1x',
|
|
233
|
+
},
|
|
234
|
+
styleProps: [...FLOW_STYLES, 'gridColumns', 'gridRows', 'gridAreas'],
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Product engineers use these to compose layouts without writing CSS:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
<Space flow="row" gap="2x" placeItems="center">
|
|
242
|
+
<Title>Dashboard</Title>
|
|
243
|
+
<Button placeSelf="end">Add Item</Button>
|
|
244
|
+
</Space>
|
|
245
|
+
|
|
246
|
+
<Grid gridColumns={{ '': '1fr', '@tablet': '1fr 1fr', '@desktop': '1fr 1fr 1fr' }} gap="3x">
|
|
247
|
+
<Card>...</Card>
|
|
248
|
+
<Card>...</Card>
|
|
249
|
+
<Card>...</Card>
|
|
250
|
+
</Grid>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Which styleProps to expose
|
|
254
|
+
|
|
255
|
+
Match the prop set to the component's role:
|
|
256
|
+
|
|
257
|
+
| Component category | Recommended styleProps |
|
|
258
|
+
|--------------------|----------------------|
|
|
259
|
+
| Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align, justify, padding, fill |
|
|
260
|
+
| Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order |
|
|
261
|
+
| Text elements | `['preset', 'color']` or a custom subset |
|
|
262
|
+
| Compound components | Typically none — styling happens via sub-elements and wrapping |
|
|
263
|
+
|
|
264
|
+
Exposing too many props weakens the design system's constraints. See [Methodology — styleProps as the public API](methodology.md#styleprops-as-the-public-api) for the rationale.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Building compound components
|
|
269
|
+
|
|
270
|
+
Compound components have inner parts — a card has a title, content, and footer; a dialog has a header, body, and actions. Tasty models these as sub-elements.
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
const Card = tasty({
|
|
274
|
+
styles: {
|
|
275
|
+
recipe: 'card',
|
|
276
|
+
flow: 'column',
|
|
277
|
+
gap: '2x',
|
|
278
|
+
|
|
279
|
+
Title: {
|
|
280
|
+
preset: 'h3',
|
|
281
|
+
color: '#primary',
|
|
282
|
+
},
|
|
283
|
+
Content: {
|
|
284
|
+
preset: 't2',
|
|
285
|
+
color: '#text',
|
|
286
|
+
},
|
|
287
|
+
Footer: {
|
|
288
|
+
display: 'flex',
|
|
289
|
+
flow: 'row',
|
|
290
|
+
gap: '1x',
|
|
291
|
+
justify: 'flex-end',
|
|
292
|
+
padding: '2x top',
|
|
293
|
+
border: '1bw solid #border top',
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
elements: {
|
|
297
|
+
Title: 'h2',
|
|
298
|
+
Content: 'div',
|
|
299
|
+
Footer: 'div',
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Usage:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<Card>
|
|
308
|
+
<Card.Title>Monthly Revenue</Card.Title>
|
|
309
|
+
<Card.Content>
|
|
310
|
+
$1.2M — up 12% from last month.
|
|
311
|
+
</Card.Content>
|
|
312
|
+
<Card.Footer>
|
|
313
|
+
<Button>Details</Button>
|
|
314
|
+
</Card.Footer>
|
|
315
|
+
</Card>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Sub-elements share the root component's state context by default. A `disabled` modifier on `<Card>` affects `Title`, `Content`, and `Footer` styles automatically — no prop drilling. For the full mental model, see [Methodology — Component architecture](methodology.md#component-architecture-root--sub-elements).
|
|
319
|
+
|
|
320
|
+
For sub-element syntax details (selector affix `$`, `@own()`, `elements` config), see [Runtime API — Sub-element Styling](runtime.md#sub-element-styling).
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Defining the override contract
|
|
325
|
+
|
|
326
|
+
A design system works best when the rules for customization are explicit. Tasty provides three levels of control:
|
|
327
|
+
|
|
328
|
+
### What product engineers can do
|
|
329
|
+
|
|
330
|
+
1. **Use styleProps** — adjust layout, spacing, positioning through the props the component exposes:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
<Space flow="row" gap="3x" padding="2x">
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
2. **Pass tokens** — inject runtime values through the `tokens` prop for per-instance customization:
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
<ProgressBar tokens={{ '$progress': `${percent}%` }} />
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
3. **Create styled wrappers** — extend a component's styles with `tasty(Base, { styles })`:
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
const DangerButton = tasty(Button, {
|
|
346
|
+
styles: {
|
|
347
|
+
fill: { '': '#danger', ':hover': '#danger-hover' },
|
|
348
|
+
color: '#on-danger',
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### What to discourage
|
|
354
|
+
|
|
355
|
+
- **Direct `styles` prop** — bypasses the component's intended API; prefer styled wrappers
|
|
356
|
+
- **`style` prop** — React inline styles that bypass Tasty entirely; reserve for third-party integration only
|
|
357
|
+
- **Overriding internal sub-elements** — if product engineers need to restyle sub-elements, the DS component should expose that through its own API (additional props or variants), not through raw `styles` overrides
|
|
358
|
+
|
|
359
|
+
See [Methodology — styles prop vs style prop](methodology.md#styles-prop-vs-style-prop) and [Methodology — Wrapping and extension](methodology.md#wrapping-and-extension) for the full rationale.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Project structure
|
|
364
|
+
|
|
365
|
+
A recommended structure for a design system built on Tasty:
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
ds/
|
|
369
|
+
config.ts # configure() — tokens, units, states, recipes
|
|
370
|
+
primitives/
|
|
371
|
+
Space.tsx # Layout: flex container with FLOW_STYLES
|
|
372
|
+
Box.tsx # Generic container
|
|
373
|
+
Grid.tsx # Grid container
|
|
374
|
+
Text.tsx # Text element with preset + color
|
|
375
|
+
components/
|
|
376
|
+
Button.tsx # Interactive component with variants
|
|
377
|
+
Card.tsx # Compound component with sub-elements
|
|
378
|
+
Input.tsx # Form input with recipes
|
|
379
|
+
Dialog.tsx # Overlay compound component
|
|
380
|
+
recipes/
|
|
381
|
+
index.ts # Recipe definitions (imported by config.ts)
|
|
382
|
+
tokens/
|
|
383
|
+
colors.ts # Color token definitions
|
|
384
|
+
typography.ts # Typography presets via generateTypographyTokens()
|
|
385
|
+
spacing.ts # Spacing token definitions
|
|
386
|
+
index.ts # Public API: re-exports components + configure
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The key principle: `config.ts` imports tokens and recipes, calls `configure()`, and is imported at the app entry point before any component renders. Components import only from `@tenphi/tasty` — they reference tokens and recipes by name, not by import.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Learn more
|
|
394
|
+
|
|
395
|
+
- **[Methodology](methodology.md)** — The recommended patterns for structuring Tasty components
|
|
396
|
+
- **[Getting Started](getting-started.md)** — Installation, first component, tooling setup
|
|
397
|
+
- **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
|
|
398
|
+
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, hooks
|
|
399
|
+
- **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers
|
|
400
|
+
- **[Adoption Guide](adoption.md)** — Who should adopt Tasty, incremental phases, what changes for product engineers
|
|
401
|
+
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|