@tenphi/tasty 0.0.0-snapshot.1d29633 → 0.0.0-snapshot.1f1c00a
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 +152 -134
- package/dist/{babel-Cg-mJV_t.d.ts → babel-rZcEwPLM.d.ts} +2 -3
- package/dist/chunks/async-storage-CG5HnUT5.js +20 -0
- package/dist/chunks/async-storage-CG5HnUT5.js.map +1 -0
- package/dist/chunks/build-config-CL4eRu2z.js +42 -0
- package/dist/chunks/build-config-CL4eRu2z.js.map +1 -0
- package/dist/{collector-BMT05AmU.js → chunks/collector-D2ARGT2L.js} +21 -117
- package/dist/chunks/collector-D2ARGT2L.js.map +1 -0
- package/dist/chunks/config-engine-DEmmPPdp.js +272 -0
- package/dist/chunks/config-engine-DEmmPPdp.js.map +1 -0
- package/dist/chunks/css-definitions-DotDkIXt.js +876 -0
- package/dist/chunks/css-definitions-DotDkIXt.js.map +1 -0
- package/dist/chunks/css-resources-P7IBUUZs.js +142 -0
- package/dist/chunks/css-resources-P7IBUUZs.js.map +1 -0
- package/dist/chunks/debug-D8pg9RGI.js +502 -0
- package/dist/chunks/debug-D8pg9RGI.js.map +1 -0
- package/dist/chunks/dsl-DRhFWQzo.js +1378 -0
- package/dist/chunks/dsl-DRhFWQzo.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-BpxlOTbO.js +562 -0
- package/dist/chunks/react-runtime-BpxlOTbO.js.map +1 -0
- package/dist/chunks/runtime-engine-CKFXUH-A.js +2416 -0
- package/dist/chunks/runtime-engine-CKFXUH-A.js.map +1 -0
- package/dist/chunks/runtime-state-BxaLhVdP.js +127 -0
- package/dist/chunks/runtime-state-BxaLhVdP.js.map +1 -0
- package/dist/{merge-styles-CkAcnd0Z.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-BBbBuSkT.js +5260 -0
- package/dist/chunks/style-engine-BBbBuSkT.js.map +1 -0
- package/dist/{css-writer-b02uBqNF.js → chunks/zero-engine-gJrKBRiw.js} +6 -121
- package/dist/chunks/zero-engine-gJrKBRiw.js.map +1 -0
- package/dist/{collector-BFuqdF2F.d.ts → collector-w4mBj5gd.d.ts} +23 -4
- package/dist/{config-DmFxnBxg.d.ts → config-CZWCzBr6.d.ts} +171 -72
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +9 -6
- package/dist/{index-D1nMkpUe.d.ts → index-G9JuwM9p.d.ts} +263 -247
- package/dist/index.d.ts +5 -5
- package/dist/index.js +11 -921
- package/dist/{merge-styles-Cx_6ySBv.d.ts → merge-styles-VXwJ0Frr.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 +10 -0
- package/dist/ssr/astro-middleware-extract-static.js +9 -0
- package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
- package/dist/ssr/astro-middleware-extract.d.ts +10 -0
- package/dist/ssr/astro-middleware-extract.js +9 -0
- package/dist/ssr/astro-middleware-extract.js.map +1 -0
- package/dist/ssr/astro-middleware-static.d.ts +3 -2
- package/dist/ssr/astro-middleware-static.js +0 -11
- package/dist/ssr/astro-middleware-static.js.map +1 -1
- package/dist/ssr/astro-middleware.d.ts +3 -2
- package/dist/ssr/astro-middleware.js +0 -12
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.d.ts +40 -6
- package/dist/ssr/astro.js +157 -93
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/index.d.ts +10 -11
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +64 -0
- package/dist/ssr/next-config.js +128 -0
- package/dist/ssr/next-config.js.map +1 -0
- package/dist/ssr/next.d.ts +9 -8
- package/dist/ssr/next.js +29 -43
- 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-LC3O3Jj4.d.ts → types-JEY8PVYP.d.ts} +862 -811
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +41 -107
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +6 -7
- package/dist/zero/index.js +1 -1
- package/dist/zero/next.d.ts +3 -5
- package/dist/zero/next.js +0 -31
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +13 -8
- package/docs/adoption.md +56 -31
- package/docs/ai-agents.md +107 -80
- package/docs/comparison.md +69 -40
- package/docs/configuration.md +179 -114
- package/docs/debug.md +19 -7
- package/docs/design-system.md +41 -22
- package/docs/dsl.md +310 -201
- package/docs/getting-started.md +64 -41
- package/docs/injector.md +63 -26
- package/docs/methodology.md +44 -38
- package/docs/migration-v3.md +49 -49
- package/docs/pipeline.md +32 -8
- package/docs/plugins.md +38 -34
- package/docs/react-api.md +67 -44
- package/docs/runtime-benchmarks.md +388 -0
- package/docs/ssr.md +219 -61
- package/docs/styles.md +95 -18
- package/docs/tasty-static.md +138 -105
- package/package.json +104 -33
- package/dist/async-storage-DKK-wTD4.js +0 -44
- package/dist/async-storage-DKK-wTD4.js.map +0 -1
- package/dist/collector-BMT05AmU.js.map +0 -1
- package/dist/config-DF93rL3u.js +0 -12661
- package/dist/config-DF93rL3u.js.map +0 -1
- package/dist/context-CA8YKeMn.js +0 -24
- package/dist/context-CA8YKeMn.js.map +0 -1
- package/dist/core-B5ybnMP1.js +0 -1529
- package/dist/core-B5ybnMP1.js.map +0 -1
- package/dist/css-writer-b02uBqNF.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-Cs4e3M9m.js +0 -130
- package/dist/format-rules-Cs4e3M9m.js.map +0 -1
- package/dist/hydrate-aaUSNJZJ.js +0 -37
- package/dist/hydrate-aaUSNJZJ.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keyframes-BedEzEXk.js +0 -424
- package/dist/keyframes-BedEzEXk.js.map +0 -1
- package/dist/merge-styles-CkAcnd0Z.js.map +0 -1
- package/dist/resolve-recipes-CiyLtJG9.js +0 -144
- package/dist/resolve-recipes-CiyLtJG9.js.map +0 -1
package/docs/dsl.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Style DSL Reference
|
|
2
2
|
|
|
3
|
-
This is the Tasty
|
|
3
|
+
This is the language behind Tasty’s predictable state resolution. Every property can use a state map; later branches declare higher priority, and Tasty compiles them so only one generated selector can match.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The same value syntax, state maps, tokens, units, extension semantics, and special declarations apply to React `tasty()`—whether rendered on the client or server—and build-time `tastyStatic()`.
|
|
6
|
+
|
|
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).
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -18,17 +20,28 @@ fill: { '': '#white', hovered: '#gray.05', 'theme=danger': '#red' }
|
|
|
18
20
|
|
|
19
21
|
#### State Key Types
|
|
20
22
|
|
|
21
|
-
| Syntax
|
|
22
|
-
|
|
23
|
-
| Boolean modifier
|
|
24
|
-
| Value modifier
|
|
25
|
-
| Pseudo-class
|
|
26
|
-
| Class selector
|
|
27
|
-
| Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]`
|
|
28
|
-
| Combined (AND)
|
|
29
|
-
| Combined (OR)
|
|
30
|
-
| Negated (NOT)
|
|
31
|
-
| Exclusive (XOR)
|
|
23
|
+
| Syntax | Example | Generated CSS |
|
|
24
|
+
| ------------------ | ------------------------ | ------------------------------------------------------------------------ |
|
|
25
|
+
| Boolean modifier | `hovered` | `[data-hovered]` |
|
|
26
|
+
| Value modifier | `theme=danger` | `[data-theme="danger"]` |
|
|
27
|
+
| Pseudo-class | `:hover` | `:hover` |
|
|
28
|
+
| Class selector | `.active` | `.active` |
|
|
29
|
+
| Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]` |
|
|
30
|
+
| Combined (AND) | `hovered & .active` | `[data-hovered].active` |
|
|
31
|
+
| Combined (OR) | `hovered \| focused` | `[data-hovered], [data-focused]` |
|
|
32
|
+
| Negated (NOT) | `!disabled` | `:not([data-disabled])` |
|
|
33
|
+
| Exclusive (XOR) | `hovered ^ focused` | `[data-hovered]:not([data-focused]), :not([data-hovered])[data-focused]` |
|
|
34
|
+
|
|
35
|
+
#### Automatic Native States
|
|
36
|
+
|
|
37
|
+
`disabled` and `checked` are built-in automatic states. On a Tasty component, they track the corresponding native prop or attribute, so these pairs express the same component state:
|
|
38
|
+
|
|
39
|
+
| Concise Tasty state | Explicit attribute selector |
|
|
40
|
+
| ------------------- | --------------------------- |
|
|
41
|
+
| `disabled` | `[disabled]` |
|
|
42
|
+
| `checked` | `[checked]` |
|
|
43
|
+
|
|
44
|
+
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.
|
|
32
45
|
|
|
33
46
|
Operator precedence (highest to lowest): `!` (NOT) > `^` (XOR) > `|` (OR) > `&` (AND). Use parentheses to override: `hovered & (pressed ^ focused)`.
|
|
34
47
|
|
|
@@ -41,6 +54,8 @@ fill: {
|
|
|
41
54
|
}
|
|
42
55
|
```
|
|
43
56
|
|
|
57
|
+
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.
|
|
58
|
+
|
|
44
59
|
#### Default State Ordering
|
|
45
60
|
|
|
46
61
|
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.
|
|
@@ -53,14 +68,18 @@ color: { '': '#text', hovered: '#accent' }
|
|
|
53
68
|
color: { hovered: '#accent', '': '#text' }
|
|
54
69
|
```
|
|
55
70
|
|
|
56
|
-
The bare `''` default still **receives negation** (it is turned off when a higher-priority state matches). For a value that must always apply as a guaranteed floor — even where a query is
|
|
71
|
+
The bare `''` default still **receives negation** (it is turned off when a higher-priority state matches). For a value that must always apply as a guaranteed floor — even where a query is _unknown_ — use the [`_` fallback floor](#_--fallback-floor) instead. The two can coexist: `''` is the negated default, `_` is the always-on floor. If a map contains only `_` and `''` (no other states), the `''` default is redundant — Tasty keeps the `_` value and drops `''` with a `REDUNDANT_DEFAULT_STATE` warning.
|
|
57
72
|
|
|
58
73
|
### Sub-element
|
|
59
74
|
|
|
60
75
|
Element styled using a capitalized key. Identified by `data-element` attribute:
|
|
61
76
|
|
|
62
77
|
```jsx
|
|
63
|
-
styles: {
|
|
78
|
+
styles: {
|
|
79
|
+
Title: {
|
|
80
|
+
preset: 'h3';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
64
83
|
// Targets: <div data-element="Title">
|
|
65
84
|
```
|
|
66
85
|
|
|
@@ -70,19 +89,19 @@ Control how a sub-element selector attaches to the root selector using the `$` p
|
|
|
70
89
|
|
|
71
90
|
Examples below assume the sub-element key is **Cell** (i.e. `[data-element="Cell"]` in CSS):
|
|
72
91
|
|
|
73
|
-
| Pattern
|
|
74
|
-
|
|
75
|
-
|
|
|
76
|
-
| `>`
|
|
77
|
-
| `>Body>`
|
|
78
|
-
| `> Cell`
|
|
79
|
-
| `h1`
|
|
80
|
-
| `h1 >`
|
|
81
|
-
| `h1 *`
|
|
82
|
-
| `*`
|
|
83
|
-
| `&::before` | `::before`
|
|
84
|
-
| `@::before` | `[data-element="Cell"]::before`
|
|
85
|
-
| `>@.active` | `> [data-element="Cell"].active`
|
|
92
|
+
| Pattern | Result | Description |
|
|
93
|
+
| ----------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
94
|
+
| _(none)_ | `[data-element="Cell"]` | Descendant (default) |
|
|
95
|
+
| `>` | `> [data-element="Cell"]` | Direct child |
|
|
96
|
+
| `>Body>` | `> [data-element="Body"] > [data-element="Cell"]` | Chained elements |
|
|
97
|
+
| `> Cell` | `> [data-element="Cell"]` | Self-name shorthand — when the trailing element name matches the sub-element's own key, it acts as the placeholder (same as `@`); no duplication |
|
|
98
|
+
| `h1` | `h1` | Tag selector (no key injection) |
|
|
99
|
+
| `h1 >` | `h1 > [data-element="Cell"]` | Key is direct child of tag |
|
|
100
|
+
| `h1 *` | `h1 *` | Any descendant of tag |
|
|
101
|
+
| `*` | `*` | All descendants |
|
|
102
|
+
| `&::before` | `::before` | Root pseudo (no key); `&` is required |
|
|
103
|
+
| `@::before` | `[data-element="Cell"]::before` | Pseudo on the sub-element |
|
|
104
|
+
| `>@.active` | `> [data-element="Cell"].active` | Class on the sub-element |
|
|
86
105
|
|
|
87
106
|
Rules for key injection (`[data-element="..."]`):
|
|
88
107
|
|
|
@@ -111,7 +130,7 @@ const List = tasty({
|
|
|
111
130
|
Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
|
|
112
131
|
|
|
113
132
|
```jsx
|
|
114
|
-
fill: '#purple.5'
|
|
133
|
+
fill: '#purple.5'; // → oklch(from var(--purple-color) l c h / .5)
|
|
115
134
|
```
|
|
116
135
|
|
|
117
136
|
### Modifier
|
|
@@ -146,12 +165,12 @@ fill: '#purple.5';
|
|
|
146
165
|
```
|
|
147
166
|
|
|
148
167
|
The channels are copied over and the alpha slot is written, which asks nothing of
|
|
149
|
-
the color beyond
|
|
168
|
+
the color beyond _being_ a color. That means the suffix works on every one of
|
|
150
169
|
these:
|
|
151
170
|
|
|
152
171
|
- a token holding a `color-mix()`, a `light-dark()`, or a `color()` in a space
|
|
153
172
|
Tasty cannot convert — none of which have channels to decompose
|
|
154
|
-
- `#current`, which resolves to
|
|
173
|
+
- `#current`, which resolves to the color the element inherits
|
|
155
174
|
- a `--name-color` variable declared in your own CSS, with no Tasty token
|
|
156
175
|
definition behind it
|
|
157
176
|
|
|
@@ -163,9 +182,44 @@ Two properties follow from writing the alpha slot rather than compositing:
|
|
|
163
182
|
`/ var(--fade)` unchanged, so it works whether `$fade` holds `.5` or `50%` —
|
|
164
183
|
which is what `--*-opacity` properties are registered to accept.
|
|
165
184
|
|
|
185
|
+
### `#current` is the keyword
|
|
186
|
+
|
|
187
|
+
`#current` emits the `currentcolor` keyword, so it resolves against the element
|
|
188
|
+
that reads it. That is what lets it follow a color an ancestor faded — a ramp
|
|
189
|
+
that expresses its disabled state once, in `color`, fades everything painted from
|
|
190
|
+
`#current` below it with no further declarations.
|
|
191
|
+
|
|
192
|
+
The `color` style also publishes `--current-color` beside every color it sets,
|
|
193
|
+
for anything that needs the inherited color _as a color_ rather than as the
|
|
194
|
+
keyword — hand-authored CSS, or a place the keyword will not do. Read it as
|
|
195
|
+
`$current-color`. It is registered with `initial-value: currentcolor`, so where
|
|
196
|
+
nothing published it, it still resolves against each element's own color:
|
|
197
|
+
|
|
198
|
+
```jsx
|
|
199
|
+
fill: '$current-color'; // → var(--current-color)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
A color that already reads the color it inherits — `#current`, a `#current` fade
|
|
203
|
+
— is not published into it: resolving such a value a second time at a descendant
|
|
204
|
+
would fade it twice.
|
|
205
|
+
|
|
206
|
+
One case needs the wider floor to be considered. A token _defined_ as `#current`
|
|
207
|
+
and then faded gives relative color syntax a `currentcolor` origin, which Safari
|
|
208
|
+
supports only from 18:
|
|
209
|
+
|
|
210
|
+
```jsx
|
|
211
|
+
{ '#ink': '#current', fill: '#ink.5' }
|
|
212
|
+
// --ink-color: currentcolor
|
|
213
|
+
// fill: oklch(from currentcolor l c h / .5) → Chrome, Firefox, Safari 18+
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Put the fade in the token instead — `{ '#ink': '#current.5' }` — and it goes
|
|
217
|
+
through `color-mix()`, which works from Safari 16.2 and composes like every other
|
|
218
|
+
`#current` fade.
|
|
219
|
+
|
|
166
220
|
### `#current` composes instead
|
|
167
221
|
|
|
168
|
-
`#current` is the one exception, and the difference is deliberate. A token
|
|
222
|
+
`#current` is the one exception, and the difference is deliberate. A token _names_
|
|
169
223
|
a color, so fading it sets its alpha. `currentcolor` is the color an element
|
|
170
224
|
**inherits**, which an ancestor may already have faded — `#current.4` means "40%
|
|
171
225
|
of what reaches me":
|
|
@@ -182,9 +236,9 @@ step. Because a `color-mix()` percentage cannot be a `<number>`, an opacity
|
|
|
182
236
|
custom property used as `#current.$fade` must hold a unitless number; a token
|
|
183
237
|
accepts either form.
|
|
184
238
|
|
|
185
|
-
The space is always `oklch
|
|
186
|
-
|
|
187
|
-
|
|
239
|
+
The space is always `oklch`: it is unbounded, so a wide-gamut color survives a
|
|
240
|
+
round trip that a gamut-limited space would clamp. Nothing configures this —
|
|
241
|
+
see [Color space](configuration.md#color-space).
|
|
188
242
|
|
|
189
243
|
---
|
|
190
244
|
|
|
@@ -231,22 +285,22 @@ configure({
|
|
|
231
285
|
},
|
|
232
286
|
});
|
|
233
287
|
|
|
234
|
-
fill: '#solid.5';
|
|
235
|
-
fill: '#adaptive.5';
|
|
288
|
+
fill: '#solid.5'; // → hsl(220 90% 50% / .5)
|
|
289
|
+
fill: '#adaptive.5'; // → oklch(from light-dark(…) l c h / .5)
|
|
236
290
|
```
|
|
237
291
|
|
|
238
292
|
---
|
|
239
293
|
|
|
240
294
|
## Built-in Units
|
|
241
295
|
|
|
242
|
-
| Unit | Description
|
|
243
|
-
|
|
244
|
-
| `x`
|
|
245
|
-
| `r`
|
|
246
|
-
| `cr` | Card border radius | `1cr`
|
|
247
|
-
| `bw` | Border width
|
|
248
|
-
| `ow` | Outline width
|
|
249
|
-
| `sf` | Stable fraction
|
|
296
|
+
| Unit | Description | Example | CSS Output |
|
|
297
|
+
| ---- | ------------------ | ------- | ------------------------------- |
|
|
298
|
+
| `x` | Gap multiplier | `2x` | `calc(var(--gap) * 2)` |
|
|
299
|
+
| `r` | Border radius | `1r` | `var(--radius)` |
|
|
300
|
+
| `cr` | Card border radius | `1cr` | `var(--card-radius)` |
|
|
301
|
+
| `bw` | Border width | `2bw` | `calc(var(--border-width) * 2)` |
|
|
302
|
+
| `ow` | Outline width | `1ow` | `var(--outline-width)` |
|
|
303
|
+
| `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)` |
|
|
250
304
|
|
|
251
305
|
You can register additional custom units via [`configure()`](configuration.md#options).
|
|
252
306
|
|
|
@@ -289,9 +343,29 @@ const ElevatedCard = tasty({
|
|
|
289
343
|
});
|
|
290
344
|
```
|
|
291
345
|
|
|
346
|
+
**Where recipes resolve:**
|
|
347
|
+
|
|
348
|
+
The `recipe` property is resolved on the component root and independently inside each sub-element's styles:
|
|
349
|
+
|
|
350
|
+
```jsx
|
|
351
|
+
const CardWithPanel = tasty({
|
|
352
|
+
styles: {
|
|
353
|
+
recipe: 'card',
|
|
354
|
+
Panel: {
|
|
355
|
+
recipe: 'card elevated',
|
|
356
|
+
padding: '2x', // Overrides the panel's base recipe padding
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
At each level, base recipes are collected in name order (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
|
+
|
|
364
|
+
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
|
+
|
|
292
366
|
**Post-merge recipes (`/` separator):**
|
|
293
367
|
|
|
294
|
-
Recipes listed after `/` are applied
|
|
368
|
+
Recipes listed after `/` are applied _after_ component styles using `mergeStyles`:
|
|
295
369
|
|
|
296
370
|
```jsx
|
|
297
371
|
const Input = tasty({
|
|
@@ -329,8 +403,8 @@ When a style property uses a state map, the merge behavior depends on whether th
|
|
|
329
403
|
const MyButton = tasty(Button, {
|
|
330
404
|
styles: {
|
|
331
405
|
fill: {
|
|
332
|
-
|
|
333
|
-
|
|
406
|
+
loading: '#yellow', // append new state
|
|
407
|
+
disabled: '#gray.20', // override existing state in place
|
|
334
408
|
},
|
|
335
409
|
},
|
|
336
410
|
});
|
|
@@ -340,7 +414,7 @@ const MyButton = tasty(Button, {
|
|
|
340
414
|
styles: {
|
|
341
415
|
fill: {
|
|
342
416
|
'': '#red',
|
|
343
|
-
|
|
417
|
+
hovered: '#blue',
|
|
344
418
|
},
|
|
345
419
|
},
|
|
346
420
|
});
|
|
@@ -365,8 +439,12 @@ fill: {
|
|
|
365
439
|
Use `null` inside a state map to remove a state, or `false` to block it entirely (tombstone):
|
|
366
440
|
|
|
367
441
|
```jsx
|
|
368
|
-
fill: {
|
|
369
|
-
|
|
442
|
+
fill: {
|
|
443
|
+
pressed: null;
|
|
444
|
+
} // removes pressed from the result
|
|
445
|
+
fill: {
|
|
446
|
+
disabled: false;
|
|
447
|
+
} // tombstone — no CSS for disabled, blocks recipe too
|
|
370
448
|
```
|
|
371
449
|
|
|
372
450
|
### Resetting Properties with `null` and `false`
|
|
@@ -374,36 +452,36 @@ fill: { disabled: false } // tombstone — no CSS for disabled, blocks recipe to
|
|
|
374
452
|
```jsx
|
|
375
453
|
const SimpleButton = tasty(Button, {
|
|
376
454
|
styles: {
|
|
377
|
-
fill: null,
|
|
455
|
+
fill: null, // discard parent's fill, let recipe fill in
|
|
378
456
|
border: false, // no border at all (tombstone — blocks recipe too)
|
|
379
457
|
},
|
|
380
458
|
});
|
|
381
459
|
```
|
|
382
460
|
|
|
383
|
-
| Value
|
|
384
|
-
|
|
385
|
-
| `undefined` | Not provided — parent preserved
|
|
386
|
-
| `null`
|
|
387
|
-
| `false`
|
|
461
|
+
| Value | Meaning | Recipe fills in? |
|
|
462
|
+
| ----------- | ------------------------------------ | ---------------- |
|
|
463
|
+
| `undefined` | Not provided — parent preserved | N/A |
|
|
464
|
+
| `null` | Intentional unset — parent discarded | Yes |
|
|
465
|
+
| `false` | Tombstone — blocks everything | No |
|
|
388
466
|
|
|
389
467
|
---
|
|
390
468
|
|
|
391
469
|
## Advanced States (`@` prefix)
|
|
392
470
|
|
|
393
|
-
| Prefix
|
|
394
|
-
|
|
395
|
-
| `@media`
|
|
396
|
-
| `@(...)`
|
|
397
|
-
| `@supports` | Feature/selector support
|
|
398
|
-
| `@root`
|
|
399
|
-
| `@parent`
|
|
400
|
-
| `@own`
|
|
401
|
-
| `@starting` | Entry animation
|
|
402
|
-
| `_`
|
|
403
|
-
| `:is()`
|
|
404
|
-
| `:has()`
|
|
405
|
-
| `:not()`
|
|
406
|
-
| `:where()`
|
|
471
|
+
| Prefix | Purpose | Example |
|
|
472
|
+
| ----------- | ----------------------------------------- | -------------------------- |
|
|
473
|
+
| `@media` | Media queries | `@media(w < 768px)` |
|
|
474
|
+
| `@(...)` | Container queries | `@(panel, w >= 300px)` |
|
|
475
|
+
| `@supports` | Feature/selector support | `@supports(display: grid)` |
|
|
476
|
+
| `@root` | Root element states | `@root(schema=dark)` |
|
|
477
|
+
| `@parent` | Parent/ancestor element states | `@parent(hovered)` |
|
|
478
|
+
| `@own` | Sub-element's own state | `@own(hovered)` |
|
|
479
|
+
| `@starting` | Entry animation | `@starting` |
|
|
480
|
+
| `_` | Fallback floor (always-on, never negated) | `_` |
|
|
481
|
+
| `:is()` | CSS `:is()` structural pseudo-class | `:is(fieldset > label)` |
|
|
482
|
+
| `:has()` | CSS `:has()` relational pseudo-class | `:has(> Icon)` |
|
|
483
|
+
| `:not()` | CSS `:not()` negation (prefer `!:is()`) | `:not(:first-child)` |
|
|
484
|
+
| `:where()` | CSS `:where()` (zero specificity) | `:where(Section)` |
|
|
407
485
|
|
|
408
486
|
> **Specificity.** All state selectors Tasty generates (modifiers, pseudo-classes,
|
|
409
487
|
> `:is()`/`:not()` groups, and `@root` / `@parent` context) are wrapped in
|
|
@@ -419,9 +497,9 @@ const SimpleButton = tasty(Button, {
|
|
|
419
497
|
Media queries support dimension shorthands and custom unit expansion:
|
|
420
498
|
|
|
421
499
|
| Shorthand | Expands to |
|
|
422
|
-
|
|
423
|
-
| `w`
|
|
424
|
-
| `h`
|
|
500
|
+
| --------- | ---------- |
|
|
501
|
+
| `w` | `width` |
|
|
502
|
+
| `h` | `height` |
|
|
425
503
|
|
|
426
504
|
```jsx
|
|
427
505
|
fill: {
|
|
@@ -432,14 +510,14 @@ fill: {
|
|
|
432
510
|
}
|
|
433
511
|
```
|
|
434
512
|
|
|
435
|
-
| Tasty syntax
|
|
436
|
-
|
|
437
|
-
| `@media(w < 768px)`
|
|
438
|
-
| `@media(600px <= w < 1200px)`
|
|
439
|
-
| `@media:print`
|
|
440
|
-
| `@media:screen`
|
|
513
|
+
| Tasty syntax | CSS output |
|
|
514
|
+
| ------------------------------------ | ------------------------------------- |
|
|
515
|
+
| `@media(w < 768px)` | `@media (width < 768px)` |
|
|
516
|
+
| `@media(600px <= w < 1200px)` | `@media (600px <= width < 1200px)` |
|
|
517
|
+
| `@media:print` | `@media print` |
|
|
518
|
+
| `@media:screen` | `@media screen` |
|
|
441
519
|
| `@media(prefers-color-scheme: dark)` | `@media (prefers-color-scheme: dark)` |
|
|
442
|
-
| `@media(prefers-reduced-motion)`
|
|
520
|
+
| `@media(prefers-reduced-motion)` | `@media (prefers-reduced-motion)` |
|
|
443
521
|
|
|
444
522
|
Custom units work inside media queries: `@media(w < 40x)` → `@media (width < calc(var(--gap) * 40))`.
|
|
445
523
|
|
|
@@ -449,12 +527,12 @@ In practice, define state aliases via `configure({ states })` and use `@mobile`
|
|
|
449
527
|
|
|
450
528
|
Container queries use the syntax `@(name, condition)` for named containers or `@(condition)` for the nearest ancestor container. Dimension shorthands (`w`, `h`, `is`, `bs`) are expanded the same way as `@media`.
|
|
451
529
|
|
|
452
|
-
| Shorthand | Expands to
|
|
453
|
-
|
|
454
|
-
| `w`
|
|
455
|
-
| `h`
|
|
456
|
-
| `is`
|
|
457
|
-
| `bs`
|
|
530
|
+
| Shorthand | Expands to |
|
|
531
|
+
| --------- | ------------- |
|
|
532
|
+
| `w` | `width` |
|
|
533
|
+
| `h` | `height` |
|
|
534
|
+
| `is` | `inline-size` |
|
|
535
|
+
| `bs` | `block-size` |
|
|
458
536
|
|
|
459
537
|
```jsx
|
|
460
538
|
const Panel = tasty({
|
|
@@ -467,26 +545,29 @@ const Panel = tasty({
|
|
|
467
545
|
});
|
|
468
546
|
```
|
|
469
547
|
|
|
470
|
-
| Tasty syntax
|
|
471
|
-
|
|
472
|
-
| `@(layout, w < 600px)`
|
|
473
|
-
| `@(w < 600px)`
|
|
474
|
-
| `@(layout, $variant=danger)`
|
|
475
|
-
| `@(layout, $compact)`
|
|
476
|
-
| `@(scroll-state(stuck: top))`
|
|
477
|
-
| `@(nav, scroll-state(stuck: top))` | `@container nav scroll-state(stuck: top)`
|
|
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: "danger")` |
|
|
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)` |
|
|
478
556
|
|
|
479
557
|
Container style queries use `$prop` (boolean) or `$prop=value` syntax, which maps to CSS `style(--prop)` or `style(--prop: "value")`.
|
|
480
558
|
|
|
481
559
|
### `@supports(...)` — Feature Queries
|
|
482
560
|
|
|
483
|
-
Feature queries test CSS property support
|
|
561
|
+
Feature queries test CSS property support and accept CSS support functions such
|
|
562
|
+
as `at-rule()`. Use `$` as the first argument to test selector support:
|
|
484
563
|
|
|
485
|
-
| Tasty syntax
|
|
486
|
-
|
|
487
|
-
| `@supports(display: grid)`
|
|
488
|
-
| `@supports($, :has(*))`
|
|
489
|
-
|
|
|
564
|
+
| Tasty syntax | CSS output |
|
|
565
|
+
| ---------------------------- | ---------------------------------- |
|
|
566
|
+
| `@supports(display: grid)` | `@supports (display: grid)` |
|
|
567
|
+
| `@supports($, :has(*))` | `@supports selector(:has(*))` |
|
|
568
|
+
| `@supports(at-rule(@scope))` | `@supports (at-rule(@scope))` |
|
|
569
|
+
| `!@supports(display: grid)` | `@supports (not (display: grid))` |
|
|
570
|
+
| `!@supports(at-rule(@scope))` | `@supports (not (at-rule(@scope)))` |
|
|
490
571
|
|
|
491
572
|
```jsx
|
|
492
573
|
display: {
|
|
@@ -495,6 +576,25 @@ display: {
|
|
|
495
576
|
}
|
|
496
577
|
```
|
|
497
578
|
|
|
579
|
+
`at-rule()` tests whether the browser supports the named at-rule. Its surrounding
|
|
580
|
+
parentheses in Tasty's CSS output are valid CSS. Negation and combinations with
|
|
581
|
+
other states use the same syntax as property queries:
|
|
582
|
+
|
|
583
|
+
```jsx
|
|
584
|
+
color: {
|
|
585
|
+
_: 'red',
|
|
586
|
+
'@supports(at-rule(@scope)) & @supports(display: grid)': 'blue',
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
Use `_` to keep a base value when a browser cannot evaluate a query. A support
|
|
591
|
+
query only detects a browser feature; it does not add an API for authoring the
|
|
592
|
+
queried at-rule in a Tasty style object. For conditional stylesheet imports such
|
|
593
|
+
as `@import url("scoped.css") supports(at-rule(@scope));`, use an ordinary CSS
|
|
594
|
+
stylesheet or document-level `useRawCSS()`. Put imports before other rules in
|
|
595
|
+
that stylesheet. Imports cannot be used in constructable stylesheets, including
|
|
596
|
+
Tasty's adopted-sheet mode for Shadow DOM.
|
|
597
|
+
|
|
498
598
|
### `_` — Fallback Floor
|
|
499
599
|
|
|
500
600
|
By default Tasty makes states **mutually exclusive**: a higher-priority state
|
|
@@ -504,7 +604,7 @@ that assumption: `@supports(...)` and `@(...)` queries can be **unknown** (not
|
|
|
504
604
|
just true/false), and `not(unknown)` is also unknown — so a negated default
|
|
505
605
|
branch silently never applies. The classic case is `scroll-state`: a browser can
|
|
506
606
|
support `container-type: scroll-state` while a specific `scroll-state(...)` query
|
|
507
|
-
is unknown, leaving
|
|
607
|
+
is unknown, leaving _no_ branch active.
|
|
508
608
|
|
|
509
609
|
The `_` fallback floor solves this. Use `_` as a **standalone key** and its value
|
|
510
610
|
**always applies** as a guaranteed floor: it never receives negation, and
|
|
@@ -519,10 +619,14 @@ inset: {
|
|
|
519
619
|
```
|
|
520
620
|
|
|
521
621
|
```css
|
|
522
|
-
.t0.t0 {
|
|
622
|
+
.t0.t0 {
|
|
623
|
+
inset: 0...;
|
|
624
|
+
}
|
|
523
625
|
@container scroll-state(scrolled: block-end) {
|
|
524
626
|
@supports (container-type: scroll-state) {
|
|
525
|
-
.t0.t0 {
|
|
627
|
+
.t0.t0 {
|
|
628
|
+
inset: -80px...;
|
|
629
|
+
}
|
|
526
630
|
}
|
|
527
631
|
}
|
|
528
632
|
```
|
|
@@ -553,13 +657,13 @@ cause is always the same: **`''` is mutually exclusive (turned off by negation),
|
|
|
553
657
|
while `_` is always on (layered underneath).**
|
|
554
658
|
|
|
555
659
|
- **Unknown / invalid branches.** If a higher-priority branch sits behind a query
|
|
556
|
-
that can be
|
|
660
|
+
that can be _unknown_ (`@supports`, container, `scroll-state`) or uses a
|
|
557
661
|
selector the browser drops, the negated `''` default disappears along with it
|
|
558
662
|
(`not(unknown) = unknown`), leaving the property with no rule. The `_` floor
|
|
559
663
|
survives because it is an unconditional bare rule. This is the case `_` exists
|
|
560
664
|
for.
|
|
561
665
|
|
|
562
|
-
- **Empty / "unset" states.** A state can intentionally produce
|
|
666
|
+
- **Empty / "unset" states.** A state can intentionally produce _no_ output (an
|
|
563
667
|
empty value) to leave a property unset while that state is active. With `''`
|
|
564
668
|
this works — the default is negated away and nothing replaces it, so the
|
|
565
669
|
property unsets. With `_` the floor can never be turned off, so its value keeps
|
|
@@ -571,14 +675,14 @@ while `_` is always on (layered underneath).**
|
|
|
571
675
|
```
|
|
572
676
|
|
|
573
677
|
- **States with different output shapes.** Because `_` layers additively, when
|
|
574
|
-
different states emit
|
|
678
|
+
different states emit _different sets_ of declarations, the floor's
|
|
575
679
|
declarations bleed through wherever the winning state does not override the
|
|
576
680
|
same property — which can produce inconsistent combinations. A `''` default is
|
|
577
681
|
swapped out cleanly by its mutually-exclusive condition.
|
|
578
682
|
|
|
579
683
|
Rule of thumb: **use `''` for the normal default** (clean mutual exclusivity,
|
|
580
684
|
supports unsetting), and reach for `_` only when a value must survive an
|
|
581
|
-
|
|
685
|
+
_unknown_ higher-priority branch.
|
|
582
686
|
|
|
583
687
|
### `@root(...)` — Root Element States
|
|
584
688
|
|
|
@@ -594,13 +698,13 @@ color: {
|
|
|
594
698
|
}
|
|
595
699
|
```
|
|
596
700
|
|
|
597
|
-
| Tasty syntax
|
|
598
|
-
|
|
599
|
-
| `@root(schema=dark)`
|
|
600
|
-
| `@root(hovered)`
|
|
601
|
-
| `@root(.premium-user)` | `:root.premium-user`
|
|
602
|
-
| `@root([lang="en"])`
|
|
603
|
-
| `!@root(schema=dark)`
|
|
701
|
+
| Tasty syntax | CSS selector |
|
|
702
|
+
| ---------------------- | --------------------------------- |
|
|
703
|
+
| `@root(schema=dark)` | `:root[data-schema="dark"]` |
|
|
704
|
+
| `@root(hovered)` | `:root[data-hovered]` |
|
|
705
|
+
| `@root(.premium-user)` | `:root.premium-user` |
|
|
706
|
+
| `@root([lang="en"])` | `:root[lang="en"]` |
|
|
707
|
+
| `!@root(schema=dark)` | `:root:not([data-schema="dark"])` |
|
|
604
708
|
|
|
605
709
|
Root conditions are prepended to the component selector: `:root[data-schema="dark"] .t0.t0 { ... }`.
|
|
606
710
|
|
|
@@ -616,7 +720,7 @@ const Nav = tasty({
|
|
|
616
720
|
'': '#text',
|
|
617
721
|
'@own(:hover)': '#primary',
|
|
618
722
|
'@own(:focus-visible)': '#primary',
|
|
619
|
-
|
|
723
|
+
selected: '#primary', // root-level modifier
|
|
620
724
|
},
|
|
621
725
|
},
|
|
622
726
|
},
|
|
@@ -624,11 +728,11 @@ const Nav = tasty({
|
|
|
624
728
|
});
|
|
625
729
|
```
|
|
626
730
|
|
|
627
|
-
| Tasty syntax (inside sub-element) | CSS output
|
|
628
|
-
|
|
629
|
-
| `@own(:hover)`
|
|
630
|
-
| `@own(hovered)`
|
|
631
|
-
| `@own(theme=dark)`
|
|
731
|
+
| Tasty syntax (inside sub-element) | CSS output |
|
|
732
|
+
| --------------------------------- | ------------------------------------------------- |
|
|
733
|
+
| `@own(:hover)` | `:hover` on the sub-element selector |
|
|
734
|
+
| `@own(hovered)` | `[data-hovered]` on the sub-element selector |
|
|
735
|
+
| `@own(theme=dark)` | `[data-theme="dark"]` on the sub-element selector |
|
|
632
736
|
|
|
633
737
|
`@own()` is only valid inside sub-element styles. Using it on root styles emits a warning and is treated as a regular modifier.
|
|
634
738
|
|
|
@@ -641,14 +745,14 @@ const FadeIn = tasty({
|
|
|
641
745
|
styles: {
|
|
642
746
|
opacity: { '': '1', '@starting': '0' },
|
|
643
747
|
transform: { '': 'scale(1)', '@starting': 'scale(0.95)' },
|
|
644
|
-
transition: 'opacity
|
|
748
|
+
transition: 'opacity, translate',
|
|
645
749
|
},
|
|
646
750
|
});
|
|
647
751
|
```
|
|
648
752
|
|
|
649
|
-
| Tasty syntax | CSS output
|
|
650
|
-
|
|
651
|
-
| `@starting`
|
|
753
|
+
| Tasty syntax | CSS output |
|
|
754
|
+
| ------------ | ------------------------------------ |
|
|
755
|
+
| `@starting` | `@starting-style { .t0.t0 { ... } }` |
|
|
652
756
|
|
|
653
757
|
### `@parent(...)` — Parent Element States
|
|
654
758
|
|
|
@@ -659,22 +763,22 @@ const Highlight = tasty({
|
|
|
659
763
|
styles: {
|
|
660
764
|
fill: {
|
|
661
765
|
'': '#white',
|
|
662
|
-
'@parent(hovered)': '#gray.05',
|
|
663
|
-
'@parent(theme=dark, >)': '#dark-02',
|
|
766
|
+
'@parent(hovered)': '#gray.05', // Any ancestor has [data-hovered]
|
|
767
|
+
'@parent(theme=dark, >)': '#dark-02', // Direct parent has [data-theme="dark"]
|
|
664
768
|
},
|
|
665
769
|
},
|
|
666
770
|
});
|
|
667
771
|
```
|
|
668
772
|
|
|
669
|
-
| Syntax
|
|
670
|
-
|
|
671
|
-
| `@parent(hovered)`
|
|
672
|
-
| `!@parent(hovered)`
|
|
673
|
-
| `@parent(hovered, >)`
|
|
674
|
-
| `@parent(.active)`
|
|
675
|
-
| `@parent(hovered & focused)`
|
|
773
|
+
| Syntax | CSS Output |
|
|
774
|
+
| ------------------------------------- | -------------------------------------------------------------------- |
|
|
775
|
+
| `@parent(hovered)` | `:is([data-hovered] *)` |
|
|
776
|
+
| `!@parent(hovered)` | `:not([data-hovered] *)` |
|
|
777
|
+
| `@parent(hovered, >)` | `:is([data-hovered] > *)` (direct parent) |
|
|
778
|
+
| `@parent(.active)` | `:is(.active *)` |
|
|
779
|
+
| `@parent(hovered & focused)` | `:is([data-hovered][data-focused] *)` (same ancestor) |
|
|
676
780
|
| `@parent(hovered) & @parent(focused)` | `:is([data-hovered] *):is([data-focused] *)` (independent ancestors) |
|
|
677
|
-
| `@parent(hovered \| focused)`
|
|
781
|
+
| `@parent(hovered \| focused)` | `:is([data-hovered] *, [data-focused] *)` (OR inside single wrapper) |
|
|
678
782
|
|
|
679
783
|
For sub-elements, the parent check applies to the root element's ancestors:
|
|
680
784
|
|
|
@@ -705,38 +809,38 @@ const Card = tasty({
|
|
|
705
809
|
styles: {
|
|
706
810
|
display: {
|
|
707
811
|
'': 'block',
|
|
708
|
-
':has(> Icon)': 'grid',
|
|
709
|
-
':has(+ Icon)': 'grid',
|
|
710
|
-
':has(~ Icon)': 'grid',
|
|
711
|
-
':has(Icon +)': 'grid',
|
|
712
|
-
':has(Icon ~)': 'grid',
|
|
713
|
-
':is(fieldset > label)': 'inline',
|
|
714
|
-
'!:has(> Icon)': 'flex',
|
|
812
|
+
':has(> Icon)': 'grid', // has Icon as direct child
|
|
813
|
+
':has(+ Icon)': 'grid', // immediately followed by an Icon sibling
|
|
814
|
+
':has(~ Icon)': 'grid', // has an Icon sibling somewhere after
|
|
815
|
+
':has(Icon +)': 'grid', // immediately preceded by an Icon sibling (auto-completes to `Icon + *`)
|
|
816
|
+
':has(Icon ~)': 'grid', // has an Icon sibling somewhere before (auto-completes to `Icon ~ *`)
|
|
817
|
+
':is(fieldset > label)': 'inline', // is a label inside a fieldset (HTML tags)
|
|
818
|
+
'!:has(> Icon)': 'flex', // negation: no Icon child
|
|
715
819
|
},
|
|
716
820
|
},
|
|
717
821
|
});
|
|
718
822
|
```
|
|
719
823
|
|
|
720
|
-
| Syntax
|
|
721
|
-
|
|
722
|
-
| `:has(> Icon)`
|
|
723
|
-
| `:has(+ Icon)`
|
|
724
|
-
| `:has(~ Icon)`
|
|
725
|
-
| `:has(Icon +)`
|
|
726
|
-
| `:has(Icon ~)`
|
|
727
|
-
| `:has(>)`
|
|
728
|
-
| `:is(> Field + input)` | `:is(> [data-element="Field"] + input)` | Structural match
|
|
729
|
-
| `:has(button)`
|
|
730
|
-
| `!:has(> Icon)`
|
|
731
|
-
| `!:is(Panel)`
|
|
824
|
+
| Syntax | CSS Output | Meaning |
|
|
825
|
+
| ---------------------- | --------------------------------------- | --------------------------------------- |
|
|
826
|
+
| `:has(> Icon)` | `:has(> [data-element="Icon"])` | Has Icon as direct child |
|
|
827
|
+
| `:has(+ Icon)` | `:has(+ [data-element="Icon"])` | Immediately followed by an Icon sibling |
|
|
828
|
+
| `:has(~ Icon)` | `:has(~ [data-element="Icon"])` | Has an Icon sibling somewhere after |
|
|
829
|
+
| `:has(Icon +)` | `:has([data-element="Icon"] + *)` | Immediately preceded by an Icon sibling |
|
|
830
|
+
| `:has(Icon ~)` | `:has([data-element="Icon"] ~ *)` | Has an Icon sibling somewhere before |
|
|
831
|
+
| `:has(>)` | `:has(> *)` | Has any direct child |
|
|
832
|
+
| `:is(> Field + input)` | `:is(> [data-element="Field"] + input)` | Structural match |
|
|
833
|
+
| `:has(button)` | `:has(button)` | HTML tag (lowercase, unchanged) |
|
|
834
|
+
| `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
|
|
835
|
+
| `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
|
|
732
836
|
|
|
733
837
|
Combine with other states using boolean logic (`&`, `|`, `!`, `^`):
|
|
734
838
|
|
|
735
839
|
```jsx
|
|
736
|
-
':has(> Icon) & hovered'
|
|
737
|
-
'@parent(hovered) & :has(> Icon)'
|
|
738
|
-
':has(> Icon) | :has(> Button)'
|
|
739
|
-
':has(> Icon) ^ :has(> Button)'
|
|
840
|
+
':has(> Icon) & hovered'; // AND: structural + data attribute
|
|
841
|
+
'@parent(hovered) & :has(> Icon)'; // AND: parent check + structural
|
|
842
|
+
':has(> Icon) | :has(> Button)'; // OR: either sub-element present
|
|
843
|
+
':has(> Icon) ^ :has(> Button)'; // XOR: exactly one present
|
|
740
844
|
```
|
|
741
845
|
|
|
742
846
|
> **Nesting limit:** The state key parser supports up to 2 levels of nested parentheses inside `:is()`, `:has()`, `:not()`, and `:where()` — e.g. `:has(Input:not(:disabled))` works, but 3+ levels like `:has(:is(:not(:hover)))` will not be tokenized correctly. This covers virtually all practical use cases.
|
|
@@ -772,8 +876,8 @@ const AnimatedGradient = tasty({
|
|
|
772
876
|
styles: {
|
|
773
877
|
'$gradient-angle': '0deg',
|
|
774
878
|
'#theme': 'okhst(280 80% 50%)',
|
|
775
|
-
|
|
776
|
-
transition: '$$gradient-angle
|
|
879
|
+
image: 'linear-gradient($gradient-angle, #theme, #clear)',
|
|
880
|
+
transition: '$$gradient-angle, ##theme',
|
|
777
881
|
},
|
|
778
882
|
});
|
|
779
883
|
```
|
|
@@ -803,7 +907,7 @@ const Heading = tasty({
|
|
|
803
907
|
fontDisplay: 'swap',
|
|
804
908
|
},
|
|
805
909
|
},
|
|
806
|
-
|
|
910
|
+
font: '"Brand Sans", sans-serif',
|
|
807
911
|
},
|
|
808
912
|
});
|
|
809
913
|
```
|
|
@@ -823,20 +927,20 @@ Supply an array to register several variants of the same family:
|
|
|
823
927
|
|
|
824
928
|
### Supported descriptors
|
|
825
929
|
|
|
826
|
-
| Descriptor
|
|
827
|
-
|
|
828
|
-
| `src` (required)
|
|
829
|
-
| `fontWeight`
|
|
830
|
-
| `fontStyle`
|
|
831
|
-
| `fontStretch`
|
|
832
|
-
| `fontDisplay`
|
|
833
|
-
| `unicodeRange`
|
|
834
|
-
| `ascentOverride`
|
|
835
|
-
| `descentOverride`
|
|
836
|
-
| `lineGapOverride`
|
|
837
|
-
| `sizeAdjust`
|
|
838
|
-
| `fontFeatureSettings`
|
|
839
|
-
| `fontVariationSettings` | `font-variation-settings` | `string`
|
|
930
|
+
| Descriptor | CSS property | Type |
|
|
931
|
+
| ----------------------- | ------------------------- | --------------------------------------------------------- |
|
|
932
|
+
| `src` (required) | `src` | `string` |
|
|
933
|
+
| `fontWeight` | `font-weight` | `string \| number` |
|
|
934
|
+
| `fontStyle` | `font-style` | `string` |
|
|
935
|
+
| `fontStretch` | `font-stretch` | `string` |
|
|
936
|
+
| `fontDisplay` | `font-display` | `'auto' \| 'block' \| 'swap' \| 'fallback' \| 'optional'` |
|
|
937
|
+
| `unicodeRange` | `unicode-range` | `string` |
|
|
938
|
+
| `ascentOverride` | `ascent-override` | `string` |
|
|
939
|
+
| `descentOverride` | `descent-override` | `string` |
|
|
940
|
+
| `lineGapOverride` | `line-gap-override` | `string` |
|
|
941
|
+
| `sizeAdjust` | `size-adjust` | `string` |
|
|
942
|
+
| `fontFeatureSettings` | `font-feature-settings` | `string` |
|
|
943
|
+
| `fontVariationSettings` | `font-variation-settings` | `string` |
|
|
840
944
|
|
|
841
945
|
> Font-face rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@font-face`.
|
|
842
946
|
|
|
@@ -864,18 +968,18 @@ const EmojiList = tasty({
|
|
|
864
968
|
|
|
865
969
|
### Supported descriptors
|
|
866
970
|
|
|
867
|
-
| Descriptor
|
|
868
|
-
|
|
869
|
-
| `system` (required) | `system`
|
|
870
|
-
| `symbols`
|
|
871
|
-
| `additiveSymbols`
|
|
872
|
-
| `prefix`
|
|
873
|
-
| `suffix`
|
|
874
|
-
| `negative`
|
|
875
|
-
| `range`
|
|
876
|
-
| `pad`
|
|
877
|
-
| `fallback`
|
|
878
|
-
| `speakAs`
|
|
971
|
+
| Descriptor | CSS property | Type |
|
|
972
|
+
| ------------------- | ------------------ | ---------------------------------------------------------------------------------------- |
|
|
973
|
+
| `system` (required) | `system` | `'cyclic' \| 'numeric' \| 'alphabetic' \| 'symbolic' \| 'additive' \| 'fixed' \| string` |
|
|
974
|
+
| `symbols` | `symbols` | `string` |
|
|
975
|
+
| `additiveSymbols` | `additive-symbols` | `string` |
|
|
976
|
+
| `prefix` | `prefix` | `string` |
|
|
977
|
+
| `suffix` | `suffix` | `string` |
|
|
978
|
+
| `negative` | `negative` | `string` |
|
|
979
|
+
| `range` | `range` | `string` |
|
|
980
|
+
| `pad` | `pad` | `string` |
|
|
981
|
+
| `fallback` | `fallback` | `string` |
|
|
982
|
+
| `speakAs` | `speak-as` | `string` |
|
|
879
983
|
|
|
880
984
|
> Counter-style rules are permanent — they are injected once and never cleaned up, matching how browsers handle `@counter-style`.
|
|
881
985
|
|
|
@@ -897,18 +1001,18 @@ const Box = tasty({
|
|
|
897
1001
|
styles: {
|
|
898
1002
|
'@function': {
|
|
899
1003
|
// simplest: one bare param + result (auto-calc, no explicit calc())
|
|
900
|
-
|
|
1004
|
+
$$negative: { args: ['$value'], result: '(-1 * $value)' },
|
|
901
1005
|
|
|
902
1006
|
// typed param + default + return type + local variable
|
|
903
|
-
|
|
1007
|
+
$$shadow: {
|
|
904
1008
|
args: { '$shadow-color': { syntax: '<color>', default: 'inherit' } },
|
|
905
1009
|
returns: '<color>',
|
|
906
|
-
|
|
1010
|
+
$offset: '2px', // local variable
|
|
907
1011
|
result: '$offset $offset ($shadow-color, black)',
|
|
908
1012
|
},
|
|
909
1013
|
},
|
|
910
|
-
|
|
911
|
-
|
|
1014
|
+
margin: '$$negative(10px) top', // → margin: --negative(10px) 0 0 0
|
|
1015
|
+
shadow: '$$shadow(#accent)', // → box-shadow: --shadow(var(--accent-color))
|
|
912
1016
|
},
|
|
913
1017
|
});
|
|
914
1018
|
```
|
|
@@ -916,22 +1020,27 @@ const Box = tasty({
|
|
|
916
1020
|
Generated CSS:
|
|
917
1021
|
|
|
918
1022
|
```css
|
|
919
|
-
@function --negative(--value) {
|
|
1023
|
+
@function --negative(--value) {
|
|
1024
|
+
result: calc(-1 * var(--value));
|
|
1025
|
+
}
|
|
920
1026
|
@function --shadow(--shadow-color <color>: inherit) returns <color> {
|
|
921
1027
|
--offset: 2px;
|
|
922
1028
|
result: var(--offset) var(--offset) var(--shadow-color, black);
|
|
923
1029
|
}
|
|
924
|
-
.t0 {
|
|
1030
|
+
.t0 {
|
|
1031
|
+
margin: --negative(10px) 0 0 0;
|
|
1032
|
+
box-shadow: --shadow(var(--accent-color));
|
|
1033
|
+
}
|
|
925
1034
|
```
|
|
926
1035
|
|
|
927
1036
|
### Descriptor shape
|
|
928
1037
|
|
|
929
|
-
| Field
|
|
930
|
-
|
|
931
|
-
| `result` (required) | `string`
|
|
932
|
-
| `args`
|
|
933
|
-
| `returns`
|
|
934
|
-
| `$name` keys
|
|
1038
|
+
| Field | Type | Notes |
|
|
1039
|
+
| ------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
1040
|
+
| `result` (required) | `string` | The `result:` value, parsed through the Tasty DSL. |
|
|
1041
|
+
| `args` | `string[]` \| `Record<string, FunctionParameter>` | Ordered parameters. Array form lists bare names; object form maps names to type/default. |
|
|
1042
|
+
| `returns` | `string` | Optional return type, e.g. `'<color>'`. |
|
|
1043
|
+
| `$name` keys | `string \| number` | Any `$name` key declares a local variable `--name` (value parsed). |
|
|
935
1044
|
|
|
936
1045
|
A `FunctionParameter` is one of: `true` (bare param), a string CSS type shorthand (`'<length>'`), or `{ syntax?: string; default?: string | number }`.
|
|
937
1046
|
|
|
@@ -953,4 +1062,4 @@ For a complete reference of all enhanced style properties — syntax, values, mo
|
|
|
953
1062
|
- **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
|
|
954
1063
|
- **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
|
|
955
1064
|
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
|
956
|
-
- **[
|
|
1065
|
+
- **[Build-Time Extraction (`tastyStatic`)](tasty-static.md)** — Static styling with the Babel plugin
|