@tenphi/tasty 0.0.0-snapshot.06c8a41 → 0.0.0-snapshot.07ea4a0
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 +408 -385
- 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-CW4k04_V.js +60 -0
- package/dist/chunks/build-config-CW4k04_V.js.map +1 -0
- package/dist/{collector-B3OsM252.js → chunks/collector-DKWQXEwN.js} +10 -127
- package/dist/chunks/collector-DKWQXEwN.js.map +1 -0
- package/dist/chunks/config-engine-DTglOA78.js +272 -0
- package/dist/chunks/config-engine-DTglOA78.js.map +1 -0
- package/dist/chunks/config-file-DlZXJMj8.js +25 -0
- package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
- package/dist/chunks/css-definitions-_C7C-daI.js +910 -0
- package/dist/chunks/css-definitions-_C7C-daI.js.map +1 -0
- package/dist/{css-resources-Cyl_axbI.js → chunks/css-resources-P7IBUUZs.js} +2 -9
- package/dist/chunks/css-resources-P7IBUUZs.js.map +1 -0
- package/dist/chunks/debug-BapJwy9w.js +502 -0
- package/dist/chunks/debug-BapJwy9w.js.map +1 -0
- package/dist/chunks/dsl-DYSJc1-T.js +1383 -0
- package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
- package/dist/chunks/hydration-TRcz5vnk.js +41 -0
- package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
- package/dist/chunks/react-runtime-DayjnPuC.js +689 -0
- package/dist/chunks/react-runtime-DayjnPuC.js.map +1 -0
- package/dist/chunks/runtime-engine-IwGbSMb4.js +2432 -0
- package/dist/chunks/runtime-engine-IwGbSMb4.js.map +1 -0
- package/dist/chunks/runtime-state-CVrL92zX.js +152 -0
- package/dist/chunks/runtime-state-CVrL92zX.js.map +1 -0
- package/dist/{merge-styles-DuoZEsm9.js → chunks/shared-utils-y-HjvPN4.js} +60 -55
- package/dist/chunks/shared-utils-y-HjvPN4.js.map +1 -0
- package/dist/chunks/style-engine-CVmNIjow.js +5521 -0
- package/dist/chunks/style-engine-CVmNIjow.js.map +1 -0
- package/dist/{css-writer-Bgowobbx.js → chunks/zero-engine-igHhzYM6.js} +65 -145
- package/dist/chunks/zero-engine-igHhzYM6.js.map +1 -0
- package/dist/{collector-BTVXj2hS.d.ts → collector-CTU-nl0g.d.ts} +6 -5
- package/dist/{config-LfIDmVHx.d.ts → config-CcxqSZI2.d.ts} +45 -40
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +9 -6
- package/dist/hydrate-CRyjjAyl.d.ts +22 -0
- package/dist/{index-Bm9ltvdH.d.ts → index--GdqCPlp.d.ts} +271 -242
- package/dist/index.d.ts +5 -5
- package/dist/index.js +11 -922
- package/dist/{merge-styles-BcLl1u_d.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -3
- package/dist/ssr/astro-client.d.ts +1 -1
- package/dist/ssr/astro-client.js +1 -14
- package/dist/ssr/astro-client.js.map +1 -1
- package/dist/ssr/astro-middleware-extract-static.d.ts +1 -2
- package/dist/ssr/astro-middleware-extract-static.js +1 -1
- package/dist/ssr/astro-middleware-extract.d.ts +1 -2
- package/dist/ssr/astro-middleware-extract.js +1 -1
- package/dist/ssr/astro-middleware-static.d.ts +1 -2
- package/dist/ssr/astro-middleware-static.js +1 -12
- package/dist/ssr/astro-middleware-static.js.map +1 -1
- package/dist/ssr/astro-middleware.d.ts +1 -2
- package/dist/ssr/astro-middleware.js +1 -13
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.d.ts +8 -16
- package/dist/ssr/astro.js +220 -1
- package/dist/ssr/astro.js.map +1 -0
- package/dist/ssr/client.d.ts +2 -0
- package/dist/ssr/client.js +2 -0
- package/dist/ssr/index.d.ts +5 -25
- package/dist/ssr/index.js +5 -5
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +3 -5
- package/dist/ssr/next-config.js +43 -35
- package/dist/ssr/next-config.js.map +1 -1
- package/dist/ssr/next.d.ts +3 -9
- package/dist/ssr/next.js +7 -38
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +8 -9
- package/dist/static/index.js +4 -22
- package/dist/static/index.js.map +1 -1
- package/dist/static/inject.d.ts +1 -2
- package/dist/{index-BmogEzTo.d.ts → types-DJEM_BOJ.d.ts} +852 -860
- package/dist/zero/babel.d.ts +67 -2
- package/dist/zero/babel.js +77 -140
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +62 -8
- package/dist/zero/index.js +24 -2
- package/dist/zero/index.js.map +1 -0
- package/dist/zero/next.d.ts +4 -5
- package/dist/zero/next.js +3 -38
- 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 +66 -33
- package/docs/comparison.md +9 -8
- package/docs/configuration.md +192 -60
- package/docs/debug.md +5 -7
- package/docs/design-system.md +117 -15
- package/docs/dsl.md +204 -82
- package/docs/getting-started.md +81 -22
- package/docs/injector.md +41 -40
- package/docs/methodology.md +48 -23
- package/docs/migration-v3.md +3 -3
- package/docs/pipeline.md +54 -27
- package/docs/plugins.md +22 -42
- package/docs/react-api.md +72 -9
- package/docs/runtime-benchmarks.md +146 -91
- package/docs/ssr.md +92 -66
- package/docs/styles.md +117 -31
- package/docs/tasty-static.md +162 -58
- package/package.json +96 -38
- package/dist/astro-CeYENy2x.js +0 -301
- package/dist/astro-CeYENy2x.js.map +0 -1
- package/dist/async-storage-DKK-wTD4.js +0 -44
- package/dist/async-storage-DKK-wTD4.js.map +0 -1
- package/dist/babel-DWCsafYw.d.ts +0 -83
- package/dist/collector-B3OsM252.js.map +0 -1
- package/dist/config-B3gPdCqd.js +0 -12421
- package/dist/config-B3gPdCqd.js.map +0 -1
- package/dist/context-CA8YKeMn.js +0 -24
- package/dist/context-CA8YKeMn.js.map +0 -1
- package/dist/core-DGm0CFHP.js +0 -1732
- package/dist/core-DGm0CFHP.js.map +0 -1
- package/dist/css-resources-Cyl_axbI.js.map +0 -1
- package/dist/css-writer-Bgowobbx.js.map +0 -1
- package/dist/format-global-rules-DklyaXv-.js +0 -22
- package/dist/format-global-rules-DklyaXv-.js.map +0 -1
- package/dist/format-rules-XRw9u7d4.js +0 -104
- package/dist/format-rules-XRw9u7d4.js.map +0 -1
- package/dist/hydrate-CNOmZprz.js +0 -37
- package/dist/hydrate-CNOmZprz.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keyframes-DE-OE76F.js +0 -535
- package/dist/keyframes-DE-OE76F.js.map +0 -1
- package/dist/merge-styles-DuoZEsm9.js.map +0 -1
- package/dist/resolve-recipes-H9NqOQuP.js +0 -144
- package/dist/resolve-recipes-H9NqOQuP.js.map +0 -1
- package/dist/ssr-collector-ref-COs_ioWl.js +0 -29
- package/dist/ssr-collector-ref-COs_ioWl.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
|
|
@@ -54,6 +68,8 @@ fill: {
|
|
|
54
68
|
}
|
|
55
69
|
```
|
|
56
70
|
|
|
71
|
+
Modifier and class names start with a letter and may be a single letter (`x` → `[data-x]`, `.x`). Tasty emits an `INVALID_STATE_KEY` warning for a key it cannot fully read and skips the unreadable parts. That covers characters outside the notation (`1st`, or `@media (w < 600px)` with a space before the parenthesis), an operator missing a state (`hovered &`), and two states with no operator between them (`hovered focused`). A partly read key can apply in states you did not mean, so fix the key rather than rely on what it produces.
|
|
72
|
+
|
|
57
73
|
#### Default State Ordering
|
|
58
74
|
|
|
59
75
|
Key order sets priority — later keys win and turn off earlier ones via negation. The bare default (`''`) is the lowest-priority state, so it **must be the first key**. If it appears after other states, Tasty moves it to the front and emits a dev warning (`MISPLACED_DEFAULT_STATE`); otherwise it would override every state above it.
|
|
@@ -74,9 +90,7 @@ Element styled using a capitalized key. Identified by `data-element` attribute:
|
|
|
74
90
|
|
|
75
91
|
```jsx
|
|
76
92
|
styles: {
|
|
77
|
-
Title: {
|
|
78
|
-
preset: 'h3';
|
|
79
|
-
}
|
|
93
|
+
Title: { preset: 'h3' },
|
|
80
94
|
}
|
|
81
95
|
// Targets: <div data-element="Title">
|
|
82
96
|
```
|
|
@@ -115,14 +129,16 @@ The `@` placeholder marks exactly where `[data-element="..."]` is injected, allo
|
|
|
115
129
|
const List = tasty({
|
|
116
130
|
styles: {
|
|
117
131
|
Item: {
|
|
118
|
-
$: '
|
|
119
|
-
|
|
132
|
+
$: '>@::after',
|
|
133
|
+
content: '""',
|
|
120
134
|
},
|
|
121
135
|
},
|
|
122
136
|
});
|
|
123
|
-
// → .
|
|
137
|
+
// → .t1x29r0l.t1x29r0l > [data-element="Item"]::after { content: ""; }
|
|
124
138
|
```
|
|
125
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. This works when `$` ends in a pseudo-element too: the condition attaches to the element the pseudo-element belongs to (see [`@own(...)`](#own--sub-elements-own-state)).
|
|
141
|
+
|
|
126
142
|
### Color Token
|
|
127
143
|
|
|
128
144
|
Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
|
|
@@ -155,6 +171,10 @@ fill: '#current', // → currentcolor
|
|
|
155
171
|
color: '(#primary, #secondary)', // Fallback syntax
|
|
156
172
|
```
|
|
157
173
|
|
|
174
|
+
`#white`, `#black`, `#border`, and `#outline` are registered by default (see [Default properties](#default-properties)), and `#clear` is a built-in alias for `transparent` (see [below](#clear-is-transparent)), so they work before you define any tokens.
|
|
175
|
+
|
|
176
|
+
A name that is also a hex color is read as a token with the literal as its fallback, in every color style: `color: '#ddd'` emits `var(--ddd-color, #ddd)`, and `#ddd.5` emits `oklch(from var(--ddd-color, #ddd) l c h / .5)`. A `#ddd` token, if you define one, wins over the literal.
|
|
177
|
+
|
|
158
178
|
The suffix sets the alpha on the token's color with CSS relative color syntax:
|
|
159
179
|
|
|
160
180
|
```jsx
|
|
@@ -180,6 +200,17 @@ Two properties follow from writing the alpha slot rather than compositing:
|
|
|
180
200
|
`/ var(--fade)` unchanged, so it works whether `$fade` holds `.5` or `50%` —
|
|
181
201
|
which is what `--*-opacity` properties are registered to accept.
|
|
182
202
|
|
|
203
|
+
### `#clear` is transparent
|
|
204
|
+
|
|
205
|
+
`#clear` is a built-in [replace token](#replace-tokens) for `transparent`.
|
|
206
|
+
Opacity suffixes are ignored: `#clear.5` and `#clear.$fade` both emit
|
|
207
|
+
`transparent`. The same rule applies to user-defined replace tokens resolving
|
|
208
|
+
to `transparent`, including aliases of `#clear`.
|
|
209
|
+
|
|
210
|
+
It does not read `--clear-color` or require an `@property` registration.
|
|
211
|
+
An explicit `configure({ replaceTokens: { '#clear': ... } })` overrides the
|
|
212
|
+
default alias and uses the replacement's usual opacity behavior.
|
|
213
|
+
|
|
183
214
|
### `#current` is the keyword
|
|
184
215
|
|
|
185
216
|
`#current` emits the `currentcolor` keyword, so it resolves against the element
|
|
@@ -217,7 +248,7 @@ through `color-mix()`, which works from Safari 16.2 and composes like every othe
|
|
|
217
248
|
|
|
218
249
|
### `#current` composes instead
|
|
219
250
|
|
|
220
|
-
`#current`
|
|
251
|
+
`#current` composes alpha, and the difference is deliberate. A token _names_
|
|
221
252
|
a color, so fading it sets its alpha. `currentcolor` is the color an element
|
|
222
253
|
**inherits**, which an ancestor may already have faded — `#current.4` means "40%
|
|
223
254
|
of what reaches me":
|
|
@@ -234,9 +265,10 @@ step. Because a `color-mix()` percentage cannot be a `<number>`, an opacity
|
|
|
234
265
|
custom property used as `#current.$fade` must hold a unitless number; a token
|
|
235
266
|
accepts either form.
|
|
236
267
|
|
|
237
|
-
|
|
238
|
-
round trip that a gamut-limited space would clamp.
|
|
239
|
-
|
|
268
|
+
Token fades always use `oklch(from …)`: it is unbounded, so a wide-gamut color
|
|
269
|
+
survives a round trip that a gamut-limited space would clamp. `#current` fades
|
|
270
|
+
mix `in oklab`. Nothing configures either — see
|
|
271
|
+
[Color space](configuration.md#color-space).
|
|
240
272
|
|
|
241
273
|
---
|
|
242
274
|
|
|
@@ -254,6 +286,9 @@ border: '1bw solid oklch(from #primary l c h / 50%)', // relative color syntax
|
|
|
254
286
|
shadow: '0 0 1x color-mix(in oklab, #dark 20%, transparent)',
|
|
255
287
|
```
|
|
256
288
|
|
|
289
|
+
Tasty also ships `okhsl()` and `okhst()`, which compile to `rgb()` at parse
|
|
290
|
+
time: `fill: 'okhsl(280 80% 50%)'` → `rgb(40% 38.5% 88.2%)`.
|
|
291
|
+
|
|
257
292
|
`light-dark()` is the one exception to "always a color": CSS lets it pick between
|
|
258
293
|
values of any type, so it is treated as a color only when its arguments are
|
|
259
294
|
colors. `padding: 'light-dark(1x, 2x)'` still reaches the padding slot.
|
|
@@ -291,16 +326,16 @@ fill: '#adaptive.5'; // → oklch(from light-dark(…) l c h / .5)
|
|
|
291
326
|
|
|
292
327
|
## Built-in Units
|
|
293
328
|
|
|
294
|
-
| Unit | Description | Example | CSS
|
|
295
|
-
| ---- | ------------------ | ------- |
|
|
296
|
-
| `x` |
|
|
297
|
-
| `r` | Border radius | `1r` | `
|
|
298
|
-
| `cr` | Card border radius | `1cr` | `
|
|
299
|
-
| `bw` | Border width | `2bw` | `
|
|
300
|
-
| `ow` | Outline width | `1ow` | `
|
|
301
|
-
| `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)`
|
|
329
|
+
| Unit | Description | Default | Example | CSS output |
|
|
330
|
+
| ---- | ------------------ | ---------------- | ------- | ---------------- |
|
|
331
|
+
| `x` | Spacing step | `8px` | `2x` | `16px` |
|
|
332
|
+
| `r` | Border radius | `6px` | `1r` | `6px` |
|
|
333
|
+
| `cr` | Card border radius | `10px` | `1cr` | `10px` |
|
|
334
|
+
| `bw` | Border width | `1px` | `2bw` | `2px` |
|
|
335
|
+
| `ow` | Outline width | `3px` | `1ow` | `3px` |
|
|
336
|
+
| `sf` | Stable fraction | `minmax(0, Nfr)` | `1sf` | `minmax(0, 1fr)` |
|
|
302
337
|
|
|
303
|
-
|
|
338
|
+
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).
|
|
304
339
|
|
|
305
340
|
---
|
|
306
341
|
|
|
@@ -341,6 +376,26 @@ const ElevatedCard = tasty({
|
|
|
341
376
|
});
|
|
342
377
|
```
|
|
343
378
|
|
|
379
|
+
**Where recipes resolve:**
|
|
380
|
+
|
|
381
|
+
The `recipe` property is resolved on the component root and independently inside each sub-element's styles:
|
|
382
|
+
|
|
383
|
+
```jsx
|
|
384
|
+
const CardWithPanel = tasty({
|
|
385
|
+
styles: {
|
|
386
|
+
recipe: 'card',
|
|
387
|
+
Panel: {
|
|
388
|
+
recipe: 'card elevated',
|
|
389
|
+
padding: '2x', // Overrides the panel's base recipe padding
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
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).
|
|
396
|
+
|
|
397
|
+
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.
|
|
398
|
+
|
|
344
399
|
**Post-merge recipes (`/` separator):**
|
|
345
400
|
|
|
346
401
|
Recipes listed after `/` are applied _after_ component styles using `mergeStyles`:
|
|
@@ -417,12 +472,14 @@ fill: {
|
|
|
417
472
|
Use `null` inside a state map to remove a state, or `false` to block it entirely (tombstone):
|
|
418
473
|
|
|
419
474
|
```jsx
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
475
|
+
const MyButton = tasty(Button, {
|
|
476
|
+
styles: {
|
|
477
|
+
fill: {
|
|
478
|
+
hovered: null, // removes the parent's hovered state
|
|
479
|
+
disabled: false, // tombstone — no CSS for disabled, blocks recipe too
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
});
|
|
426
483
|
```
|
|
427
484
|
|
|
428
485
|
### Resetting Properties with `null` and `false`
|
|
@@ -472,7 +529,7 @@ const SimpleButton = tasty(Button, {
|
|
|
472
529
|
|
|
473
530
|
### `@media(...)` — Media Queries
|
|
474
531
|
|
|
475
|
-
Media queries support dimension shorthands
|
|
532
|
+
Media queries support dimension shorthands:
|
|
476
533
|
|
|
477
534
|
| Shorthand | Expands to |
|
|
478
535
|
| --------- | ---------- |
|
|
@@ -494,10 +551,17 @@ fill: {
|
|
|
494
551
|
| `@media(600px <= w < 1200px)` | `@media (600px <= width < 1200px)` |
|
|
495
552
|
| `@media:print` | `@media print` |
|
|
496
553
|
| `@media:screen` | `@media screen` |
|
|
554
|
+
| `@media:all` | `@media all` |
|
|
555
|
+
| `@media:speech` | `@media speech` |
|
|
497
556
|
| `@media(prefers-color-scheme: dark)` | `@media (prefers-color-scheme: dark)` |
|
|
498
557
|
| `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
|
|
558
|
+
| `@media(aspect-ratio > 16/9)` | `@media (aspect-ratio > 16/9)` |
|
|
559
|
+
|
|
560
|
+
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`.
|
|
499
561
|
|
|
500
|
-
|
|
562
|
+
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`.
|
|
563
|
+
|
|
564
|
+
When it combines or orders conditions, Tasty compares bounds only within one unit. `@media(w >= 600px) & @media(w < 40em)` keeps both bounds, because a query `em` follows the user's font size, so `600px` and `40em` cannot be ordered at build time.
|
|
501
565
|
|
|
502
566
|
In practice, define state aliases via `configure({ states })` and use `@mobile` instead of writing the full query in every component.
|
|
503
567
|
|
|
@@ -523,26 +587,31 @@ const Panel = tasty({
|
|
|
523
587
|
});
|
|
524
588
|
```
|
|
525
589
|
|
|
526
|
-
| Tasty syntax | CSS output
|
|
527
|
-
| ---------------------------------- |
|
|
528
|
-
| `@(layout, w < 600px)` | `@container layout (width < 600px)`
|
|
529
|
-
| `@(w < 600px)` | `@container (width < 600px)`
|
|
530
|
-
| `@(layout, $variant=danger)` | `@container layout style(--variant:
|
|
531
|
-
| `@(layout, $compact)` | `@container layout style(--compact)`
|
|
532
|
-
| `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)`
|
|
533
|
-
| `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)`
|
|
590
|
+
| Tasty syntax | CSS output |
|
|
591
|
+
| ---------------------------------- | -------------------------------------------- |
|
|
592
|
+
| `@(layout, w < 600px)` | `@container layout (width < 600px)` |
|
|
593
|
+
| `@(w < 600px)` | `@container (width < 600px)` |
|
|
594
|
+
| `@(layout, $variant=danger)` | `@container layout style(--variant: danger)` |
|
|
595
|
+
| `@(layout, $compact)` | `@container layout style(--compact)` |
|
|
596
|
+
| `@(scroll-state(stuck: top))` | `@container scroll-state(stuck: top)` |
|
|
597
|
+
| `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)` |
|
|
598
|
+
|
|
599
|
+
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"`.
|
|
534
600
|
|
|
535
|
-
|
|
601
|
+
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.
|
|
536
602
|
|
|
537
603
|
### `@supports(...)` — Feature Queries
|
|
538
604
|
|
|
539
|
-
Feature queries test CSS property support
|
|
605
|
+
Feature queries test CSS property support and accept CSS support functions such
|
|
606
|
+
as `at-rule()`. Use `$` as the first argument to test selector support:
|
|
540
607
|
|
|
541
|
-
| Tasty syntax
|
|
542
|
-
|
|
|
543
|
-
| `@supports(display: grid)`
|
|
544
|
-
| `@supports($, :has(*))`
|
|
545
|
-
|
|
|
608
|
+
| Tasty syntax | CSS output |
|
|
609
|
+
| ----------------------------- | ----------------------------------- |
|
|
610
|
+
| `@supports(display: grid)` | `@supports (display: grid)` |
|
|
611
|
+
| `@supports($, :has(*))` | `@supports selector(:has(*))` |
|
|
612
|
+
| `@supports(at-rule(@scope))` | `@supports (at-rule(@scope))` |
|
|
613
|
+
| `!@supports(display: grid)` | `@supports (not (display: grid))` |
|
|
614
|
+
| `!@supports(at-rule(@scope))` | `@supports (not (at-rule(@scope)))` |
|
|
546
615
|
|
|
547
616
|
```jsx
|
|
548
617
|
display: {
|
|
@@ -551,6 +620,25 @@ display: {
|
|
|
551
620
|
}
|
|
552
621
|
```
|
|
553
622
|
|
|
623
|
+
`at-rule()` tests whether the browser supports the named at-rule. Its surrounding
|
|
624
|
+
parentheses in Tasty's CSS output are valid CSS. Negation and combinations with
|
|
625
|
+
other states use the same syntax as property queries:
|
|
626
|
+
|
|
627
|
+
```jsx
|
|
628
|
+
color: {
|
|
629
|
+
_: 'red',
|
|
630
|
+
'@supports(at-rule(@scope)) & @supports(display: grid)': 'blue',
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Use `_` to keep a base value when a browser cannot evaluate a query. A support
|
|
635
|
+
query only detects a browser feature; it does not add an API for authoring the
|
|
636
|
+
queried at-rule in a Tasty style object. For conditional stylesheet imports such
|
|
637
|
+
as `@import url("scoped.css") supports(at-rule(@scope));`, use an ordinary CSS
|
|
638
|
+
stylesheet or document-level `useRawCSS()`. Put imports before other rules in
|
|
639
|
+
that stylesheet. Imports cannot be used in constructable stylesheets, including
|
|
640
|
+
Tasty's adopted-sheet mode for Shadow DOM.
|
|
641
|
+
|
|
554
642
|
### `_` — Fallback Floor
|
|
555
643
|
|
|
556
644
|
By default Tasty makes states **mutually exclusive**: a higher-priority state
|
|
@@ -576,12 +664,12 @@ inset: {
|
|
|
576
664
|
|
|
577
665
|
```css
|
|
578
666
|
.t0.t0 {
|
|
579
|
-
inset: 0
|
|
667
|
+
inset: 0 auto auto auto;
|
|
580
668
|
}
|
|
581
|
-
@container scroll-state
|
|
582
|
-
@
|
|
669
|
+
@supports (container-type: scroll-state) {
|
|
670
|
+
@container scroll-state(scrolled: block-end) {
|
|
583
671
|
.t0.t0 {
|
|
584
|
-
inset: -
|
|
672
|
+
inset: -640px auto auto auto;
|
|
585
673
|
}
|
|
586
674
|
}
|
|
587
675
|
}
|
|
@@ -590,7 +678,7 @@ inset: {
|
|
|
590
678
|
The floor is emitted as a bare rule (no negated `@supports` / container branches),
|
|
591
679
|
so it always applies. When the override matches it wins because it is emitted
|
|
592
680
|
later and all state selectors share the same specificity (see the note on
|
|
593
|
-
`:where()`
|
|
681
|
+
`:where()` above).
|
|
594
682
|
|
|
595
683
|
Notes:
|
|
596
684
|
|
|
@@ -662,7 +750,7 @@ color: {
|
|
|
662
750
|
| `@root([lang="en"])` | `:root[lang="en"]` |
|
|
663
751
|
| `!@root(schema=dark)` | `:root:not([data-schema="dark"])` |
|
|
664
752
|
|
|
665
|
-
Root conditions are prepended to the component selector
|
|
753
|
+
Root conditions are prepended to the component selector inside `:where()`: `:where(:root[data-schema="dark"]) .t0.t0 { ... }`.
|
|
666
754
|
|
|
667
755
|
### `@own(...)` — Sub-element's Own State
|
|
668
756
|
|
|
@@ -692,6 +780,18 @@ const Nav = tasty({
|
|
|
692
780
|
|
|
693
781
|
`@own()` is only valid inside sub-element styles. Using it on root styles emits a warning and is treated as a regular modifier.
|
|
694
782
|
|
|
783
|
+
When `$` ends in a pseudo-element, the condition attaches to the originating element, before the pseudo-element. CSS accepts no `:where()` after a pseudo-element:
|
|
784
|
+
|
|
785
|
+
```jsx
|
|
786
|
+
Icon: {
|
|
787
|
+
$: 'a::before',
|
|
788
|
+
content: { '': 'none', '@own([rel="next"])': '"→"' },
|
|
789
|
+
},
|
|
790
|
+
// → .t0.t0 a:where([rel="next"])::before { content: "→"; }
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
This covers every pseudo-element form: `::before`, `::marker`, `::backdrop`, vendor-prefixed ones such as `::-webkit-details-marker`, functional ones such as `::part(label)`, and the legacy `:before`, `:after`, `:first-line`, `:first-letter`. In a `$` list, each item gets the condition before its own pseudo-element. A pseudo-class written after the pseudo-element in `$`, as in `'@::part(label):hover'`, qualifies the pseudo-element and stays after it.
|
|
794
|
+
|
|
695
795
|
### `@starting` — Entry Animation
|
|
696
796
|
|
|
697
797
|
Wraps the rule in `@starting-style`, enabling CSS entry animations for elements as they appear in the DOM:
|
|
@@ -701,18 +801,18 @@ const FadeIn = tasty({
|
|
|
701
801
|
styles: {
|
|
702
802
|
opacity: { '': '1', '@starting': '0' },
|
|
703
803
|
transform: { '': 'scale(1)', '@starting': 'scale(0.95)' },
|
|
704
|
-
transition: 'opacity
|
|
804
|
+
transition: 'opacity, translate',
|
|
705
805
|
},
|
|
706
806
|
});
|
|
707
807
|
```
|
|
708
808
|
|
|
709
809
|
| Tasty syntax | CSS output |
|
|
710
810
|
| ------------ | ------------------------------------ |
|
|
711
|
-
| `@starting` |
|
|
811
|
+
| `@starting` | `.t0.t0 { @starting-style { ... } }` |
|
|
712
812
|
|
|
713
813
|
### `@parent(...)` — Parent Element States
|
|
714
814
|
|
|
715
|
-
Style based on ancestor element attributes. Uses `:
|
|
815
|
+
Style based on ancestor element attributes. Uses `:where([selector] *)` / `:where(:not([selector] *))` for symmetric, composable parent checks. Boolean logic (`&`, `|`, `!`, `^`) is supported inside `@parent()`.
|
|
716
816
|
|
|
717
817
|
```jsx
|
|
718
818
|
const Highlight = tasty({
|
|
@@ -726,15 +826,15 @@ const Highlight = tasty({
|
|
|
726
826
|
});
|
|
727
827
|
```
|
|
728
828
|
|
|
729
|
-
| Syntax | CSS Output
|
|
730
|
-
| ------------------------------------- |
|
|
731
|
-
| `@parent(hovered)` | `:
|
|
732
|
-
| `!@parent(hovered)` | `:not([data-hovered] *)`
|
|
733
|
-
| `@parent(hovered, >)` | `:
|
|
734
|
-
| `@parent(.active)` | `:
|
|
735
|
-
| `@parent(hovered & focused)` | `:
|
|
736
|
-
| `@parent(hovered) & @parent(focused)` | `:
|
|
737
|
-
| `@parent(hovered \| focused)` | `:
|
|
829
|
+
| Syntax | CSS Output |
|
|
830
|
+
| ------------------------------------- | -------------------------------------------------------------------------- |
|
|
831
|
+
| `@parent(hovered)` | `:where([data-hovered] *)` |
|
|
832
|
+
| `!@parent(hovered)` | `:where(:not([data-hovered] *))` |
|
|
833
|
+
| `@parent(hovered, >)` | `:where([data-hovered] > *)` (direct parent) |
|
|
834
|
+
| `@parent(.active)` | `:where(.active *)` |
|
|
835
|
+
| `@parent(hovered & focused)` | `:where([data-hovered][data-focused] *)` (same ancestor) |
|
|
836
|
+
| `@parent(hovered) & @parent(focused)` | `:where([data-hovered] *):where([data-focused] *)` (independent ancestors) |
|
|
837
|
+
| `@parent(hovered \| focused)` | `:where([data-hovered] *, [data-focused] *)` (OR inside single wrapper) |
|
|
738
838
|
|
|
739
839
|
For sub-elements, the parent check applies to the root element's ancestors:
|
|
740
840
|
|
|
@@ -749,12 +849,12 @@ const Card = tasty({
|
|
|
749
849
|
},
|
|
750
850
|
},
|
|
751
851
|
});
|
|
752
|
-
// → .t0.t0:
|
|
852
|
+
// → .t0.t0:where([data-hovered] *) [data-element="Label"]
|
|
753
853
|
```
|
|
754
854
|
|
|
755
855
|
### `:is()`, `:has()` — CSS Structural Pseudo-classes
|
|
756
856
|
|
|
757
|
-
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 `*`.
|
|
857
|
+
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()`.
|
|
758
858
|
|
|
759
859
|
`:where()` and `:not()` are also supported but rarely needed — use `:is()` and `!` negation instead.
|
|
760
860
|
|
|
@@ -777,18 +877,18 @@ const Card = tasty({
|
|
|
777
877
|
});
|
|
778
878
|
```
|
|
779
879
|
|
|
780
|
-
| Syntax
|
|
781
|
-
|
|
|
782
|
-
| `:has(> Icon)`
|
|
783
|
-
| `:has(+ Icon)`
|
|
784
|
-
| `:has(~ Icon)`
|
|
785
|
-
| `:has(Icon +)`
|
|
786
|
-
| `:has(Icon ~)`
|
|
787
|
-
| `:has(>)`
|
|
788
|
-
| `:is(
|
|
789
|
-
| `:has(button)`
|
|
790
|
-
| `!:has(> Icon)`
|
|
791
|
-
| `!:is(Panel)`
|
|
880
|
+
| Syntax | CSS Output | Meaning |
|
|
881
|
+
| -------------------- | ------------------------------------- | --------------------------------------- |
|
|
882
|
+
| `:has(> Icon)` | `:has(> [data-element="Icon"])` | Has Icon as direct child |
|
|
883
|
+
| `:has(+ Icon)` | `:has(+ [data-element="Icon"])` | Immediately followed by an Icon sibling |
|
|
884
|
+
| `:has(~ Icon)` | `:has(~ [data-element="Icon"])` | Has an Icon sibling somewhere after |
|
|
885
|
+
| `:has(Icon +)` | `:has([data-element="Icon"] + *)` | Immediately preceded by an Icon sibling |
|
|
886
|
+
| `:has(Icon ~)` | `:has([data-element="Icon"] ~ *)` | Has an Icon sibling somewhere before |
|
|
887
|
+
| `:has(>)` | `:has(> *)` | Has any direct child |
|
|
888
|
+
| `:is(Field + input)` | `:is([data-element="Field"] + input)` | Is an input right after a Field sibling |
|
|
889
|
+
| `:has(button)` | `:has(button)` | HTML tag (lowercase, unchanged) |
|
|
890
|
+
| `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
|
|
891
|
+
| `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
|
|
792
892
|
|
|
793
893
|
Combine with other states using boolean logic (`&`, `|`, `!`, `^`):
|
|
794
894
|
|
|
@@ -821,6 +921,8 @@ const Pulse = tasty({
|
|
|
821
921
|
});
|
|
822
922
|
```
|
|
823
923
|
|
|
924
|
+
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.
|
|
925
|
+
|
|
824
926
|
---
|
|
825
927
|
|
|
826
928
|
## Properties (`@property`)
|
|
@@ -833,7 +935,7 @@ const AnimatedGradient = tasty({
|
|
|
833
935
|
'$gradient-angle': '0deg',
|
|
834
936
|
'#theme': 'okhst(280 80% 50%)',
|
|
835
937
|
image: 'linear-gradient($gradient-angle, #theme, #clear)',
|
|
836
|
-
transition: '$$gradient-angle
|
|
938
|
+
transition: '$$gradient-angle, ##theme',
|
|
837
939
|
},
|
|
838
940
|
});
|
|
839
941
|
```
|
|
@@ -848,6 +950,26 @@ Use explicit `@property` when you need non-default settings like `inherits: fals
|
|
|
848
950
|
},
|
|
849
951
|
```
|
|
850
952
|
|
|
953
|
+
### Default properties
|
|
954
|
+
|
|
955
|
+
Tasty registers these properties itself. Style handlers read their defaults from them rather than repeating the values inline, so styles work before you define any tokens, and defining a token replaces the initial value.
|
|
956
|
+
|
|
957
|
+
| Token | Initial value | Read by |
|
|
958
|
+
| ---------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------- |
|
|
959
|
+
| `#current` | `currentcolor` | anything reading `$current-color`, which `color` sets |
|
|
960
|
+
| `#white`, `#black` | `rgb(255 255 255)`, `rgb(0 0 0)` | your styles |
|
|
961
|
+
| `#border` | `currentcolor` | `border` with no color |
|
|
962
|
+
| `#outline` | `currentcolor` | `outline` with no color |
|
|
963
|
+
| `$gap`, `$radius`, `$border-width`, `$outline-width` | `8px`, `6px`, `1px`, `3px` | units you point at them, as in `units: { x: 'var(--gap)' }` |
|
|
964
|
+
| `$transition` | `80ms` | `transition` with no duration |
|
|
965
|
+
| `$sharp-radius` | `0px` | the sharp corners of `radius: 'leaf'` and `radius: 'backleaf'` |
|
|
966
|
+
| `$bold-font-weight` | `700` | `preset: 'name / strong'` |
|
|
967
|
+
| `$font-sans-fallback`, `$font-mono-fallback` | a `system-ui` and a `ui-monospace` stack | `font` and presets, after `$font-sans` / `$font-mono` |
|
|
968
|
+
|
|
969
|
+
Every rendering path emits them: the client injector, the SSR collector (`TastyRegistry`, `tastyIntegration()`, `runWithCollector()`), the `withTastyNext()` shared stylesheet, and the Babel plugin. CSS written with the programmatic API needs [`extractConfigProperties()`](tasty-static.md#programmatic-api), once per stylesheet. Server rendering without a collector, such as Astro without `tastyIntegration()`, emits none of them.
|
|
970
|
+
|
|
971
|
+
The initial values need `@property` support: Chrome and Edge 85+, Firefox 128+, Safari 16.4+. In older engines a handler that reads one of these tokens resolves to nothing unless your tokens define it: a `border` with no color draws no border, and a `transition` with no duration does not animate. To support those engines, define the tokens your styles rely on in `configure({ tokens })`.
|
|
972
|
+
|
|
851
973
|
---
|
|
852
974
|
|
|
853
975
|
## Font Face (`@font-face`)
|
|
@@ -908,7 +1030,7 @@ Define custom list markers via the CSS `@counter-style` at-rule. Keys are counte
|
|
|
908
1030
|
|
|
909
1031
|
```ts
|
|
910
1032
|
const EmojiList = tasty({
|
|
911
|
-
|
|
1033
|
+
as: 'ol',
|
|
912
1034
|
styles: {
|
|
913
1035
|
'@counter-style': {
|
|
914
1036
|
thumbs: {
|
|
@@ -1000,7 +1122,7 @@ Generated CSS:
|
|
|
1000
1122
|
|
|
1001
1123
|
A `FunctionParameter` is one of: `true` (bare param), a string CSS type shorthand (`'<length>'`), or `{ syntax?: string; default?: string | number }`.
|
|
1002
1124
|
|
|
1003
|
-
Functions can also be registered with the [`useFunction`](react-api.md#usefunction)
|
|
1125
|
+
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).
|
|
1004
1126
|
|
|
1005
1127
|
> `@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.
|
|
1006
1128
|
|