@pitlane/theme 0.1.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.
@@ -0,0 +1,722 @@
1
+ import { ElementProps, Handle, MixinDescriptor, RemixElement, css as css$1 } from "remix/ui";
2
+ import * as CSS from "csstype";
3
+ //#region src/brands.d.ts
4
+ /**
5
+ * The twelve DTCG token types, in canonical order.
6
+ *
7
+ * @internal
8
+ */
9
+ declare const TOKEN_TYPES: readonly ["color", "dimension", "duration", "fontFamily", "fontWeight", "number", "cubicBezier", "shadow", "border", "transition", "gradient", "strokeStyle"];
10
+ /** The twelve DTCG token `$type` values. @see {@link AnyToken} */
11
+ type TokenType = (typeof TOKEN_TYPES)[number];
12
+ declare const COLOR: unique symbol;
13
+ declare const DIMENSION: unique symbol;
14
+ declare const DURATION: unique symbol;
15
+ declare const FONT_FAMILY: unique symbol;
16
+ declare const FONT_WEIGHT: unique symbol;
17
+ declare const NUMBER: unique symbol;
18
+ declare const CUBIC_BEZIER: unique symbol;
19
+ declare const SHADOW: unique symbol;
20
+ declare const BORDER: unique symbol;
21
+ declare const TRANSITION: unique symbol;
22
+ declare const GRADIENT: unique symbol;
23
+ declare const STROKE_STYLE: unique symbol;
24
+ /** Compile-time brand for a `color` token. */
25
+ type ColorToken = string & {
26
+ readonly [COLOR]: true;
27
+ };
28
+ /** Compile-time brand for a `dimension` token. */
29
+ type DimensionToken = string & {
30
+ readonly [DIMENSION]: true;
31
+ };
32
+ /** Compile-time brand for a `duration` token. */
33
+ type DurationToken = string & {
34
+ readonly [DURATION]: true;
35
+ };
36
+ /** Compile-time brand for a `fontFamily` token. */
37
+ type FontFamilyToken = string & {
38
+ readonly [FONT_FAMILY]: true;
39
+ };
40
+ /** Compile-time brand for a `fontWeight` token. */
41
+ type FontWeightToken = string & {
42
+ readonly [FONT_WEIGHT]: true;
43
+ };
44
+ /** Compile-time brand for a `number` token. */
45
+ type NumberToken = string & {
46
+ readonly [NUMBER]: true;
47
+ };
48
+ /** Compile-time brand for a `cubicBezier` token. */
49
+ type CubicBezierToken = string & {
50
+ readonly [CUBIC_BEZIER]: true;
51
+ };
52
+ /** Compile-time brand for a `shadow` token. */
53
+ type ShadowToken = string & {
54
+ readonly [SHADOW]: true;
55
+ };
56
+ /** Compile-time brand for a `border` token. */
57
+ type BorderToken = string & {
58
+ readonly [BORDER]: true;
59
+ };
60
+ /** Compile-time brand for a `transition` token. */
61
+ type TransitionToken = string & {
62
+ readonly [TRANSITION]: true;
63
+ };
64
+ /** Compile-time brand for a `gradient` token. */
65
+ type GradientToken = string & {
66
+ readonly [GRADIENT]: true;
67
+ };
68
+ /** Compile-time brand for a `strokeStyle` token. */
69
+ type StrokeStyleToken = string & {
70
+ readonly [STROKE_STYLE]: true;
71
+ };
72
+ /**
73
+ * Maps each {@link TokenType} to its token brand.
74
+ *
75
+ * @internal
76
+ */
77
+ interface BrandByType {
78
+ color: ColorToken;
79
+ dimension: DimensionToken;
80
+ duration: DurationToken;
81
+ fontFamily: FontFamilyToken;
82
+ fontWeight: FontWeightToken;
83
+ number: NumberToken;
84
+ cubicBezier: CubicBezierToken;
85
+ shadow: ShadowToken;
86
+ border: BorderToken;
87
+ transition: TransitionToken;
88
+ gradient: GradientToken;
89
+ strokeStyle: StrokeStyleToken;
90
+ }
91
+ /**
92
+ * The union of all twelve token brands. Brands are compile-time tags
93
+ * naming each token's type; they let {@link css} reject a dimension
94
+ * where a color belongs. They exist only in the type system — every
95
+ * ref is a plain string at runtime, so brands cost nothing — and they
96
+ * are theme-independent, so tokens minted by two different
97
+ * {@link createTheme} calls mix freely in one {@link css} call.
98
+ */
99
+ type AnyToken = BrandByType[TokenType];
100
+ //#endregion
101
+ //#region src/types.d.ts
102
+ /**
103
+ * A DTCG group node: an optional `$type` shared by descendants, plus
104
+ * nested groups and tokens.
105
+ *
106
+ * @internal
107
+ */
108
+ interface TokenGroup {
109
+ $type?: TokenType;
110
+ $description?: string;
111
+ $extensions?: Record<string, unknown>;
112
+ [key: string]: unknown;
113
+ }
114
+ /**
115
+ * The input document {@link createTheme} accepts, shaped by the
116
+ * [W3C DTCG format](https://www.designtokens.org/tr/drafts/format/). A
117
+ * node with a `$value` is a token; every other key is a group, and
118
+ * groups nest to any depth. A token's type comes from its own
119
+ * `$type`, from the token it aliases, or from the nearest ancestor
120
+ * group's `$type`, in that order.
121
+ *
122
+ * Each `$type` fixes the accepted `$value` forms and how the value
123
+ * serializes into CSS:
124
+ *
125
+ * | `$type` | Accepted `$value` | Serializes to |
126
+ * | --- | --- | --- |
127
+ * | `color` | CSS color string, or `{ colorSpace, components, alpha?, hex? }` | the string as written, `hex` when present, or the color-space function (`oklch(…)`, `color(display-p3 …)`) |
128
+ * | `dimension` | `"16px"`, or `{ value, unit }` with `px` or `rem` | the string verbatim, or concatenation |
129
+ * | `duration` | `"200ms"`, or `{ value, unit }` with `ms` or `s` | the string verbatim, or concatenation |
130
+ * | `fontFamily` | string or non-empty array of strings | quoted where needed, comma-joined |
131
+ * | `fontWeight` | number 1–1000, or a DTCG keyword like `"semi-bold"` | the number (keywords map to numbers) |
132
+ * | `number` | number | the number |
133
+ * | `cubicBezier` | `[x1, y1, x2, y2]` | `cubic-bezier(…)` |
134
+ * | `shadow` | `{ color, offsetX, offsetY, blur?, spread?, inset? }`, or an array of them | a CSS shadow list, `inset` first when `inset` is `true` |
135
+ * | `border` | `{ color, width, style }` | `width style color` |
136
+ * | `transition` | `{ duration, timingFunction, delay? }` | `duration timing-function delay` |
137
+ * | `gradient` | array of `{ color, position }` stops | a color-stop list for use inside `linear-gradient(…)` |
138
+ * | `strokeStyle` | keyword or object | the keyword, or `dashed` for the object form |
139
+ *
140
+ * Gradient stop positions must be literal numbers, though stop colors
141
+ * may be aliases. `typography` tokens throw — they need one variable
142
+ * per subproperty, which is not built.
143
+ *
144
+ * Only the object form of a `dimension` or `duration` is unit-checked.
145
+ * The string form is emitted verbatim, which is the way in for units
146
+ * the DTCG format does not cover (`em`, `ch`, `%`) and for computed
147
+ * values such as `clamp(…)`.
148
+ *
149
+ * A `$value` of `"{path.to.token}"` is an alias: it resolves to
150
+ * `var()` indirection in the emitted CSS, not a copied value, which is
151
+ * what makes mode overrides cascade. Aliases work as full token values
152
+ * and inside composite sub-values (e.g. a shadow's `color`), and they
153
+ * are type-checked — a token with an explicit `$type` only aliases a
154
+ * token of that same type. Unknown targets and reference cycles throw.
155
+ *
156
+ * @see {@link createTheme}
157
+ * @see {@link TokenTree} for the resulting accessor shape.
158
+ * @see {@link DeepPartialTokens} for the mode-override shape.
159
+ */
160
+ type DTCGDocument = TokenGroup;
161
+ type GroupType<N, Inherited> = N extends {
162
+ $type: infer Ty extends TokenType;
163
+ } ? Ty : Inherited;
164
+ type BrandOf<Ty> = Ty extends TokenType ? BrandByType[Ty] : never;
165
+ type TokenTypeOf<N, Root, Inherited> = N extends {
166
+ $type: infer Ty extends TokenType;
167
+ } ? Ty : N extends {
168
+ $value: `{${infer P}}`;
169
+ } ? TypeAtPath<Root, P, Root, GroupType<Root, undefined>> : Inherited extends TokenType ? Inherited : never;
170
+ type MatchKey<N, S extends string> = keyof N extends (infer K) ? K extends string | number ? `${K}` extends S ? K : never : never : never;
171
+ type TypeAtPath<N, P extends string, Root, Inherited> = P extends `${infer Head}.${infer Rest}` ? MatchKey<N, Head> extends (infer K) ? [K] extends [never] ? never : TypeAtPath<N[K & keyof N], Rest, Root, GroupType<N[K & keyof N], Inherited>> : never : MatchKey<N, P> extends (infer K) ? [K] extends [never] ? never : TokenTypeOf<N[K & keyof N], Root, Inherited> : never;
172
+ type TreeOf<N, Root, Inherited> = { [K in Exclude<keyof N, `$${string}`>]: N[K] extends {
173
+ $value: unknown;
174
+ } ? BrandOf<TokenTypeOf<N[K], Root, Inherited>> : TreeOf<N[K], Root, GroupType<N[K], Inherited>>; };
175
+ /**
176
+ * The accessor shape for a document `T`: the same nesting as the
177
+ * document, with every token leaf replaced by its branded `var(--…)`
178
+ * reference string. Numeric keys index with brackets
179
+ * (`t.color.gray[900]`).
180
+ *
181
+ * An `any` document (e.g. from `JSON.parse`) short-circuits to
182
+ * `unknown` — mapping over `any` would otherwise recurse without
183
+ * bound.
184
+ *
185
+ * @see {@link ThemeResult}
186
+ */
187
+ type TokenTree<T> = 0 extends 1 & T ? unknown : TreeOf<T, T, GroupType<T, undefined>>;
188
+ /**
189
+ * The mode-override shape for a document `T`: every group is optional
190
+ * and each token node is reduced to `{ $value }`. A mode overrides a
191
+ * token's value only, never its `$type` or structure.
192
+ *
193
+ * An `any` document short-circuits to `unknown`, as in
194
+ * {@link TokenTree}.
195
+ *
196
+ * @see {@link ThemeOptions}
197
+ */
198
+ type DeepPartialTokens<T> = 0 extends 1 & T ? unknown : { [K in Exclude<keyof T, `$${string}`>]?: T[K] extends {
199
+ $value: unknown;
200
+ } ? {
201
+ $value: unknown;
202
+ } : DeepPartialTokens<T[K]>; };
203
+ //#endregion
204
+ //#region src/theme.d.ts
205
+ /**
206
+ * Options for {@link createTheme}.
207
+ *
208
+ * @see {@link DeepPartialTokens} for the override shape.
209
+ */
210
+ interface ThemeOptions<T> {
211
+ /**
212
+ * Per-appearance token overrides. Each mode is a partial of the
213
+ * base document that may set `$value` only; its overrides emit
214
+ * inside an `@media (prefers-color-scheme: <mode>)` block, so the
215
+ * OS appearance setting flips the affected variables with no
216
+ * attribute selectors and no JavaScript.
217
+ */
218
+ modes?: {
219
+ /** Overrides applied under `prefers-color-scheme: light`. */
220
+ light?: DeepPartialTokens<T>;
221
+ /** Overrides applied under `prefers-color-scheme: dark`. */
222
+ dark?: DeepPartialTokens<T>;
223
+ };
224
+ }
225
+ /**
226
+ * Props for the {@link ThemeComponent}. `nonce` sets the `nonce`
227
+ * attribute on the emitted `<style>` element for Content Security
228
+ * Policy setups.
229
+ */
230
+ type ThemeProps = {
231
+ /** CSP nonce forwarded to the `<style>` element's `nonce` attribute. */
232
+ nonce?: string;
233
+ };
234
+ /**
235
+ * The `<Theme />` component returned by {@link createTheme}. Render it
236
+ * once near the document root (e.g. inside `<head>`). It renders a
237
+ * `<style data-pitlane-theme>` element holding the base `:root`
238
+ * declarations plus one `@media` block per configured mode, streams
239
+ * identically on the server and the client, and escapes the style
240
+ * text so token values cannot break out of the tag.
241
+ *
242
+ * @see {@link ThemeProps} for the `nonce` prop.
243
+ */
244
+ type ThemeComponent = (handle: Handle<ThemeProps>) => () => RemixElement;
245
+ /**
246
+ * The object returned by {@link createTheme}: the typed token
247
+ * accessor, the `raw` resolver, and the `<Theme />` component.
248
+ */
249
+ interface ThemeResult<T> {
250
+ /**
251
+ * Same-shape accessor over the document: every token leaf is a
252
+ * branded `var(--…)` reference string. Numeric keys index with
253
+ * brackets (`t.color.gray[900]`).
254
+ *
255
+ * @see {@link TokenTree}
256
+ */
257
+ token: TokenTree<T>;
258
+ /**
259
+ * Resolves a token ref to its serialized base-mode value, chasing
260
+ * aliases and composite sub-value references to the end. It always
261
+ * answers for the base mode, even when a dark override exists,
262
+ * since mode resolution happens in CSS rather than in JavaScript.
263
+ * Because refs are plain strings, two themes that mint the same
264
+ * token path produce identical refs and `raw` cannot tell them
265
+ * apart, answering for its own theme.
266
+ *
267
+ * @param ref - A branded token ref from this theme's accessor.
268
+ * @throws ThemeError if `ref` names a variable this theme never
269
+ * minted.
270
+ */
271
+ raw(ref: AnyToken): string;
272
+ /** The `<Theme />` component. @see {@link ThemeComponent} */
273
+ Theme: ThemeComponent;
274
+ }
275
+ /**
276
+ * Compiles a DTCG design-token document into a typed accessor, a
277
+ * `raw` resolver, and a `<Theme />` component. All validation and
278
+ * serialization happen eagerly here: a malformed document throws
279
+ * {@link ThemeError} rather than emitting broken CSS.
280
+ *
281
+ * Each token becomes a CSS custom property named after its
282
+ * kebab-cased path — `color.gray.900` becomes `--color-gray-900`.
283
+ * Two paths that collide after kebab-casing throw, as do names
284
+ * containing `.`, `{`, or `}`, which the alias syntax reserves.
285
+ *
286
+ * Author the document in TypeScript, not imported JSON: `createTheme`
287
+ * infers a `const` type parameter, so an inline object needs no
288
+ * `as const`, but a JSON import widens its literals and the token
289
+ * brands degrade.
290
+ *
291
+ * @param config - The token document. Groups nest to any depth; a
292
+ * node with a `$value` is a token.
293
+ * @param options - Optional per-mode overrides ({@link ThemeOptions}).
294
+ * @returns The {@link ThemeResult}: `token`, `raw`, and `Theme`.
295
+ * @throws ThemeError on any validation failure (unknown or
296
+ * unresolvable `$type`, reserved characters, variable collision,
297
+ * unknown or wrong-typed alias, alias cycle, invalid value, or a bad
298
+ * mode override).
299
+ *
300
+ * @see {@link DTCGDocument} for the document shape, the accepted
301
+ * `$value` forms, and alias semantics.
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * export let { token: t, raw, Theme } = createTheme(
306
+ * {
307
+ * color: {
308
+ * $type: "color",
309
+ * white: { $value: "#fff" },
310
+ * gray: { 900: { $value: "#171717" } },
311
+ * bg: { $value: "{color.white}" }, // alias → var() indirection
312
+ * },
313
+ * },
314
+ * { modes: { dark: { color: { bg: { $value: "{color.gray.900}" } } } } },
315
+ * );
316
+ *
317
+ * t.color.bg; // "var(--color-bg)"
318
+ * raw(t.color.bg); // "#fff" (base mode, alias chased to the end)
319
+ * ```
320
+ */
321
+ declare function createTheme<const T extends DTCGDocument>(config: T, options?: ThemeOptions<T>): ThemeResult<T>;
322
+ //#endregion
323
+ //#region src/props.d.ts
324
+ type Wide = "inherit" | "initial" | "unset" | "revert" | "revert-layer";
325
+ type ColorLike = ColorToken | "transparent" | "currentColor" | Wide;
326
+ type Size = DimensionToken | 0 | Wide;
327
+ type SizeAuto = DimensionToken | 0 | "auto" | Wide;
328
+ type SizeIntrinsic = DimensionToken | 0 | "auto" | "min-content" | "max-content" | "fit-content" | Wide;
329
+ type SpacingText = DimensionToken | 0 | "normal" | Wide;
330
+ type LineWidth = DimensionToken | 0 | "thin" | "medium" | "thick" | Wide;
331
+ type Easing = CubicBezierToken | "ease" | "linear" | "ease-in" | "ease-out" | "ease-in-out" | "step-start" | "step-end" | Wide;
332
+ type ShadowLike = ShadowToken | "none" | Wide;
333
+ type Numeric = NumberToken | number | Wide;
334
+ type Repeat1to4<V> = readonly [V] | readonly [V, V] | readonly [V, V, V] | readonly [V, V, V, V];
335
+ type PadItem = DimensionToken | 0;
336
+ type MarginItem = DimensionToken | 0 | "auto";
337
+ /**
338
+ * Longhands whose values must come from the token document. Each one
339
+ * narrows the matching CSS property to a token brand plus the small
340
+ * keyword set the property genuinely needs.
341
+ *
342
+ * @see {@link ThemedCSSProps}
343
+ */
344
+ interface TokenMappedProps {
345
+ color?: ColorLike;
346
+ backgroundColor?: ColorLike;
347
+ borderColor?: ColorLike;
348
+ borderTopColor?: ColorLike;
349
+ borderRightColor?: ColorLike;
350
+ borderBottomColor?: ColorLike;
351
+ borderLeftColor?: ColorLike;
352
+ borderBlockColor?: ColorLike;
353
+ borderInlineColor?: ColorLike;
354
+ outlineColor?: ColorLike;
355
+ textDecorationColor?: ColorLike;
356
+ columnRuleColor?: ColorLike;
357
+ caretColor?: ColorLike;
358
+ accentColor?: ColorLike;
359
+ fill?: ColorLike;
360
+ stroke?: ColorLike;
361
+ width?: SizeIntrinsic;
362
+ height?: SizeIntrinsic;
363
+ minWidth?: SizeIntrinsic;
364
+ minHeight?: SizeIntrinsic;
365
+ maxWidth?: SizeIntrinsic;
366
+ maxHeight?: SizeIntrinsic;
367
+ blockSize?: SizeIntrinsic;
368
+ inlineSize?: SizeIntrinsic;
369
+ minBlockSize?: SizeIntrinsic;
370
+ minInlineSize?: SizeIntrinsic;
371
+ maxBlockSize?: SizeIntrinsic;
372
+ maxInlineSize?: SizeIntrinsic;
373
+ flexBasis?: SizeIntrinsic;
374
+ top?: SizeAuto;
375
+ right?: SizeAuto;
376
+ bottom?: SizeAuto;
377
+ left?: SizeAuto;
378
+ insetBlockStart?: SizeAuto;
379
+ insetBlockEnd?: SizeAuto;
380
+ insetInlineStart?: SizeAuto;
381
+ insetInlineEnd?: SizeAuto;
382
+ marginTop?: SizeAuto;
383
+ marginRight?: SizeAuto;
384
+ marginBottom?: SizeAuto;
385
+ marginLeft?: SizeAuto;
386
+ marginBlockStart?: SizeAuto;
387
+ marginBlockEnd?: SizeAuto;
388
+ marginInlineStart?: SizeAuto;
389
+ marginInlineEnd?: SizeAuto;
390
+ paddingTop?: Size;
391
+ paddingRight?: Size;
392
+ paddingBottom?: Size;
393
+ paddingLeft?: Size;
394
+ paddingBlockStart?: Size;
395
+ paddingBlockEnd?: Size;
396
+ paddingInlineStart?: Size;
397
+ paddingInlineEnd?: Size;
398
+ fontSize?: Size;
399
+ textIndent?: Size;
400
+ outlineOffset?: Size;
401
+ borderTopLeftRadius?: Size;
402
+ borderTopRightRadius?: Size;
403
+ borderBottomRightRadius?: Size;
404
+ borderBottomLeftRadius?: Size;
405
+ rowGap?: Size;
406
+ columnGap?: Size;
407
+ letterSpacing?: SpacingText;
408
+ wordSpacing?: SpacingText;
409
+ borderTopWidth?: LineWidth;
410
+ borderRightWidth?: LineWidth;
411
+ borderBottomWidth?: LineWidth;
412
+ borderLeftWidth?: LineWidth;
413
+ outlineWidth?: LineWidth;
414
+ padding?: Size | Repeat1to4<PadItem>;
415
+ paddingBlock?: Size | readonly [PadItem, PadItem];
416
+ paddingInline?: Size | readonly [PadItem, PadItem];
417
+ margin?: SizeAuto | Repeat1to4<MarginItem>;
418
+ marginBlock?: SizeAuto | readonly [MarginItem, MarginItem];
419
+ marginInline?: SizeAuto | readonly [MarginItem, MarginItem];
420
+ inset?: SizeAuto | Repeat1to4<MarginItem>;
421
+ insetBlock?: SizeAuto | readonly [MarginItem, MarginItem];
422
+ insetInline?: SizeAuto | readonly [MarginItem, MarginItem];
423
+ borderRadius?: Size | Repeat1to4<PadItem>;
424
+ gap?: Size | readonly [PadItem, PadItem];
425
+ fontFamily?: FontFamilyToken | Wide;
426
+ fontWeight?: FontWeightToken | "normal" | "bold" | "lighter" | "bolder" | Wide;
427
+ lineHeight?: NumberToken | DimensionToken | "normal" | Wide;
428
+ opacity?: Numeric;
429
+ zIndex?: Numeric;
430
+ flexGrow?: Numeric;
431
+ flexShrink?: Numeric;
432
+ order?: Numeric;
433
+ transitionDuration?: DurationToken | Wide;
434
+ transitionDelay?: DurationToken | Wide;
435
+ animationDuration?: DurationToken | Wide;
436
+ animationDelay?: DurationToken | Wide;
437
+ transitionTimingFunction?: Easing;
438
+ animationTimingFunction?: Easing;
439
+ boxShadow?: ShadowLike;
440
+ textShadow?: ShadowLike;
441
+ }
442
+ /**
443
+ * Every CSS property [csstype](https://github.com/frenic/csstype) knows
444
+ * that {@link TokenMappedProps} does not claim, with lengths narrowed to
445
+ * `dimension` tokens and times to `duration` tokens.
446
+ *
447
+ * Properties whose grammar is a closed keyword set (`display`, `resize`,
448
+ * `position`, …) resolve to that keyword union. Properties that accept
449
+ * open-ended values (`background`, `transform`, `gridTemplateColumns`, …)
450
+ * keep csstype's `string` escape, because CSS genuinely allows any
451
+ * `calc()`, function, or list there.
452
+ *
453
+ * @see {@link ThemedCSSProps}
454
+ */
455
+ type KeywordProps = Omit<CSS.Properties<DimensionToken | 0, DurationToken>, keyof TokenMappedProps>;
456
+ /**
457
+ * The style-object type accepted by {@link css} and every {@link tva}
458
+ * slot.
459
+ *
460
+ * Two layers stack. Token-mapped longhands enforce the matching token
461
+ * brand plus a small set of CSS keywords and the literal `0`. Every
462
+ * other property carries csstype's value union, so closed-grammar
463
+ * properties such as `display`, `position`, `resize`, and `overflow`
464
+ * only accept their real keywords. Unknown keys — nested selectors,
465
+ * at-rules, and CSS custom properties — recurse or stay loose.
466
+ *
467
+ * `Wide` below is the CSS-wide keyword union
468
+ * `"inherit" | "initial" | "unset" | "revert" | "revert-layer"`.
469
+ *
470
+ * | Property family | Accepted values |
471
+ * | --- | --- |
472
+ * | `color`, `backgroundColor`, `borderColor`, the four per-side and two logical border colors, `outlineColor`, `textDecorationColor`, `columnRuleColor`, `caretColor`, `accentColor`, `fill`, `stroke` | `ColorToken \| "transparent" \| "currentColor" \| Wide` |
473
+ * | `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight`, their `blockSize`/`inlineSize` logical forms, `flexBasis` | `DimensionToken \| 0 \| "auto" \| "min-content" \| "max-content" \| "fit-content" \| Wide` |
474
+ * | `top`, `right`, `bottom`, `left`, `marginTop`, `marginRight`, `marginBottom`, `marginLeft`, and their `inset*`/`margin*` logical start/end forms | `DimensionToken \| 0 \| "auto" \| Wide` |
475
+ * | `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft`, the logical padding start/end forms, `fontSize`, `textIndent`, `outlineOffset`, the four corner radii, `rowGap`, `columnGap` | `DimensionToken \| 0 \| Wide` |
476
+ * | `letterSpacing`, `wordSpacing` | `DimensionToken \| 0 \| "normal" \| Wide` |
477
+ * | `borderTopWidth`, `borderRightWidth`, `borderBottomWidth`, `borderLeftWidth`, `outlineWidth` | `DimensionToken \| 0 \| "thin" \| "medium" \| "thick" \| Wide` |
478
+ * | `padding`, `margin`, `inset`, `borderRadius` (box shorthands) | a single value as the longhand, or a tuple of 1–4 such values, space-joined |
479
+ * | `gap`, `paddingBlock`, `paddingInline`, `marginBlock`, `marginInline`, `insetBlock`, `insetInline` | the longhand value, or a 2-tuple |
480
+ * | `fontFamily` | `FontFamilyToken \| Wide` |
481
+ * | `fontWeight` | `FontWeightToken \| "normal" \| "bold" \| "lighter" \| "bolder" \| Wide` |
482
+ * | `lineHeight` | `NumberToken \| DimensionToken \| "normal" \| Wide` |
483
+ * | `opacity`, `zIndex`, `flexGrow`, `flexShrink`, `order` | `NumberToken \| number \| Wide` (plain numbers stay legal) |
484
+ * | `transitionDuration`, `transitionDelay`, `animationDuration`, `animationDelay` | `DurationToken \| Wide` |
485
+ * | `transitionTimingFunction`, `animationTimingFunction` | `CubicBezierToken \| "ease" \| "linear" \| "ease-in" \| "ease-out" \| "ease-in-out" \| "step-start" \| "step-end" \| Wide` |
486
+ * | `boxShadow`, `textShadow` | `ShadowToken \| "none" \| Wide` |
487
+ * | every other CSS property | csstype's union for that property, with `dimension` tokens for lengths and `duration` tokens for times |
488
+ *
489
+ * @see {@link css}
490
+ */
491
+ interface ThemedCSSProps extends KeywordProps, TokenMappedProps {
492
+ [key: string]: ThemedCSSProps | string | number | null | undefined | readonly (string | number)[];
493
+ }
494
+ //#endregion
495
+ //#region src/css.d.ts
496
+ type RemixCSSProps = Parameters<typeof css$1>[0];
497
+ /**
498
+ * The descriptor {@link css} produces. `MixinDescriptor` is invariant
499
+ * in its node type, so — exactly like `remix/ui`'s own `css` factory —
500
+ * the node binds per callsite through the generic parameter.
501
+ *
502
+ * @see {@link css}
503
+ */
504
+ type ThemedCSSMixin<node extends Element = Element> = MixinDescriptor<node, [styles: RemixCSSProps], ElementProps>;
505
+ /**
506
+ * Brand-enforced wrapper over `remix/ui`'s `css()` mixin. Token-mapped
507
+ * longhands accept the matching token brand, CSS-wide keywords,
508
+ * property keywords, and `0`; anything else — including a raw
509
+ * `color: "#ff0000"` — is a type error. Every other CSS property
510
+ * carries csstype's value union, so `display`, `position`, `resize`,
511
+ * and the rest of the closed-grammar properties accept only their real
512
+ * keywords. Nested selectors, at-rules, and custom properties recurse.
513
+ *
514
+ * Branded token refs are already `var()` strings and pass through; an
515
+ * array value joins with spaces, which is how the box shorthands take
516
+ * a 1–4 tuple. A comma list needs a template string.
517
+ *
518
+ * `css()` is node-generic, exactly like `remix/ui`'s own `css`: the
519
+ * descriptor binds to the element type of the `mix` position it
520
+ * appears in, so write `css({ … })` inline at each element and share
521
+ * {@link ThemedCSSProps} objects, never stored descriptors.
522
+ *
523
+ * Interpolating a token into a template string
524
+ * (`` `1px solid ${t.color.line}` ``) yields a plain string, which the
525
+ * open-grammar shorthands accept.
526
+ *
527
+ * @see {@link ThemedCSSProps} for the accepted per-property values.
528
+ * @see {@link ThemedCSSMixin} for the returned descriptor.
529
+ *
530
+ * @example
531
+ * ```tsx
532
+ * <div
533
+ * mix={css({
534
+ * color: t.color.bg,
535
+ * padding: [t.space.sm, t.space.md],
536
+ * margin: 0,
537
+ * "&:hover": { color: t.color.gray[900] },
538
+ * })}
539
+ * />
540
+ * ```
541
+ */
542
+ declare function css<node extends Element = Element>(styles: ThemedCSSProps): ThemedCSSMixin<node>;
543
+ //#endregion
544
+ //#region src/tva.d.ts
545
+ type VariantShape = Record<string, Record<string, ThemedCSSProps>>;
546
+ type VariantValue<K> = K extends "true" | "false" ? boolean : K;
547
+ /**
548
+ * Controller-approved one-token deviation from the brief: `-readonly` strips
549
+ * the readonly modifier that a homomorphic mapped type would otherwise
550
+ * inherit from V's const-inferred (deeply readonly) property modifiers.
551
+ * TVAProps<F> must yield ordinary mutable optional props for consumers;
552
+ * readonly inheritance here is an artifact of `const V`, not intent.
553
+ */
554
+ type Selection<V extends VariantShape> = { -readonly [K in keyof V]?: VariantValue<keyof V[K] & string>; };
555
+ /**
556
+ * Configuration for {@link tva}. Every style slot is
557
+ * {@link ThemedCSSProps}, carrying the same brand enforcement as
558
+ * {@link css}.
559
+ */
560
+ interface TVAConfig<V extends VariantShape> {
561
+ /** Styles applied to every invocation, before any variant. */
562
+ base?: ThemedCSSProps;
563
+ /**
564
+ * The variant axes. Each axis maps option names to styles; name
565
+ * an axis's options `true` and `false` to accept a boolean.
566
+ */
567
+ variants?: V;
568
+ /**
569
+ * Extra styles applied when every listed variant condition
570
+ * matches, merged after the individual variants in array order.
571
+ */
572
+ compoundVariants?: readonly (Selection<V> & {
573
+ css: ThemedCSSProps;
574
+ })[];
575
+ /**
576
+ * Options used for axes the caller leaves unset. Passing an axis
577
+ * `undefined` explicitly still falls back to its default.
578
+ */
579
+ defaultVariants?: Selection<V>;
580
+ }
581
+ /**
582
+ * A variant component built by {@link tva}. Call it with a variant
583
+ * selection to get a `mix`-ready descriptor; call `resolve` for the
584
+ * merged {@link ThemedCSSProps} without building a descriptor.
585
+ */
586
+ interface TVAFn<V extends VariantShape> {
587
+ /**
588
+ * Resolves the selection and returns a `mix`-ready descriptor.
589
+ * Node-generic like {@link css}: `MixinDescriptor` is invariant in
590
+ * its node, so the element type binds per `mix` callsite.
591
+ */
592
+ <node extends Element = Element>(props?: Selection<V>): ThemedCSSMixin<node>;
593
+ /**
594
+ * Returns the merged style object for a selection without
595
+ * producing a descriptor. {@link combine} is built on it.
596
+ */
597
+ resolve(props?: Selection<V>): ThemedCSSProps;
598
+ }
599
+ /**
600
+ * Extracts a {@link tva} component's variant props, like cva's
601
+ * `VariantProps`. All props are optional; wrap the component to
602
+ * require one.
603
+ *
604
+ * @example
605
+ * ```ts
606
+ * export type ButtonProps = TVAProps<typeof button>;
607
+ * // { intent?: "primary" | "secondary"; size?: "sm" | "md" }
608
+ * ```
609
+ */
610
+ type TVAProps<F> = F extends TVAFn<infer V> ? Selection<V> : never;
611
+ /**
612
+ * Builds a variant resolver modeled on [cva](https://cva.style). Where
613
+ * cva composes class strings, `tva` composes brand-enforced style
614
+ * objects into a `mix`-ready descriptor.
615
+ *
616
+ * Each invocation resolves the selection by deep-merging `base`, then
617
+ * every matching variant in declaration order, then every matching
618
+ * compound variant in array order, and feeds the result to a single
619
+ * {@link css} call. `defaultVariants` fills in unset axes, and boolean
620
+ * axes come from options named `true` and `false`.
621
+ *
622
+ * @see {@link TVAConfig} for the configuration shape.
623
+ * @see {@link TVAProps} to extract the props type.
624
+ *
625
+ * @example
626
+ * ```ts
627
+ * export let button = tva({
628
+ * base: { borderRadius: t.radius.md },
629
+ * variants: {
630
+ * intent: {
631
+ * primary: { backgroundColor: t.color.accent },
632
+ * secondary: { backgroundColor: "transparent" },
633
+ * },
634
+ * size: { sm: { fontSize: t.text.sm }, md: { fontSize: t.text.md } },
635
+ * block: { true: { display: "flex" } },
636
+ * },
637
+ * compoundVariants: [
638
+ * { intent: "secondary", size: "md", css: { fontSize: t.text.lg } },
639
+ * ],
640
+ * defaultVariants: { intent: "primary", size: "md" },
641
+ * });
642
+ *
643
+ * <button mix={button({ intent: "secondary", block: true })} />;
644
+ * ```
645
+ */
646
+ declare function tva<const V extends VariantShape>(config: TVAConfig<V>): TVAFn<V>;
647
+ type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I : never;
648
+ type CombinedProps<Fns extends readonly TVAFn<VariantShape>[]> = UnionToIntersection<Exclude<Parameters<Fns[number]>[0], undefined>> extends (infer P) ? { [K in keyof P]: P[K]; } : never;
649
+ /**
650
+ * A component built by {@link combine}. Accepts the union of the input
651
+ * components' props and honors each input's own defaults.
652
+ */
653
+ interface CombinedTVAFn<Fns extends readonly TVAFn<VariantShape>[]> {
654
+ /** Resolves every input and returns a `mix`-ready descriptor. */
655
+ <node extends Element = Element>(props?: CombinedProps<Fns>): ThemedCSSMixin<node>;
656
+ /** Returns the merged style object without building a descriptor. */
657
+ resolve(props?: CombinedProps<Fns>): ThemedCSSProps;
658
+ }
659
+ /**
660
+ * Composes {@link tva} components, like cva's `compose`. Each input
661
+ * resolves independently against the shared props, the results
662
+ * deep-merge in argument order, and one {@link css} call produces the
663
+ * descriptor. The result accepts the union of the inputs' props and
664
+ * honors each input's own defaults.
665
+ *
666
+ * @see {@link CombinedTVAFn}
667
+ *
668
+ * @example
669
+ * ```tsx
670
+ * export let pillButton = combine(button, rounded);
671
+ * <button mix={pillButton({ intent: "primary", pill: true })} />;
672
+ * ```
673
+ */
674
+ declare function combine<Fns extends readonly TVAFn<VariantShape>[]>(...fns: Fns): CombinedTVAFn<Fns>;
675
+ /**
676
+ * A value {@link cx} accepts: a string, a number, a nested array of
677
+ * the same, or a record whose truthy keys are emitted. `null`,
678
+ * `undefined`, and `false` are dropped. Compatible with clsx.
679
+ */
680
+ type ClassValue = string | number | null | undefined | false | readonly ClassValue[] | Record<string, boolean | null | undefined>;
681
+ /**
682
+ * clsx-compatible `className` joiner for interop with plain
683
+ * stylesheets, since `mix` and `className` compose on the same
684
+ * element. Strings and numbers join with spaces, falsy values drop,
685
+ * arrays flatten, and truthy object keys join.
686
+ *
687
+ * @see {@link ClassValue}
688
+ *
689
+ * @example
690
+ * ```tsx
691
+ * <span className={cx("mono", isAlias && "alias-tag")} />;
692
+ * ```
693
+ */
694
+ declare function cx(...inputs: ClassValue[]): string;
695
+ //#endregion
696
+ //#region src/tokens.d.ts
697
+ /**
698
+ * The error {@link createTheme} throws for every validation and
699
+ * serialization failure. Validation is eager, so a bad document never
700
+ * emits CSS. Every message names the offending token path.
701
+ *
702
+ * | Condition | Message shape |
703
+ * | --- | --- |
704
+ * | Unknown `$type` | `"color.brand" has unknown $type "sparkles"` |
705
+ * | Unresolvable `$type` | `"color.brand" has no resolvable $type` |
706
+ * | Typography token | `"heading": typography tokens are not supported in v1` |
707
+ * | Reserved character in a name | `Token or group name "a.b" contains characters reserved by DTCG references (".", "{", "}")` |
708
+ * | Empty CSS identifier | `Token path segment "!" produces an empty CSS identifier` |
709
+ * | Malformed node | `"color.bg" is neither a group nor a token` |
710
+ * | Variable-name collision | `Tokens "a" and "b" both produce the CSS variable --x` |
711
+ * | Alias to a missing token | `"color.bg" references unknown token "color.white"` |
712
+ * | Alias to a wrong-typed token | `"x" references "space.sm" of type "dimension" where "color" is required` |
713
+ * | Alias cycle | `Alias cycle: a → b → a` |
714
+ * | Invalid value for a declared type | `"x" has an invalid color value: …` — also `unknown colorSpace`, `unknown fontWeight keyword`, and `unknown strokeStyle keyword`; an empty `fontFamily` array counts |
715
+ * | Bad mode override | `Mode override "x" does not exist in the base document`, `Mode override "x" may only set $value`, or (via a cross-type alias) the wrong-typed-alias message |
716
+ * | Unminted `raw()` ref | `raw(): "var(--x)" names a var this theme never minted` |
717
+ */
718
+ declare class ThemeError extends Error {
719
+ name: string;
720
+ }
721
+ //#endregion
722
+ export { type AnyToken, type BorderToken, type ClassValue, type ColorToken, type CombinedTVAFn, type CubicBezierToken, type DTCGDocument, type DeepPartialTokens, type DimensionToken, type DurationToken, type FontFamilyToken, type FontWeightToken, type GradientToken, type NumberToken, type ShadowToken, type StrokeStyleToken, type TVAConfig, type TVAFn, type TVAProps, type ThemeComponent, ThemeError, type ThemeOptions, type ThemeProps, type ThemeResult, type ThemedCSSMixin, type ThemedCSSProps, type TokenTree, type TokenType, type TransitionToken, combine, createTheme, css, cx, tva };