@tenphi/tasty 3.9.11 → 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-BKDTT4--.d.ts +18 -0
- package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
- package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
- package/dist/chunks/build-config-5d8WlQbq.js +60 -0
- package/dist/chunks/build-config-5d8WlQbq.js.map +1 -0
- package/dist/chunks/{collector-D2ARGT2L.js → collector-CjCak4iE.js} +8 -6
- package/dist/chunks/collector-CjCak4iE.js.map +1 -0
- package/dist/chunks/{config-engine-DEmmPPdp.js → config-engine-uOFYZidB.js} +4 -4
- package/dist/chunks/{config-engine-DEmmPPdp.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-DotDkIXt.js → css-definitions-ubrHP97m.js} +130 -96
- package/dist/chunks/css-definitions-ubrHP97m.js.map +1 -0
- package/dist/chunks/{debug-D8pg9RGI.js → debug-CJ31U_WE.js} +6 -6
- package/dist/chunks/{debug-D8pg9RGI.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-BpxlOTbO.js → react-runtime-CyyCI02N.js} +190 -63
- package/dist/chunks/react-runtime-CyyCI02N.js.map +1 -0
- package/dist/chunks/{runtime-engine-CKFXUH-A.js → runtime-engine-BQmOu5-W.js} +69 -53
- package/dist/chunks/runtime-engine-BQmOu5-W.js.map +1 -0
- package/dist/chunks/{runtime-state-BxaLhVdP.js → runtime-state-DEsyUMU-.js} +28 -3
- package/dist/chunks/runtime-state-DEsyUMU-.js.map +1 -0
- package/dist/chunks/{style-engine-BBbBuSkT.js → style-engine-BX2LshDZ.js} +309 -103
- package/dist/chunks/style-engine-BX2LshDZ.js.map +1 -0
- package/dist/chunks/{zero-engine-gJrKBRiw.js → zero-engine-Dw0P4yGM.js} +62 -27
- 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-CZWCzBr6.d.ts → config-CcxqSZI2.d.ts} +35 -16
- 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-G9JuwM9p.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 +13 -18
- 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 +67 -2
- package/dist/zero/babel.js +47 -44
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +58 -3
- package/dist/zero/index.js +24 -2
- package/dist/zero/index.js.map +1 -0
- package/dist/zero/next.d.ts +2 -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 +174 -59
- package/docs/debug.md +3 -2
- package/docs/design-system.md +117 -15
- package/docs/dsl.md +122 -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 +20 -17
- package/docs/plugins.md +20 -40
- package/docs/react-api.md +28 -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 +142 -55
- package/package.json +24 -10
- package/dist/babel-rZcEwPLM.d.ts +0 -82
- package/dist/chunks/async-storage-CG5HnUT5.js +0 -20
- package/dist/chunks/async-storage-CG5HnUT5.js.map +0 -1
- package/dist/chunks/build-config-CL4eRu2z.js +0 -42
- package/dist/chunks/build-config-CL4eRu2z.js.map +0 -1
- package/dist/chunks/collector-D2ARGT2L.js.map +0 -1
- package/dist/chunks/css-definitions-DotDkIXt.js.map +0 -1
- package/dist/chunks/dsl-DRhFWQzo.js.map +0 -1
- package/dist/chunks/react-runtime-BpxlOTbO.js.map +0 -1
- package/dist/chunks/runtime-engine-CKFXUH-A.js.map +0 -1
- package/dist/chunks/runtime-state-BxaLhVdP.js.map +0 -1
- package/dist/chunks/style-engine-BBbBuSkT.js.map +0 -1
- package/dist/chunks/zero-engine-gJrKBRiw.js.map +0 -1
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
|
|
|
@@ -865,6 +904,8 @@ const Pulse = tasty({
|
|
|
865
904
|
});
|
|
866
905
|
```
|
|
867
906
|
|
|
907
|
+
Keyframes names are case-sensitive and keep their authored case in `animation` and `animationName` (`fadeIn`, not `fadein`), so they match `@keyframes` defined here, in `configure({ keyframes })`, or in plain CSS. Everything else in the value — durations, timing functions, keywords — is parsed as usual.
|
|
908
|
+
|
|
868
909
|
---
|
|
869
910
|
|
|
870
911
|
## Properties (`@property`)
|
|
@@ -952,7 +993,7 @@ Define custom list markers via the CSS `@counter-style` at-rule. Keys are counte
|
|
|
952
993
|
|
|
953
994
|
```ts
|
|
954
995
|
const EmojiList = tasty({
|
|
955
|
-
|
|
996
|
+
as: 'ol',
|
|
956
997
|
styles: {
|
|
957
998
|
'@counter-style': {
|
|
958
999
|
thumbs: {
|
|
@@ -1044,7 +1085,7 @@ Generated CSS:
|
|
|
1044
1085
|
|
|
1045
1086
|
A `FunctionParameter` is one of: `true` (bare param), a string CSS type shorthand (`'<length>'`), or `{ syntax?: string; default?: string | number }`.
|
|
1046
1087
|
|
|
1047
|
-
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).
|
|
1048
1089
|
|
|
1049
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.
|
|
1050
1091
|
|
package/docs/getting-started.md
CHANGED
|
@@ -64,7 +64,7 @@ export default function App() {
|
|
|
64
64
|
|
|
65
65
|
`tasty()` returns a normal React component. The keys in each state map define priority: later branches win over earlier ones. Because `disabled` is last, a disabled button keeps its disabled styles even while the pointer is over it.
|
|
66
66
|
|
|
67
|
-
`disabled` and `
|
|
67
|
+
`disabled`, `checked`, and `hidden` are built-in automatic states. Tasty sets `data-disabled`, `data-checked`, and `data-hidden` from the matching native prop, so `disabled` in a state map is the concise form of `[disabled]` (it compiles to `[data-disabled]`).
|
|
68
68
|
|
|
69
69
|
Tasty compiles that priority into selectors that exclude one another. The hover, active, and disabled background rules cannot all match and ask the CSS cascade to decide the winner.
|
|
70
70
|
|
|
@@ -101,9 +101,9 @@ import App from './App';
|
|
|
101
101
|
createRoot(document.getElementById('root')!).render(<App />);
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
### Define shared tokens and
|
|
104
|
+
### Define shared tokens and units
|
|
105
105
|
|
|
106
|
-
Color tokens like `#primary` resolve to CSS custom properties
|
|
106
|
+
Color tokens like `#primary` and value tokens like `$sidebar-width` resolve to CSS custom properties (`var(--primary-color)`, `var(--sidebar-width)`). Define their values with `configure({ tokens })`:
|
|
107
107
|
|
|
108
108
|
```tsx
|
|
109
109
|
// src/tasty-config.ts
|
|
@@ -114,10 +114,7 @@ configure({
|
|
|
114
114
|
'#primary': 'oklch(55% 0.25 265)',
|
|
115
115
|
'#surface': '#fff',
|
|
116
116
|
'#text': '#111',
|
|
117
|
-
$
|
|
118
|
-
$radius: '4px',
|
|
119
|
-
'$border-width': '1px',
|
|
120
|
-
'$outline-width': '2px',
|
|
117
|
+
'$sidebar-width': '280px',
|
|
121
118
|
},
|
|
122
119
|
});
|
|
123
120
|
```
|
|
@@ -135,7 +132,20 @@ configure({
|
|
|
135
132
|
});
|
|
136
133
|
```
|
|
137
134
|
|
|
138
|
-
|
|
135
|
+
Tokens are injected as `:root` CSS custom properties when the first style is rendered, and every component using `#primary` follows them. You can also use standard CSS color values such as `rgb(...)`, `hsl(...)`, and named colors directly; the built-in `okhsl(...)` and `okhst(...)` functions are the recommended choice when you want authored colors that stay aligned with Tasty's design-system-oriented workflow.
|
|
136
|
+
|
|
137
|
+
Units work differently: `x`, `r`, `cr`, `bw`, and `ow` compile to plain values when styles are generated (`2x` → `16px` with the default `8px` step), so setting a `$gap` token does not change them. Change the scale with `configure({ units })`:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
configure({
|
|
141
|
+
units: {
|
|
142
|
+
x: '4px', // spacing step: 2x → 8px
|
|
143
|
+
r: '8px', // radius: 1r → 8px
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
When the scale has to change at runtime, for example per theme or density, point the unit at a custom property and define that property as a token. With `units: { x: 'var(--gap)' }` and `tokens: { $gap: '8px' }`, `2x` compiles to `calc(2 * var(--gap))`. See [Built-in Units](dsl.md#built-in-units) for the defaults.
|
|
139
149
|
|
|
140
150
|
> **Note:** `configure({ replaceTokens })` is a separate mechanism — it replaces tokens with literal values at parse time (baked into CSS). Use it for value aliases like `$card-padding: '4x'` that should be resolved during style generation, not for defining color or unit values. See [Configuration — Replace Tokens](configuration.md#replace-tokens-parse-time-substitution) for details.
|
|
141
151
|
|
|
@@ -145,7 +155,7 @@ See [Configuration](configuration.md) for the full `configure()` API — tokens,
|
|
|
145
155
|
|
|
146
156
|
## ESLint plugin
|
|
147
157
|
|
|
148
|
-
The ESLint plugin catches invalid style properties, bad token references, malformed state keys, and other mistakes at lint time — before they reach the browser.
|
|
158
|
+
The ESLint plugin catches invalid style properties, bad token references, malformed state keys, and other mistakes at lint time — before they reach the browser. It checks style objects and JSX style props alike, and runs under both ESLint 9+ and oxlint.
|
|
149
159
|
|
|
150
160
|
### Install
|
|
151
161
|
|
|
@@ -167,36 +177,80 @@ export default [
|
|
|
167
177
|
];
|
|
168
178
|
```
|
|
169
179
|
|
|
170
|
-
###
|
|
180
|
+
### Tell it about your design system
|
|
181
|
+
|
|
182
|
+
Without project configuration the plugin checks syntax and property names, but it cannot know which tokens, presets, or recipes exist. Add a `tasty.config.ts` at the project root to enable those checks for the names your `configure()` call defines:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// tasty.config.ts
|
|
186
|
+
export default {
|
|
187
|
+
tokens: ['#primary', '#surface', '#text', '$sidebar-width'],
|
|
188
|
+
states: ['@mobile', '@dark'],
|
|
189
|
+
presets: ['h1', 'h2', 't1', 't2'],
|
|
190
|
+
recipes: ['card'],
|
|
191
|
+
importSources: ['@my-org/ui'], // modules that re-export tasty()
|
|
192
|
+
};
|
|
193
|
+
```
|
|
171
194
|
|
|
172
|
-
|
|
195
|
+
Every list is optional; leaving one out skips that check. `importSources` matters when your design system re-exports `tasty()` from its own module: the plugin only lints calls whose import it can trace.
|
|
173
196
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
197
|
+
### What `recommended` catches
|
|
198
|
+
|
|
199
|
+
| Category | Rules | Examples |
|
|
200
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
201
|
+
| Property validation | `known-property`, `valid-boolean-property`, `valid-sub-element` | Flags typos like `pading` or invalid boolean usage |
|
|
202
|
+
| Value validation | `valid-value`, `valid-color-token`, `valid-custom-unit` | Catches `#nonexistent` tokens, bad unit syntax |
|
|
203
|
+
| State validation | `valid-state-key`, `no-nested-state-map`, `require-default-state` | Validates state key syntax, ensures `''` default exists |
|
|
204
|
+
| Structure | `valid-styles-structure`, `no-important`, `no-nested-selector` | Prevents `!important`, invalid nesting |
|
|
205
|
+
| Static mode | `static-no-dynamic-values`, `static-valid-selector` | Enforces build-time constraints in `tastyStatic()` |
|
|
206
|
+
| Style properties | `valid-preset`, `valid-recipe`, `valid-transition`, `valid-directional-modifier`, `valid-radius-shape` | Validates preset names, recipe references, transition syntax |
|
|
207
|
+
| Token usage | `no-raw-color-values`, `consistent-token-usage`, `no-raw-transition-duration` | Suggests `#color` tokens over raw colors, units over raw pixel lengths |
|
|
208
|
+
| Preferred syntax | `prefer-shorthand-property`, `prefer-auto-calc`, `prefer-hide`, `prefer-state-negation`, and others | Suggests `fill` over `backgroundColor`, `(...)` over `calc(...)` |
|
|
209
|
+
| Component props | `no-style-prop`, `no-classname-prop`, `no-styles-prop` | Points escape hatches toward `tokens`, `mods`, and the component's props |
|
|
210
|
+
| Dynamic styles | `no-runtime-styles-mutation`, `no-style-spread` | Warns on JavaScript values and spreads inside runtime style objects |
|
|
184
211
|
|
|
185
212
|
### Strict config
|
|
186
213
|
|
|
187
|
-
For stricter governance, use `tasty.configs.strict`. It adds
|
|
214
|
+
For stricter governance, use `tasty.configs.strict`. It adds existence checks for `$` custom properties and `@` state aliases, and validates state definitions in `configure()` and `tasty.config.ts`:
|
|
188
215
|
|
|
189
216
|
```js
|
|
190
217
|
export default [tasty.configs.strict];
|
|
191
218
|
```
|
|
192
219
|
|
|
220
|
+
See the [plugin README](https://github.com/tenphi/eslint-plugin-tasty) for every rule, the oxlint setup, and the experimental type-aware mode for JSX props.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## TypeScript autocomplete
|
|
225
|
+
|
|
226
|
+
Component props are typed from their definitions without extra setup: `modProps` value lists become unions, `variant` is typed from the variant keys, and sub-elements are typed components. To get autocomplete for your own token, preset, and theme names inside style values, register them with module augmentation:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// tasty.d.ts
|
|
230
|
+
import '@tenphi/tasty';
|
|
231
|
+
|
|
232
|
+
declare module '@tenphi/tasty' {
|
|
233
|
+
interface TastyNamedColors {
|
|
234
|
+
primary: true;
|
|
235
|
+
surface: true;
|
|
236
|
+
text: true;
|
|
237
|
+
}
|
|
238
|
+
interface TastyPresetNames {
|
|
239
|
+
h1: true;
|
|
240
|
+
t2: true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
See [Configuration → TypeScript](configuration.md#typescript) for every augmentation interface and how component props are typed.
|
|
246
|
+
|
|
193
247
|
---
|
|
194
248
|
|
|
195
249
|
## Editor support
|
|
196
250
|
|
|
197
|
-
**[VS Code Extension](https://github.com/tenphi/tasty-vscode-extension)** — Syntax highlighting for Tasty styles in TypeScript/TSX/JavaScript/JSX
|
|
251
|
+
**[VS Code Extension](https://github.com/tenphi/tasty-vscode-extension)** — Syntax highlighting for Tasty styles in TypeScript/TSX/JavaScript/JSX: color tokens, custom units, state keys, presets, and style properties inside `tasty()`, `tastyStatic()`, `styles` objects, and style functions. It also adds snippets for components, state maps, and `configure()`. Install the `.vsix` from the [latest GitHub release](https://github.com/tenphi/tasty-vscode-extension/releases/latest); the extension is not published to the marketplace.
|
|
198
252
|
|
|
199
|
-
**[Glaze](https://github.com/tenphi/glaze)** —
|
|
253
|
+
**[Glaze](https://github.com/tenphi/glaze)** — Not an editor tool, but the fastest way to fill `configure({ tokens })`: it generates light, dark, and high-contrast palettes from one hue in the OKHST color space, solves WCAG and APCA contrast, and exports them as Tasty tokens with `configure({ tokens: theme.tasty() })`. See [Glaze in the README](../README.md#glaze).
|
|
200
254
|
|
|
201
255
|
---
|
|
202
256
|
|
|
@@ -228,6 +282,7 @@ Both `tasty()` and `tastyStatic()` share the same DSL, tokens, units, and state
|
|
|
228
282
|
- **[Adoption Guide](adoption.md)** — Roll out Tasty inside an existing design system or platform team
|
|
229
283
|
- **[Comparison](comparison.md)** — Evaluate Tasty against other styling systems
|
|
230
284
|
- **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers, TypeScript extensions
|
|
285
|
+
- **[Plugins & Extension Points](plugins.md)** — Add your own style properties, units, functions, and component props
|
|
231
286
|
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
|
232
287
|
- **[Debug Utilities](debug.md)** — Inspect generated CSS and debug runtime behavior when styles do not look right
|
|
233
288
|
|
|
@@ -237,4 +292,6 @@ Both `tasty()` and `tastyStatic()` share the same DSL, tokens, units, and state
|
|
|
237
292
|
|
|
238
293
|
- Styles are missing on first render: make sure the file that calls `configure()` is imported before any `tasty()` component renders.
|
|
239
294
|
- Token or unit values are not what you expect: check your `configure({ tokens, units })` setup, then inspect the generated CSS variables with [Debug Utilities](debug.md).
|
|
295
|
+
- Changing a `$gap` or `$radius` token does not change `2x` or `1r`: units compile to plain values from `configure({ units })`. Point a unit at the custom property (`units: { x: 'var(--gap)' }`) if it should follow the token.
|
|
296
|
+
- The linter accepts unknown tokens or presets: list them in `tasty.config.ts` so the plugin knows what your design system defines.
|
|
240
297
|
- You need zero-runtime delivery: use server-only [`tasty()` with server rendering](ssr.md) to keep the full React API, or use [`tastyStatic()` build-time extraction](tasty-static.md) when styles must be compiled before rendering.
|