@tenphi/tasty 3.10.0 → 3.11.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 +399 -380
- package/dist/{babel-types-C_7kRP38.d.ts → babel-types-BKDTT4--.d.ts} +2 -2
- package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
- package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
- package/dist/chunks/{build-config-l2Ys-AJr.js → build-config-5d8WlQbq.js} +5 -5
- package/dist/chunks/{build-config-l2Ys-AJr.js.map → build-config-5d8WlQbq.js.map} +1 -1
- package/dist/chunks/{collector-CaNtsRtn.js → collector-CjCak4iE.js} +4 -4
- package/dist/chunks/{collector-CaNtsRtn.js.map → collector-CjCak4iE.js.map} +1 -1
- package/dist/chunks/{config-engine-CpExZO_T.js → config-engine-uOFYZidB.js} +4 -4
- package/dist/chunks/{config-engine-CpExZO_T.js.map → config-engine-uOFYZidB.js.map} +1 -1
- package/dist/chunks/config-file-DlZXJMj8.js +25 -0
- package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
- package/dist/chunks/{css-definitions-DAIJxzFY.js → css-definitions-ubrHP97m.js} +88 -77
- package/dist/chunks/css-definitions-ubrHP97m.js.map +1 -0
- package/dist/chunks/{debug-CKy49GEr.js → debug-CJ31U_WE.js} +6 -6
- package/dist/chunks/{debug-CKy49GEr.js.map → debug-CJ31U_WE.js.map} +1 -1
- package/dist/chunks/{dsl-DRhFWQzo.js → dsl-DYSJc1-T.js} +11 -6
- package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
- package/dist/chunks/{react-runtime-oi6i3ELR.js → react-runtime-CyyCI02N.js} +168 -61
- package/dist/chunks/react-runtime-CyyCI02N.js.map +1 -0
- package/dist/chunks/{runtime-engine-DoULyhPt.js → runtime-engine-BQmOu5-W.js} +53 -36
- package/dist/chunks/runtime-engine-BQmOu5-W.js.map +1 -0
- package/dist/chunks/{runtime-state-CZ2J5QiZ.js → runtime-state-DEsyUMU-.js} +14 -3
- package/dist/chunks/runtime-state-DEsyUMU-.js.map +1 -0
- package/dist/chunks/{style-engine-CeKsjtIr.js → style-engine-BX2LshDZ.js} +116 -56
- package/dist/chunks/style-engine-BX2LshDZ.js.map +1 -0
- package/dist/chunks/{zero-engine-fxKu8Jbn.js → zero-engine-Dw0P4yGM.js} +22 -7
- package/dist/chunks/zero-engine-Dw0P4yGM.js.map +1 -0
- package/dist/{collector-w4mBj5gd.d.ts → collector-CTU-nl0g.d.ts} +2 -2
- package/dist/{config-CrZOfNJ5.d.ts → config-CcxqSZI2.d.ts} +28 -9
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +7 -7
- package/dist/hydrate-CRyjjAyl.d.ts +22 -0
- package/dist/{index-CYJtAI9a.d.ts → index-xtey-KDu.d.ts} +43 -13
- package/dist/index.d.ts +4 -4
- package/dist/index.js +8 -8
- package/dist/{merge-styles-VXwJ0Frr.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -2
- package/dist/ssr/astro.js +4 -4
- package/dist/ssr/client.d.ts +2 -0
- package/dist/ssr/client.js +2 -0
- package/dist/ssr/index.d.ts +3 -22
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +1 -1
- package/dist/ssr/next-config.js +6 -17
- package/dist/ssr/next-config.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +3 -3
- package/dist/static/index.d.ts +2 -2
- package/dist/{types-JEY8PVYP.d.ts → types-DJEM_BOJ.d.ts} +6 -6
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +30 -19
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +24 -6
- package/dist/zero/index.js +4 -4
- package/dist/zero/index.js.map +1 -1
- package/dist/zero/next.d.ts +1 -1
- package/dist/zero/next.js +3 -7
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +4 -1
- package/docs/adoption.md +25 -17
- package/docs/ai-agents.md +37 -34
- package/docs/comparison.md +9 -8
- package/docs/configuration.md +173 -58
- package/docs/debug.md +3 -2
- package/docs/design-system.md +117 -15
- package/docs/dsl.md +120 -81
- package/docs/getting-started.md +81 -24
- package/docs/injector.md +39 -40
- package/docs/methodology.md +45 -20
- package/docs/migration-v3.md +2 -2
- package/docs/pipeline.md +19 -16
- package/docs/plugins.md +20 -40
- package/docs/react-api.md +21 -8
- package/docs/runtime-benchmarks.md +4 -4
- package/docs/ssr.md +83 -63
- package/docs/styles.md +27 -17
- package/docs/tasty-static.md +110 -56
- package/package.json +23 -9
- package/dist/chunks/async-storage-CG5HnUT5.js +0 -20
- package/dist/chunks/async-storage-CG5HnUT5.js.map +0 -1
- package/dist/chunks/css-definitions-DAIJxzFY.js.map +0 -1
- package/dist/chunks/dsl-DRhFWQzo.js.map +0 -1
- package/dist/chunks/react-runtime-oi6i3ELR.js.map +0 -1
- package/dist/chunks/runtime-engine-DoULyhPt.js.map +0 -1
- package/dist/chunks/runtime-state-CZ2J5QiZ.js.map +0 -1
- package/dist/chunks/style-engine-CeKsjtIr.js.map +0 -1
- package/dist/chunks/zero-engine-fxKu8Jbn.js.map +0 -1
package/docs/design-system.md
CHANGED
|
@@ -42,15 +42,25 @@ configure({
|
|
|
42
42
|
// Borders
|
|
43
43
|
'#border': '#e0e0e0',
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// Spacing and shape scale
|
|
46
46
|
$gap: '8px',
|
|
47
47
|
$radius: '4px',
|
|
48
48
|
'$border-width': '1px',
|
|
49
49
|
'$outline-width': '2px',
|
|
50
50
|
},
|
|
51
|
+
// Built-in units compile to fixed values by default. Pointing them at the
|
|
52
|
+
// tokens above lets a theme or density mode change the scale at runtime.
|
|
53
|
+
units: {
|
|
54
|
+
x: 'var(--gap)', // 2x → calc(2 * var(--gap))
|
|
55
|
+
r: 'var(--radius)',
|
|
56
|
+
bw: 'var(--border-width)',
|
|
57
|
+
ow: 'var(--outline-width)',
|
|
58
|
+
},
|
|
51
59
|
});
|
|
52
60
|
```
|
|
53
61
|
|
|
62
|
+
Leave out the `units` block to keep the built-in fixed scale (`x` = `8px`, `r` = `6px`, `bw` = `1px`, `ow` = `3px`), or set fixed values there instead (`units: { x: '8px', r: '4px' }`). Define the token a unit points at; until you do, `--gap`, `--radius`, `--border-width`, and `--outline-width` start at the built-in unit values. Boolean shorthands follow the same scale: `radius: true` is `1r`, `border: true` is `1bw`, and `outline: true` is `1ow`. See [Built-in Units](dsl.md#built-in-units).
|
|
63
|
+
|
|
54
64
|
Tokens are injected as CSS custom properties on `:root` when the first style is rendered. They support state maps for theme-aware values:
|
|
55
65
|
|
|
56
66
|
```tsx
|
|
@@ -66,7 +76,7 @@ configure({
|
|
|
66
76
|
|
|
67
77
|
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
78
|
|
|
69
|
-
|
|
79
|
+
To generate the whole palette instead of hand-picking values, use [Glaze](https://github.com/tenphi/glaze): it builds light, dark, and high-contrast variants from one hue in the OKHST color space, solves WCAG and APCA contrast, and exports Tasty tokens you can pass straight to `configure({ tokens: theme.tasty() })`.
|
|
70
80
|
|
|
71
81
|
### Replace tokens for value aliases
|
|
72
82
|
|
|
@@ -176,11 +186,13 @@ const Card = tasty({
|
|
|
176
186
|
styles: {
|
|
177
187
|
padding: { '': '4x', '@mobile': '2x' },
|
|
178
188
|
flow: { '': 'row', '@mobile': 'column' },
|
|
179
|
-
|
|
189
|
+
radius: { '': '1r', '@mobile': '0' },
|
|
180
190
|
},
|
|
181
191
|
});
|
|
182
192
|
```
|
|
183
193
|
|
|
194
|
+
Theme switching belongs in tokens (`'#surface': { '': '#fff', '@dark': '#1c1c1e' }`) rather than in component state maps, so components stay theme-agnostic.
|
|
195
|
+
|
|
184
196
|
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.
|
|
185
197
|
|
|
186
198
|
---
|
|
@@ -279,7 +291,7 @@ const Grid = tasty({
|
|
|
279
291
|
display: 'grid',
|
|
280
292
|
gap: '1x',
|
|
281
293
|
},
|
|
282
|
-
styleProps:
|
|
294
|
+
styleProps: FLOW_STYLES, // includes gridColumns, gridRows, gridAreas
|
|
283
295
|
});
|
|
284
296
|
```
|
|
285
297
|
|
|
@@ -298,18 +310,54 @@ Product engineers use these to compose layouts without writing CSS:
|
|
|
298
310
|
</Grid>
|
|
299
311
|
```
|
|
300
312
|
|
|
313
|
+
### Interactive components with variants
|
|
314
|
+
|
|
315
|
+
Variants are named looks that replace each other. Combine them with `modProps` for states that stack, such as size or loading:
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
const Button = tasty({
|
|
319
|
+
as: 'button',
|
|
320
|
+
modProps: { size: ['sm', 'md', 'lg'] as const },
|
|
321
|
+
styles: {
|
|
322
|
+
recipe: 'interactive',
|
|
323
|
+
padding: { '': '1.5x 3x', 'size=sm': '1x 2x', 'size=lg': '2x 4x' },
|
|
324
|
+
radius: '1r',
|
|
325
|
+
},
|
|
326
|
+
variants: {
|
|
327
|
+
default: {
|
|
328
|
+
fill: {
|
|
329
|
+
'': '#primary',
|
|
330
|
+
':hover': '#primary-hover',
|
|
331
|
+
disabled: '#surface',
|
|
332
|
+
},
|
|
333
|
+
color: '#on-primary',
|
|
334
|
+
},
|
|
335
|
+
danger: {
|
|
336
|
+
fill: { '': '#danger', ':hover': '#danger-hover', disabled: '#surface' },
|
|
337
|
+
color: '#on-danger',
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
<Button variant="danger" size="lg">
|
|
343
|
+
Delete
|
|
344
|
+
</Button>;
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`variant` is typed from the keys, the `default` variant applies when no `variant` prop is passed, and only the variants that actually render emit CSS. See [React API — Variants](react-api.md#variants).
|
|
348
|
+
|
|
301
349
|
### Which styleProps to expose
|
|
302
350
|
|
|
303
351
|
Match the prop set to the component's role:
|
|
304
352
|
|
|
305
|
-
| Component category | Recommended styleProps
|
|
306
|
-
| ------------------------------------------ |
|
|
307
|
-
| Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align
|
|
308
|
-
| Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order
|
|
309
|
-
| Text elements | `['
|
|
310
|
-
| Compound components | Typically none — styling happens via sub-elements and wrapping
|
|
353
|
+
| Component category | Recommended styleProps |
|
|
354
|
+
| ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
|
355
|
+
| Layout containers (`Space`, `Box`, `Grid`) | `FLOW_STYLES` — flow, gap, align/justify/place, grid templates (add `padding` or `fill` explicitly) |
|
|
356
|
+
| Positioned elements (`Button`, `Badge`) | `POSITION_STYLES` — placeSelf, gridArea, order |
|
|
357
|
+
| Text elements | `['color', ...TEXT_STYLES]` (`preset` is always available) |
|
|
358
|
+
| Compound components | Typically none — styling happens via sub-elements and wrapping |
|
|
311
359
|
|
|
312
|
-
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.
|
|
360
|
+
`BASE_STYLES` (`display`, `font`, `preset`, `hide`, `whiteSpace`, `opacity`, `transition`) are accepted as props on every component without being listed. 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.
|
|
313
361
|
|
|
314
362
|
---
|
|
315
363
|
|
|
@@ -369,14 +417,14 @@ For sub-element syntax details (selector affix `$`, `@own()`, `elements` config)
|
|
|
369
417
|
|
|
370
418
|
## Defining the override contract
|
|
371
419
|
|
|
372
|
-
A design system works best when the rules for customization are explicit. Tasty
|
|
420
|
+
A design system works best when the rules for customization are explicit. Tasty gives product engineers four sanctioned ways to customize a component, and a few to discourage:
|
|
373
421
|
|
|
374
422
|
### What product engineers can do
|
|
375
423
|
|
|
376
424
|
1. **Use styleProps** — adjust layout, spacing, positioning through the props the component exposes:
|
|
377
425
|
|
|
378
426
|
```tsx
|
|
379
|
-
<Space flow="row" gap="3x"
|
|
427
|
+
<Space flow="row" gap="3x" placeItems="center">
|
|
380
428
|
```
|
|
381
429
|
|
|
382
430
|
2. **Use modProps** — control component states through typed props instead of `mods`:
|
|
@@ -398,7 +446,12 @@ A design system works best when the rules for customization are explicit. Tasty
|
|
|
398
446
|
```tsx
|
|
399
447
|
const DangerButton = tasty(Button, {
|
|
400
448
|
styles: {
|
|
401
|
-
fill: {
|
|
449
|
+
fill: {
|
|
450
|
+
'': '#danger',
|
|
451
|
+
':hover': '#danger-hover',
|
|
452
|
+
':active': '#danger-pressed',
|
|
453
|
+
disabled: '@inherit', // a map with '' replaces the parent's; keep its disabled branch
|
|
454
|
+
},
|
|
402
455
|
color: '#on-danger',
|
|
403
456
|
},
|
|
404
457
|
});
|
|
@@ -414,6 +467,53 @@ See [Methodology — styles prop vs style prop](methodology.md#styles-prop-vs-st
|
|
|
414
467
|
|
|
415
468
|
---
|
|
416
469
|
|
|
470
|
+
## Guardrails: linting and types
|
|
471
|
+
|
|
472
|
+
A vocabulary only holds if mistakes surface early. Register the same names you pass to `configure()` with the ESLint plugin and with TypeScript:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
// tasty.config.ts — read by @tenphi/eslint-plugin-tasty
|
|
476
|
+
export default {
|
|
477
|
+
tokens: ['#surface', '#text', '#primary', '#danger', '$gap', '$radius'],
|
|
478
|
+
states: ['@mobile', '@tablet', '@desktop', '@dark', '@reduced-motion'],
|
|
479
|
+
presets: ['h1', 'h2', 'h3', 't2', 't3'],
|
|
480
|
+
recipes: ['card', 'elevated', 'input-reset', 'interactive'],
|
|
481
|
+
importSources: ['@my-org/ds'], // where product code imports tasty() from
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
```js
|
|
486
|
+
// eslint.config.js
|
|
487
|
+
import tasty from '@tenphi/eslint-plugin-tasty';
|
|
488
|
+
|
|
489
|
+
export default [tasty.configs.recommended]; // or tasty.configs.strict
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
// tasty.d.ts
|
|
494
|
+
import '@tenphi/tasty';
|
|
495
|
+
|
|
496
|
+
declare module '@tenphi/tasty' {
|
|
497
|
+
interface TastyNamedColors {
|
|
498
|
+
surface: true;
|
|
499
|
+
text: true;
|
|
500
|
+
primary: true;
|
|
501
|
+
danger: true;
|
|
502
|
+
}
|
|
503
|
+
interface TastyPresetNames {
|
|
504
|
+
h1: true;
|
|
505
|
+
h2: true;
|
|
506
|
+
h3: true;
|
|
507
|
+
t2: true;
|
|
508
|
+
t3: true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Unknown colors, presets, and recipes then fail lint (`valid-color-token`, `valid-preset`, `valid-recipe`), and `strict` adds checks for unknown `$` tokens and state aliases (`valid-custom-property`, `no-unknown-state-alias`). The same names autocomplete in editors. Deriving both lists from the same token and recipe modules that `config.ts` imports keeps all three in sync.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
417
517
|
## Project structure
|
|
418
518
|
|
|
419
519
|
A recommended structure for a design system built on Tasty:
|
|
@@ -421,6 +521,8 @@ A recommended structure for a design system built on Tasty:
|
|
|
421
521
|
```
|
|
422
522
|
ds/
|
|
423
523
|
config.ts # configure() — tokens, units, states, recipes
|
|
524
|
+
tasty.config.ts # Names the ESLint plugin validates against
|
|
525
|
+
tasty.d.ts # TypeScript augmentation for autocomplete
|
|
424
526
|
primitives/
|
|
425
527
|
Space.tsx # Layout: flex container with FLOW_STYLES
|
|
426
528
|
Box.tsx # Generic container
|
|
@@ -436,7 +538,7 @@ ds/
|
|
|
436
538
|
tokens/
|
|
437
539
|
colors.ts # Color token definitions
|
|
438
540
|
typography.ts # Typography presets for configure({ presets })
|
|
439
|
-
|
|
541
|
+
units.ts # Spacing and shape scale for configure({ units })
|
|
440
542
|
index.ts # Public API: re-exports components + configure
|
|
441
543
|
```
|
|
442
544
|
|
package/docs/dsl.md
CHANGED
|
@@ -6,6 +6,8 @@ The same value syntax, state maps, tokens, units, extension semantics, and speci
|
|
|
6
6
|
|
|
7
7
|
For the React API (`tasty()`, style functions, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
|
|
8
8
|
|
|
9
|
+
To autocomplete your own token and preset names, see [TypeScript autocomplete](getting-started.md#typescript-autocomplete); to check state keys, tokens, units, and values at lint time, see [ESLint plugin](getting-started.md#eslint-plugin).
|
|
10
|
+
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
## Dictionary
|
|
@@ -18,6 +20,15 @@ Object where keys represent states and values are the styles to apply:
|
|
|
18
20
|
fill: { '': '#white', hovered: '#gray.05', 'theme=danger': '#red' }
|
|
19
21
|
```
|
|
20
22
|
|
|
23
|
+
Define an alias for one component by adding an `'@name': '<state expression>'` key to its styles. It works like a global alias from [`configure({ states })`](configuration.md#options), scoped to that component:
|
|
24
|
+
|
|
25
|
+
```jsx
|
|
26
|
+
styles: {
|
|
27
|
+
'@narrow': '@media(w < 600px)',
|
|
28
|
+
fill: { '': '#surface', '@narrow': '#surface-mobile' },
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
21
32
|
#### State Key Types
|
|
22
33
|
|
|
23
34
|
| Syntax | Example | Generated CSS |
|
|
@@ -34,17 +45,20 @@ fill: { '': '#white', hovered: '#gray.05', 'theme=danger': '#red' }
|
|
|
34
45
|
|
|
35
46
|
#### Automatic Native States
|
|
36
47
|
|
|
37
|
-
`disabled` and `
|
|
48
|
+
`disabled`, `checked`, and `hidden` are built-in automatic states. A Tasty component sets `data-disabled`, `data-checked`, and `data-hidden` from the native `disabled`, `checked`, and `hidden` props (or `isDisabled`, `isChecked`, `isHidden`), so each pair below expresses the same component state:
|
|
38
49
|
|
|
39
|
-
| Concise Tasty state | Explicit attribute selector |
|
|
40
|
-
| ------------------- | --------------------------- |
|
|
41
|
-
| `disabled` | `[disabled]` |
|
|
42
|
-
| `checked` | `[checked]` |
|
|
50
|
+
| Concise Tasty state | Compiles to | Explicit attribute selector |
|
|
51
|
+
| ------------------- | ----------------- | --------------------------- |
|
|
52
|
+
| `disabled` | `[data-disabled]` | `[disabled]` |
|
|
53
|
+
| `checked` | `[data-checked]` | `[checked]` |
|
|
54
|
+
| `hidden` | `[data-hidden]` | `[hidden]` |
|
|
43
55
|
|
|
44
56
|
Prefer the concise form when authoring Tasty component styles. Explicit attribute selectors remain valid and can be useful when targeting arbitrary attributes or when CSS-shaped syntax makes an introductory example easier to recognize.
|
|
45
57
|
|
|
46
58
|
Operator precedence (highest to lowest): `!` (NOT) > `^` (XOR) > `|` (OR) > `&` (AND). Use parentheses to override: `hovered & (pressed ^ focused)`.
|
|
47
59
|
|
|
60
|
+
A top-level comma is the same as `|`: `'hovered, focused'` is `'hovered | focused'`.
|
|
61
|
+
|
|
48
62
|
`^` (XOR) means "exactly one of" — `A ^ B` expands to `(A & !B) | (!A & B)`. This is useful for mutually exclusive states where exactly one should be active:
|
|
49
63
|
|
|
50
64
|
```jsx
|
|
@@ -76,9 +90,7 @@ Element styled using a capitalized key. Identified by `data-element` attribute:
|
|
|
76
90
|
|
|
77
91
|
```jsx
|
|
78
92
|
styles: {
|
|
79
|
-
Title: {
|
|
80
|
-
preset: 'h3';
|
|
81
|
-
}
|
|
93
|
+
Title: { preset: 'h3' },
|
|
82
94
|
}
|
|
83
95
|
// Targets: <div data-element="Title">
|
|
84
96
|
```
|
|
@@ -117,14 +129,16 @@ The `@` placeholder marks exactly where `[data-element="..."]` is injected, allo
|
|
|
117
129
|
const List = tasty({
|
|
118
130
|
styles: {
|
|
119
131
|
Item: {
|
|
120
|
-
$: '
|
|
121
|
-
|
|
132
|
+
$: '>@::after',
|
|
133
|
+
content: '""',
|
|
122
134
|
},
|
|
123
135
|
},
|
|
124
136
|
});
|
|
125
|
-
// → .t0 > [data-element="Item"]
|
|
137
|
+
// → .t0.t0 > [data-element="Item"]::after { content: "" }
|
|
126
138
|
```
|
|
127
139
|
|
|
140
|
+
Keep conditions such as `:last-child` out of `$` and write them as `@own(:last-child)` in a state map; `tasty/no-state-in-selector` warns otherwise.
|
|
141
|
+
|
|
128
142
|
### Color Token
|
|
129
143
|
|
|
130
144
|
Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
|
|
@@ -157,6 +171,8 @@ fill: '#current', // → currentcolor
|
|
|
157
171
|
color: '(#primary, #secondary)', // Fallback syntax
|
|
158
172
|
```
|
|
159
173
|
|
|
174
|
+
`#white`, `#black`, `#border`, and `#outline` are registered by default, and `#clear` is a built-in alias for `transparent` (see [below](#clear-is-transparent)), so they work before you define any tokens.
|
|
175
|
+
|
|
160
176
|
The suffix sets the alpha on the token's color with CSS relative color syntax:
|
|
161
177
|
|
|
162
178
|
```jsx
|
|
@@ -182,6 +198,17 @@ Two properties follow from writing the alpha slot rather than compositing:
|
|
|
182
198
|
`/ var(--fade)` unchanged, so it works whether `$fade` holds `.5` or `50%` —
|
|
183
199
|
which is what `--*-opacity` properties are registered to accept.
|
|
184
200
|
|
|
201
|
+
### `#clear` is transparent
|
|
202
|
+
|
|
203
|
+
`#clear` is a built-in [replace token](#replace-tokens) for `transparent`.
|
|
204
|
+
Opacity suffixes are ignored: `#clear.5` and `#clear.$fade` both emit
|
|
205
|
+
`transparent`. The same rule applies to user-defined replace tokens resolving
|
|
206
|
+
to `transparent`, including aliases of `#clear`.
|
|
207
|
+
|
|
208
|
+
It does not read `--clear-color` or require an `@property` registration.
|
|
209
|
+
An explicit `configure({ replaceTokens: { '#clear': ... } })` overrides the
|
|
210
|
+
default alias and uses the replacement's usual opacity behavior.
|
|
211
|
+
|
|
185
212
|
### `#current` is the keyword
|
|
186
213
|
|
|
187
214
|
`#current` emits the `currentcolor` keyword, so it resolves against the element
|
|
@@ -219,7 +246,7 @@ through `color-mix()`, which works from Safari 16.2 and composes like every othe
|
|
|
219
246
|
|
|
220
247
|
### `#current` composes instead
|
|
221
248
|
|
|
222
|
-
`#current`
|
|
249
|
+
`#current` composes alpha, and the difference is deliberate. A token _names_
|
|
223
250
|
a color, so fading it sets its alpha. `currentcolor` is the color an element
|
|
224
251
|
**inherits**, which an ancestor may already have faded — `#current.4` means "40%
|
|
225
252
|
of what reaches me":
|
|
@@ -236,9 +263,10 @@ step. Because a `color-mix()` percentage cannot be a `<number>`, an opacity
|
|
|
236
263
|
custom property used as `#current.$fade` must hold a unitless number; a token
|
|
237
264
|
accepts either form.
|
|
238
265
|
|
|
239
|
-
|
|
240
|
-
round trip that a gamut-limited space would clamp.
|
|
241
|
-
|
|
266
|
+
Token fades always use `oklch(from …)`: it is unbounded, so a wide-gamut color
|
|
267
|
+
survives a round trip that a gamut-limited space would clamp. `#current` fades
|
|
268
|
+
mix `in oklab`. Nothing configures either — see
|
|
269
|
+
[Color space](configuration.md#color-space).
|
|
242
270
|
|
|
243
271
|
---
|
|
244
272
|
|
|
@@ -256,6 +284,9 @@ border: '1bw solid oklch(from #primary l c h / 50%)', // relative color syntax
|
|
|
256
284
|
shadow: '0 0 1x color-mix(in oklab, #dark 20%, transparent)',
|
|
257
285
|
```
|
|
258
286
|
|
|
287
|
+
Tasty also ships `okhsl()` and `okhst()`, which compile to `rgb()` at parse
|
|
288
|
+
time: `fill: 'okhsl(280 80% 50%)'` → `rgb(40% 38.5% 88.2%)`.
|
|
289
|
+
|
|
259
290
|
`light-dark()` is the one exception to "always a color": CSS lets it pick between
|
|
260
291
|
values of any type, so it is treated as a color only when its arguments are
|
|
261
292
|
colors. `padding: 'light-dark(1x, 2x)'` still reaches the padding slot.
|
|
@@ -293,16 +324,16 @@ fill: '#adaptive.5'; // → oklch(from light-dark(…) l c h / .5)
|
|
|
293
324
|
|
|
294
325
|
## Built-in Units
|
|
295
326
|
|
|
296
|
-
| Unit | Description | Example | CSS
|
|
297
|
-
| ---- | ------------------ | ------- |
|
|
298
|
-
| `x` |
|
|
299
|
-
| `r` | Border radius | `1r` | `
|
|
300
|
-
| `cr` | Card border radius | `1cr` | `
|
|
301
|
-
| `bw` | Border width | `2bw` | `
|
|
302
|
-
| `ow` | Outline width | `1ow` | `
|
|
303
|
-
| `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)`
|
|
327
|
+
| Unit | Description | Default | Example | CSS output |
|
|
328
|
+
| ---- | ------------------ | ---------------- | ------- | ---------------- |
|
|
329
|
+
| `x` | Spacing step | `8px` | `2x` | `16px` |
|
|
330
|
+
| `r` | Border radius | `6px` | `1r` | `6px` |
|
|
331
|
+
| `cr` | Card border radius | `10px` | `1cr` | `10px` |
|
|
332
|
+
| `bw` | Border width | `1px` | `2bw` | `2px` |
|
|
333
|
+
| `ow` | Outline width | `3px` | `1ow` | `3px` |
|
|
334
|
+
| `sf` | Stable fraction | `minmax(0, Nfr)` | `1sf` | `minmax(0, 1fr)` |
|
|
304
335
|
|
|
305
|
-
|
|
336
|
+
Units compile to plain values. Change the scale with `configure({ units: { x: '4px' } })`, or point a unit at a custom property when it must change at runtime: with `units: { x: 'var(--gap)' }`, `2x` compiles to `calc(2 * var(--gap))`. Setting a `$gap` or `$radius` token does not change the units. Register additional units the same way via [`configure()`](configuration.md#options).
|
|
306
337
|
|
|
307
338
|
---
|
|
308
339
|
|
|
@@ -359,7 +390,7 @@ const CardWithPanel = tasty({
|
|
|
359
390
|
});
|
|
360
391
|
```
|
|
361
392
|
|
|
362
|
-
At each level, base recipes are collected in
|
|
393
|
+
At each level, base recipes are collected in the order listed (later recipes replace earlier values for the same property), then local styles are merged, then post recipes are merged. The merges with local styles and post recipes follow the [state-map extension and replacement rules](#extending-vs-replacing-state-maps).
|
|
363
394
|
|
|
364
395
|
Recipe definitions themselves cannot contain a `recipe` reference to another recipe: there is no recursive expansion or recipe inheritance. Compose names in the root or sub-element `recipe` property instead. See [Configuration — Recipes](configuration.md#recipes) for an unsupported nesting example.
|
|
365
396
|
|
|
@@ -439,12 +470,14 @@ fill: {
|
|
|
439
470
|
Use `null` inside a state map to remove a state, or `false` to block it entirely (tombstone):
|
|
440
471
|
|
|
441
472
|
```jsx
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
473
|
+
const MyButton = tasty(Button, {
|
|
474
|
+
styles: {
|
|
475
|
+
fill: {
|
|
476
|
+
hovered: null, // removes the parent's hovered state
|
|
477
|
+
disabled: false, // tombstone — no CSS for disabled, blocks recipe too
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
});
|
|
448
481
|
```
|
|
449
482
|
|
|
450
483
|
### Resetting Properties with `null` and `false`
|
|
@@ -494,7 +527,7 @@ const SimpleButton = tasty(Button, {
|
|
|
494
527
|
|
|
495
528
|
### `@media(...)` — Media Queries
|
|
496
529
|
|
|
497
|
-
Media queries support dimension shorthands
|
|
530
|
+
Media queries support dimension shorthands:
|
|
498
531
|
|
|
499
532
|
| Shorthand | Expands to |
|
|
500
533
|
| --------- | ---------- |
|
|
@@ -516,10 +549,14 @@ fill: {
|
|
|
516
549
|
| `@media(600px <= w < 1200px)` | `@media (600px <= width < 1200px)` |
|
|
517
550
|
| `@media:print` | `@media print` |
|
|
518
551
|
| `@media:screen` | `@media screen` |
|
|
552
|
+
| `@media:all` | `@media all` |
|
|
553
|
+
| `@media:speech` | `@media speech` |
|
|
519
554
|
| `@media(prefers-color-scheme: dark)` | `@media (prefers-color-scheme: dark)` |
|
|
520
555
|
| `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
|
|
521
556
|
|
|
522
|
-
|
|
557
|
+
Tasty units resolve to their literal length inside query conditions, built-in and configured units alike: `@media(w < 40x)` emits `@media (width < 320px)`. A query condition cannot read a custom property, so a unit configured as one (`units: { x: 'var(--gap)' }`) cannot be used there; write that length in `px`, `em`, or `rem`.
|
|
558
|
+
|
|
559
|
+
Units are only converted in features that take a length: `width`, `height`, `inline-size`, and `block-size`, with or without `min-`/`max-` (`@media(min-width: 40x)` converts too). Every other feature keeps its value as written, so `@media(min-resolution: 2x)` and `@media(resolution >= 2x)` reach the browser unchanged: there `x` is CSS's own resolution unit, an alias of `dppx`.
|
|
523
560
|
|
|
524
561
|
In practice, define state aliases via `configure({ states })` and use `@mobile` instead of writing the full query in every component.
|
|
525
562
|
|
|
@@ -545,28 +582,30 @@ const Panel = tasty({
|
|
|
545
582
|
});
|
|
546
583
|
```
|
|
547
584
|
|
|
548
|
-
| Tasty syntax | CSS output
|
|
549
|
-
| ---------------------------------- |
|
|
550
|
-
| `@(layout, w < 600px)` | `@container layout (width < 600px)`
|
|
551
|
-
| `@(w < 600px)` | `@container (width < 600px)`
|
|
552
|
-
| `@(layout, $variant=danger)` | `@container layout style(--variant:
|
|
553
|
-
| `@(layout, $compact)` | `@container layout style(--compact)`
|
|
554
|
-
| `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)`
|
|
555
|
-
| `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)`
|
|
585
|
+
| Tasty syntax | CSS output |
|
|
586
|
+
| ---------------------------------- | -------------------------------------------- |
|
|
587
|
+
| `@(layout, w < 600px)` | `@container layout (width < 600px)` |
|
|
588
|
+
| `@(w < 600px)` | `@container (width < 600px)` |
|
|
589
|
+
| `@(layout, $variant=danger)` | `@container layout style(--variant: danger)` |
|
|
590
|
+
| `@(layout, $compact)` | `@container layout style(--compact)` |
|
|
591
|
+
| `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)` |
|
|
592
|
+
| `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)` |
|
|
593
|
+
|
|
594
|
+
Container style queries use `$prop` (boolean) or `$prop=value` syntax, which maps to CSS `style(--prop)` or `style(--prop: value)`. The value is emitted unquoted, so it matches a custom property set to the identifier `danger`, not the string `"danger"`.
|
|
556
595
|
|
|
557
|
-
|
|
596
|
+
Other size features pass through as written: `@(aspect-ratio > 1)` → `@container (aspect-ratio > 1)`, `@(orientation: portrait)` → `@container (orientation: portrait)`. They need a container with `containerType: 'size'`, since an `inline-size` container has no block size to compare against.
|
|
558
597
|
|
|
559
598
|
### `@supports(...)` — Feature Queries
|
|
560
599
|
|
|
561
600
|
Feature queries test CSS property support and accept CSS support functions such
|
|
562
601
|
as `at-rule()`. Use `$` as the first argument to test selector support:
|
|
563
602
|
|
|
564
|
-
| Tasty syntax
|
|
565
|
-
|
|
|
566
|
-
| `@supports(display: grid)`
|
|
567
|
-
| `@supports($, :has(*))` | `@supports selector(:has(*))`
|
|
568
|
-
| `@supports(at-rule(@scope))` | `@supports (at-rule(@scope))`
|
|
569
|
-
| `!@supports(display: grid)`
|
|
603
|
+
| Tasty syntax | CSS output |
|
|
604
|
+
| ----------------------------- | ----------------------------------- |
|
|
605
|
+
| `@supports(display: grid)` | `@supports (display: grid)` |
|
|
606
|
+
| `@supports($, :has(*))` | `@supports selector(:has(*))` |
|
|
607
|
+
| `@supports(at-rule(@scope))` | `@supports (at-rule(@scope))` |
|
|
608
|
+
| `!@supports(display: grid)` | `@supports (not (display: grid))` |
|
|
570
609
|
| `!@supports(at-rule(@scope))` | `@supports (not (at-rule(@scope)))` |
|
|
571
610
|
|
|
572
611
|
```jsx
|
|
@@ -620,12 +659,12 @@ inset: {
|
|
|
620
659
|
|
|
621
660
|
```css
|
|
622
661
|
.t0.t0 {
|
|
623
|
-
inset: 0
|
|
662
|
+
inset: 0 auto auto auto;
|
|
624
663
|
}
|
|
625
|
-
@container scroll-state
|
|
626
|
-
@
|
|
664
|
+
@supports (container-type: scroll-state) {
|
|
665
|
+
@container scroll-state(scrolled: block-end) {
|
|
627
666
|
.t0.t0 {
|
|
628
|
-
inset: -
|
|
667
|
+
inset: -640px auto auto auto;
|
|
629
668
|
}
|
|
630
669
|
}
|
|
631
670
|
}
|
|
@@ -634,7 +673,7 @@ inset: {
|
|
|
634
673
|
The floor is emitted as a bare rule (no negated `@supports` / container branches),
|
|
635
674
|
so it always applies. When the override matches it wins because it is emitted
|
|
636
675
|
later and all state selectors share the same specificity (see the note on
|
|
637
|
-
`:where()`
|
|
676
|
+
`:where()` above).
|
|
638
677
|
|
|
639
678
|
Notes:
|
|
640
679
|
|
|
@@ -706,7 +745,7 @@ color: {
|
|
|
706
745
|
| `@root([lang="en"])` | `:root[lang="en"]` |
|
|
707
746
|
| `!@root(schema=dark)` | `:root:not([data-schema="dark"])` |
|
|
708
747
|
|
|
709
|
-
Root conditions are prepended to the component selector
|
|
748
|
+
Root conditions are prepended to the component selector inside `:where()`: `:where(:root[data-schema="dark"]) .t0.t0 { ... }`.
|
|
710
749
|
|
|
711
750
|
### `@own(...)` — Sub-element's Own State
|
|
712
751
|
|
|
@@ -752,11 +791,11 @@ const FadeIn = tasty({
|
|
|
752
791
|
|
|
753
792
|
| Tasty syntax | CSS output |
|
|
754
793
|
| ------------ | ------------------------------------ |
|
|
755
|
-
| `@starting` |
|
|
794
|
+
| `@starting` | `.t0.t0 { @starting-style { ... } }` |
|
|
756
795
|
|
|
757
796
|
### `@parent(...)` — Parent Element States
|
|
758
797
|
|
|
759
|
-
Style based on ancestor element attributes. Uses `:
|
|
798
|
+
Style based on ancestor element attributes. Uses `:where([selector] *)` / `:where(:not([selector] *))` for symmetric, composable parent checks. Boolean logic (`&`, `|`, `!`, `^`) is supported inside `@parent()`.
|
|
760
799
|
|
|
761
800
|
```jsx
|
|
762
801
|
const Highlight = tasty({
|
|
@@ -770,15 +809,15 @@ const Highlight = tasty({
|
|
|
770
809
|
});
|
|
771
810
|
```
|
|
772
811
|
|
|
773
|
-
| Syntax | CSS Output
|
|
774
|
-
| ------------------------------------- |
|
|
775
|
-
| `@parent(hovered)` | `:
|
|
776
|
-
| `!@parent(hovered)` | `:not([data-hovered] *)`
|
|
777
|
-
| `@parent(hovered, >)` | `:
|
|
778
|
-
| `@parent(.active)` | `:
|
|
779
|
-
| `@parent(hovered & focused)` | `:
|
|
780
|
-
| `@parent(hovered) & @parent(focused)` | `:
|
|
781
|
-
| `@parent(hovered \| focused)` | `:
|
|
812
|
+
| Syntax | CSS Output |
|
|
813
|
+
| ------------------------------------- | -------------------------------------------------------------------------- |
|
|
814
|
+
| `@parent(hovered)` | `:where([data-hovered] *)` |
|
|
815
|
+
| `!@parent(hovered)` | `:where(:not([data-hovered] *))` |
|
|
816
|
+
| `@parent(hovered, >)` | `:where([data-hovered] > *)` (direct parent) |
|
|
817
|
+
| `@parent(.active)` | `:where(.active *)` |
|
|
818
|
+
| `@parent(hovered & focused)` | `:where([data-hovered][data-focused] *)` (same ancestor) |
|
|
819
|
+
| `@parent(hovered) & @parent(focused)` | `:where([data-hovered] *):where([data-focused] *)` (independent ancestors) |
|
|
820
|
+
| `@parent(hovered \| focused)` | `:where([data-hovered] *, [data-focused] *)` (OR inside single wrapper) |
|
|
782
821
|
|
|
783
822
|
For sub-elements, the parent check applies to the root element's ancestors:
|
|
784
823
|
|
|
@@ -793,12 +832,12 @@ const Card = tasty({
|
|
|
793
832
|
},
|
|
794
833
|
},
|
|
795
834
|
});
|
|
796
|
-
// → .t0.t0:
|
|
835
|
+
// → .t0.t0:where([data-hovered] *) [data-element="Label"]
|
|
797
836
|
```
|
|
798
837
|
|
|
799
838
|
### `:is()`, `:has()` — CSS Structural Pseudo-classes
|
|
800
839
|
|
|
801
|
-
Use CSS structural pseudo-classes directly in state keys. Capitalized words become `[data-element="..."]` selectors; lowercase words are HTML tags. A trailing combinator (`>`, `+`, `~`) is auto-completed with `*`.
|
|
840
|
+
Use CSS structural pseudo-classes directly in state keys. Capitalized words become `[data-element="..."]` selectors; lowercase words are HTML tags. A trailing combinator (`>`, `+`, `~`) is auto-completed with `*`. Leading combinators (`> Icon`, `+ Icon`) are relative selectors and are valid only inside `:has()`.
|
|
802
841
|
|
|
803
842
|
`:where()` and `:not()` are also supported but rarely needed — use `:is()` and `!` negation instead.
|
|
804
843
|
|
|
@@ -821,18 +860,18 @@ const Card = tasty({
|
|
|
821
860
|
});
|
|
822
861
|
```
|
|
823
862
|
|
|
824
|
-
| Syntax
|
|
825
|
-
|
|
|
826
|
-
| `:has(> Icon)`
|
|
827
|
-
| `:has(+ Icon)`
|
|
828
|
-
| `:has(~ Icon)`
|
|
829
|
-
| `:has(Icon +)`
|
|
830
|
-
| `:has(Icon ~)`
|
|
831
|
-
| `:has(>)`
|
|
832
|
-
| `:is(
|
|
833
|
-
| `:has(button)`
|
|
834
|
-
| `!:has(> Icon)`
|
|
835
|
-
| `!:is(Panel)`
|
|
863
|
+
| Syntax | CSS Output | Meaning |
|
|
864
|
+
| -------------------- | ------------------------------------- | --------------------------------------- |
|
|
865
|
+
| `:has(> Icon)` | `:has(> [data-element="Icon"])` | Has Icon as direct child |
|
|
866
|
+
| `:has(+ Icon)` | `:has(+ [data-element="Icon"])` | Immediately followed by an Icon sibling |
|
|
867
|
+
| `:has(~ Icon)` | `:has(~ [data-element="Icon"])` | Has an Icon sibling somewhere after |
|
|
868
|
+
| `:has(Icon +)` | `:has([data-element="Icon"] + *)` | Immediately preceded by an Icon sibling |
|
|
869
|
+
| `:has(Icon ~)` | `:has([data-element="Icon"] ~ *)` | Has an Icon sibling somewhere before |
|
|
870
|
+
| `:has(>)` | `:has(> *)` | Has any direct child |
|
|
871
|
+
| `:is(Field + input)` | `:is([data-element="Field"] + input)` | Is an input right after a Field sibling |
|
|
872
|
+
| `:has(button)` | `:has(button)` | HTML tag (lowercase, unchanged) |
|
|
873
|
+
| `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
|
|
874
|
+
| `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
|
|
836
875
|
|
|
837
876
|
Combine with other states using boolean logic (`&`, `|`, `!`, `^`):
|
|
838
877
|
|
|
@@ -954,7 +993,7 @@ Define custom list markers via the CSS `@counter-style` at-rule. Keys are counte
|
|
|
954
993
|
|
|
955
994
|
```ts
|
|
956
995
|
const EmojiList = tasty({
|
|
957
|
-
|
|
996
|
+
as: 'ol',
|
|
958
997
|
styles: {
|
|
959
998
|
'@counter-style': {
|
|
960
999
|
thumbs: {
|
|
@@ -1046,7 +1085,7 @@ Generated CSS:
|
|
|
1046
1085
|
|
|
1047
1086
|
A `FunctionParameter` is one of: `true` (bare param), a string CSS type shorthand (`'<length>'`), or `{ syntax?: string; default?: string | number }`.
|
|
1048
1087
|
|
|
1049
|
-
Functions can also be registered with the [`useFunction`](react-api.md#usefunction)
|
|
1088
|
+
Functions can also be registered with the [`useFunction`](react-api.md#usefunction) style function or globally via [`configure({ functions })`](configuration.md#functions) (the same map also accepts bare-key parse functions).
|
|
1050
1089
|
|
|
1051
1090
|
> `@function` is permanent — injected once and never cleaned up, like `@counter-style`. It is an experimental CSS feature ([Chrome 139+](https://developer.mozilla.org/en-US/docs/Web/CSS/@function)); unsupported browsers safely ignore the native rule, so use it as progressive enhancement — or enable the [`configure({ polyfills: { functions: true } })`](configuration.md#polyfills) inline polyfill to expand `$$name(...)` calls into plain CSS for full cross-browser support.
|
|
1052
1091
|
|