You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/lib/recipes/base.ts

4275 lines
196 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type {
RecipeTokenSet,
RecipeTokenMap,
RecipeTokenMultiDeclaration,
RecipeTokenDeclaration
} from '../config-types';
export const THEME_BASE_RECIPE_TOKENS = {
accordion: {
'provider-border-width': 'var(--border-width)',
'provider-radius': 'var(--radius-lg)',
'outline-provider-bg': 'var(--color-surface-default)',
'outline-provider-border': 'var(--color-border-default)',
'outline-item-border': 'var(--color-border-subtle)',
'outline-content-bg': 'var(--color-surface-default)',
'surface-provider-bg': 'var(--color-surface-raised)',
'surface-provider-border': 'var(--color-border-subtle)',
'surface-provider-shadow': 'var(--shadow-subtle)',
'surface-item-border': 'var(--color-border-subtle)',
'surface-content-bg': 'var(--color-surface-default)',
'ghost-provider-bg': 'transparent',
'ghost-provider-border': 'transparent',
'ghost-item-border': 'var(--color-border-subtle)',
'ghost-content-bg': 'transparent',
'item-border-width': 'var(--border-width)',
'trigger-min-height-xs': 'var(--control-height-xs)',
'trigger-min-height-sm': 'var(--control-height-sm)',
'trigger-min-height-md': 'var(--control-height-md)',
'trigger-min-height-lg': 'var(--control-height-lg)',
'trigger-min-height-full': 'var(--control-height-xl)',
'trigger-px-xs': 'var(--space-2-5)',
'trigger-px-sm': 'var(--space-3)',
'trigger-px-md': 'var(--space-4)',
'trigger-px-lg': 'var(--space-5)',
'trigger-px-full': 'var(--space-6)',
'trigger-py-xs': 'var(--space-2)',
'trigger-py-sm': 'var(--space-2-5)',
'trigger-py-md': 'var(--space-3)',
'trigger-py-lg': 'var(--space-3-5)',
'trigger-py-full': 'var(--space-4)',
'trigger-gap': 'var(--space-3)',
'trigger-font-family': 'var(--font-ui)',
'trigger-font-size-xs': 'var(--text-2-size)',
'trigger-font-size-sm': 'var(--text-3-size)',
'trigger-font-size-md': 'var(--text-4-size)',
'trigger-font-size-lg': 'var(--text-5-size)',
'trigger-font-size-full': 'var(--text-6-size)',
'trigger-font-weight': 'var(--font-weight-medium)',
'trigger-line-height': 'var(--leading-ui)',
'trigger-color': 'var(--color-content-primary)',
// Open state: keep the trigger text in the primary content tone.
// The chevron rotation + revealed content already signal openness;
// recoloring the heading adds noise without information (matches
// shadcn/Radix/Ark).
'trigger-color-open': 'var(--color-content-primary)',
'trigger-color-disabled': 'var(--color-content-disabled)',
'trigger-hover-bg': 'var(--color-surface-raised)',
'trigger-hover-color': 'var(--color-content-primary)',
'trigger-transition-duration': 'var(--duration-fast)',
'trigger-transition-ease': 'var(--ease-default)',
'trigger-focus-ring-offset': 'var(--focus-ring-offset)',
'trigger-focus-ring-width': 'var(--focus-ring-width)',
'trigger-focus-ring-color': 'var(--focus-ring-color)',
'content-px-xs': 'var(--space-2-5)',
'content-px-sm': 'var(--space-3)',
'content-px-md': 'var(--space-4)',
'content-px-lg': 'var(--space-5)',
'content-px-full': 'var(--space-6)',
'content-py-xs': 'var(--space-2)',
'content-py-sm': 'var(--space-2-5)',
'content-py-md': 'var(--space-3)',
'content-py-lg': 'var(--space-4)',
'content-py-full': 'var(--space-5)',
'content-font-family': 'var(--font-prose)',
'content-font-size': 'var(--text-4-size)',
'content-line-height': 'var(--leading-prose)',
'content-color': 'var(--color-content-secondary)',
'content-open-duration': 'var(--duration-normal)',
'content-close-duration': 'var(--duration-fast)',
'content-open-ease': 'var(--ease-out)',
'content-close-ease': 'var(--ease-in)',
'indicator-size-xs': 'var(--icon-size-xs)',
'indicator-size-sm': 'var(--icon-size-sm)',
'indicator-size-md': 'var(--icon-size-sm)',
'indicator-size-lg': 'var(--icon-size-md)',
'indicator-color': 'var(--color-content-secondary)',
// Indicator turns to the primary content tone when open — a subtle
// emphasis without the loud accent recolor.
'indicator-color-open': 'var(--color-content-primary)',
'indicator-rotation': '180deg',
'indicator-transition-duration': 'var(--duration-fast)',
'indicator-transition-ease': 'var(--ease-default)'
},
avatar: ((): RecipeTokenMap => {
// TSC v2.2 — 2D matrix (variant × color) cascade.
//
// Avatar's per-instance palette varies across 8 colors × 3
// variants. Each variant flavor (solid/soft/outline) reads
// different slots from the same color block. Encoding 24
// composite scopes by hand for 6 tokens (3 on the root, 3 on the
// badge part) is 144 lines of boilerplate; this small helper
// generates them with the same intent.
//
// Helper consumed only here — explicit declarations would obscure
// the variant logic. Keep the helper local to the entry to avoid
// polluting the recipes module surface.
const COLORS = [
'primary',
'secondary',
'neutral',
'affirm',
'fulfill',
'risk',
'threat',
'loss'
] as const;
type AvatarColor = (typeof COLORS)[number];
type AvatarVariant = 'solid' | 'soft' | 'outline';
/** Build (variant × color) declarations for a slot getter. */
function matrix(
pick: (color: AvatarColor, variant: AvatarVariant) => string | undefined,
parts?: readonly string[]
): RecipeTokenMultiDeclaration {
const declarations: RecipeTokenDeclaration[] = [];
for (const variant of ['solid', 'soft', 'outline'] as const) {
for (const color of COLORS) {
const value = pick(color, variant);
if (value === undefined) continue;
declarations.push({
value,
scope: [`variant:${variant}`, `color:${color}`]
});
}
}
return parts ? { parts, declarations } : { declarations };
}
return {
'size-xs': '24px',
'size-sm': '32px',
'size-md': '40px',
'size-lg': '48px',
'size-xl': '64px',
'size-xxl': '96px',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xxs)',
'font-size-sm': 'var(--font-size-xs)',
'font-size-md': 'var(--font-size-sm)',
'font-size-lg': 'var(--font-size-md)',
'font-size-xl': 'var(--font-size-lg)',
'font-size-xxl': 'var(--font-size-xl)',
'font-weight': 'var(--font-weight-medium)',
'radius-full': 'var(--radius-full)',
'radius-md': 'var(--radius-md)',
'radius-sm': 'var(--radius-sm)',
'radius-none': '0',
'border-width': 'var(--border-width-medium)',
'primary-solid-bg': 'var(--color-primary-solid)',
'primary-solid-fg': 'var(--color-primary-contrast)',
'primary-soft-bg': 'var(--color-primary-surface)',
'primary-soft-fg': 'var(--color-primary-text)',
'primary-outline-border': 'var(--color-primary-border)',
'primary-outline-fg': 'var(--color-primary-text)',
'secondary-solid-bg': 'var(--color-secondary-solid)',
'secondary-solid-fg': 'var(--color-secondary-contrast)',
'secondary-soft-bg': 'var(--color-secondary-surface)',
'secondary-soft-fg': 'var(--color-secondary-text)',
'secondary-outline-border': 'var(--color-secondary-border)',
'secondary-outline-fg': 'var(--color-secondary-text)',
'neutral-solid-bg': 'var(--color-neutral-solid)',
'neutral-solid-fg': 'var(--color-neutral-contrast)',
'neutral-soft-bg': 'var(--color-neutral-surface)',
'neutral-soft-fg': 'var(--color-neutral-text)',
'neutral-outline-border': 'var(--color-neutral-border)',
'neutral-outline-fg': 'var(--color-neutral-text)',
'affirm-solid-bg': 'var(--color-affirm-solid)',
'affirm-solid-fg': 'var(--color-affirm-contrast)',
'affirm-soft-bg': 'var(--color-affirm-surface)',
'affirm-soft-fg': 'var(--color-affirm-text)',
'affirm-outline-border': 'var(--color-affirm-border)',
'affirm-outline-fg': 'var(--color-affirm-text)',
'fulfill-solid-bg': 'var(--color-fulfill-solid)',
'fulfill-solid-fg': 'var(--color-fulfill-contrast)',
'fulfill-soft-bg': 'var(--color-fulfill-surface)',
'fulfill-soft-fg': 'var(--color-fulfill-text)',
'fulfill-outline-border': 'var(--color-fulfill-border)',
'fulfill-outline-fg': 'var(--color-fulfill-text)',
'risk-solid-bg': 'var(--color-risk-solid)',
'risk-solid-fg': 'var(--color-risk-contrast)',
'risk-soft-bg': 'var(--color-risk-surface)',
'risk-soft-fg': 'var(--color-risk-text)',
'risk-outline-border': 'var(--color-risk-border)',
'risk-outline-fg': 'var(--color-risk-text)',
'threat-solid-bg': 'var(--color-threat-solid)',
'threat-solid-fg': 'var(--color-threat-contrast)',
'threat-soft-bg': 'var(--color-threat-surface)',
'threat-soft-fg': 'var(--color-threat-text)',
'threat-outline-border': 'var(--color-threat-border)',
'threat-outline-fg': 'var(--color-threat-text)',
'loss-solid-bg': 'var(--color-loss-solid)',
'loss-solid-fg': 'var(--color-loss-contrast)',
'loss-soft-bg': 'var(--color-loss-surface)',
'loss-soft-fg': 'var(--color-loss-text)',
'loss-outline-border': 'var(--color-loss-border)',
'loss-outline-fg': 'var(--color-loss-text)',
// ── Ring (outline halo around the avatar) ────────────────────────
'ring-width-sm': '1.5px',
'ring-width-md': '2px',
'ring-width-lg': '3px',
// ── Custom color (consumer-provided CSS color string) ────────────
// Set inline by the Svelte file via `style="--avatar-color-custom: …"`.
// Declared here so the recipe-css-contract test sees a declaration.
'color-custom': 'currentColor',
'color-custom-contrast': 'white',
'ring-color-custom': 'currentColor',
// ── Badge ─────────────────────────────────────────────────────────
'badge-border-width': 'var(--border-width-medium)',
'badge-color-custom': 'currentColor',
'badge-color-custom-contrast': 'white',
// ── AvatarGroup ──────────────────────────────────────────────────
'group-gap-xs': 'var(--space-1)',
'group-gap-sm': 'var(--space-2)',
'group-gap-md': 'var(--space-3)',
'group-gap-lg': 'var(--space-4)',
'group-overlap': '-0.6em',
'group-carve-width': 'var(--border-width-medium)',
'group-carve-color': 'var(--color-surface-default)',
'group-reverse-z-max': '20',
'group-max': '99',
'overflow-font-weight': 'var(--font-weight-medium)',
// ── TSC v2.2 — composite (variant × color) cascade ────────────────
// 8 colors × 3 variants = 24 composite scopes per slot, applied
// to the root for {_bg, _fg, _border} and to the badge part for
// {_badge-bg, _badge-fg, _badge-border} (matrix helper above
// generates the declarations[] from the slot pickers).
//
// Defaults at `host` match the legacy CSS base rule (avatar:
// neutral-soft; badge: affirm-solid). Outline is the only variant
// that touches `_border` / `_badge-border`; solid+soft keep the
// transparent / surface-default defaults.
_bg: {
declarations: [
{ value: 'var(--avatar-neutral-soft-bg)', scope: 'host' },
...matrix((color, variant) =>
variant === 'outline' ? 'transparent' : `var(--avatar-${color}-${variant}-bg)`
).declarations
]
},
_fg: {
declarations: [
{ value: 'var(--avatar-neutral-soft-fg)', scope: 'host' },
...matrix((color, variant) => `var(--avatar-${color}-${variant}-fg)`).declarations
]
},
_border: {
declarations: [
{ value: 'transparent', scope: 'host' },
...matrix((color, variant) =>
variant === 'outline' ? `var(--avatar-${color}-outline-border)` : undefined
).declarations
]
},
'_badge-bg': {
parts: ['badge'],
declarations: [
{ value: 'var(--avatar-affirm-solid-bg)', scope: 'host' },
...matrix((color, variant) =>
variant === 'outline'
? 'var(--color-surface-default)'
: `var(--avatar-${color}-${variant}-bg)`
).declarations
]
},
'_badge-fg': {
parts: ['badge'],
declarations: [
{ value: 'var(--avatar-affirm-solid-fg)', scope: 'host' },
...matrix((color, variant) => `var(--avatar-${color}-${variant}-fg)`).declarations
]
},
'_badge-border': {
parts: ['badge'],
declarations: [
{ value: 'var(--color-surface-default)', scope: 'host' },
...matrix((color, variant) =>
variant === 'outline' ? `var(--avatar-${color}-outline-border)` : undefined
).declarations
]
}
};
})(),
badge: {
// ── Padding scale ────────────────────────────────────────────────
'px-xs': 'var(--space-1-5)',
'px-sm': 'var(--space-2)',
'px-md': 'var(--space-2-5)',
'px-lg': 'var(--space-3)',
'py-xs': 'var(--space-0-5)',
'py-sm': 'var(--space-0-5)',
'py-md': 'var(--space-1)',
'py-lg': 'var(--space-1-5)',
// ── Gap between dot / icon / label / remove ───────────────────────
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
// ── Typography ────────────────────────────────────────────────────
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-weight': 'var(--font-weight-medium)',
'line-height': 'var(--leading-ui)',
'letter-spacing': '0.01em',
// ── Borders + radii ───────────────────────────────────────────────
'border-width': 'var(--border-width)',
'radius-sm': 'var(--radius-sm)',
'radius-md': 'var(--radius-md)',
'radius-lg': 'var(--radius-lg)',
'radius-full': 'var(--radius-full)',
// ── Inner slots ───────────────────────────────────────────────────
// Use em so internal parts scale with the badge's resolved font-size.
'dot-size': '0.5em',
'icon-size': '1em',
'remove-size': '1.25em',
'remove-margin-start': '0.125em',
// ── Color palette (mirrors the canonical 8-color matrix) ──────────
'primary-solid': 'var(--color-primary-solid)',
'primary-text': 'var(--color-primary-text)',
'primary-contrast': 'var(--color-primary-contrast)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-text': 'var(--color-secondary-text)',
'secondary-contrast': 'var(--color-secondary-contrast)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-text': 'var(--color-neutral-text)',
'neutral-contrast': 'var(--color-neutral-contrast)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-text': 'var(--color-affirm-text)',
'affirm-contrast': 'var(--color-affirm-contrast)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-text': 'var(--color-fulfill-text)',
'fulfill-contrast': 'var(--color-fulfill-contrast)',
'risk-solid': 'var(--color-risk-solid)',
'risk-text': 'var(--color-risk-text)',
'risk-contrast': 'var(--color-risk-contrast)',
'threat-solid': 'var(--color-threat-solid)',
'threat-text': 'var(--color-threat-text)',
'threat-contrast': 'var(--color-threat-contrast)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-solid': 'var(--color-loss-solid)',
'loss-text': 'var(--color-loss-text)',
'loss-contrast': 'var(--color-loss-contrast)',
// Restored after accidental sed-removal during switch migration.
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)',
// Private palette (TSC v2.1). 8 colors, 5 slots.
_track: {
declarations: [
{ value: 'var(--badge-primary-track)', scope: 'host' },
{ value: 'var(--badge-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--badge-neutral-track)', scope: 'color:neutral' },
{ value: 'var(--badge-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--badge-fulfill-track)', scope: 'color:fulfill' },
{ value: 'var(--badge-risk-track)', scope: 'color:risk' },
{ value: 'var(--badge-threat-track)', scope: 'color:threat' },
{ value: 'var(--badge-loss-track)', scope: 'color:loss' }
]
},
_border: {
declarations: [
{ value: 'var(--badge-primary-border)', scope: 'host' },
{ value: 'var(--badge-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--badge-neutral-border)', scope: 'color:neutral' },
{ value: 'var(--badge-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--badge-fulfill-border)', scope: 'color:fulfill' },
{ value: 'var(--badge-risk-border)', scope: 'color:risk' },
{ value: 'var(--badge-threat-border)', scope: 'color:threat' },
{ value: 'var(--badge-loss-border)', scope: 'color:loss' }
]
},
_solid: {
declarations: [
{ value: 'var(--badge-primary-solid)', scope: 'host' },
{ value: 'var(--badge-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--badge-neutral-solid)', scope: 'color:neutral' },
{ value: 'var(--badge-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--badge-fulfill-solid)', scope: 'color:fulfill' },
{ value: 'var(--badge-risk-solid)', scope: 'color:risk' },
{ value: 'var(--badge-threat-solid)', scope: 'color:threat' },
{ value: 'var(--badge-loss-solid)', scope: 'color:loss' }
]
},
_text: {
declarations: [
{ value: 'var(--badge-primary-text)', scope: 'host' },
{ value: 'var(--badge-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--badge-neutral-text)', scope: 'color:neutral' },
{ value: 'var(--badge-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--badge-fulfill-text)', scope: 'color:fulfill' },
{ value: 'var(--badge-risk-text)', scope: 'color:risk' },
{ value: 'var(--badge-threat-text)', scope: 'color:threat' },
{ value: 'var(--badge-loss-text)', scope: 'color:loss' }
]
},
_contrast: {
declarations: [
{ value: 'var(--badge-primary-contrast)', scope: 'host' },
{ value: 'var(--badge-secondary-contrast)', scope: 'color:secondary' },
{ value: 'var(--badge-neutral-contrast)', scope: 'color:neutral' },
{ value: 'var(--badge-affirm-contrast)', scope: 'color:affirm' },
{ value: 'var(--badge-fulfill-contrast)', scope: 'color:fulfill' },
{ value: 'var(--badge-risk-contrast)', scope: 'color:risk' },
{ value: 'var(--badge-threat-contrast)', scope: 'color:threat' },
{ value: 'var(--badge-loss-contrast)', scope: 'color:loss' }
]
}
},
breadcrumb: {
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'line-height': 'var(--leading-ui)',
color: 'var(--color-content-secondary)',
'link-radius': 'var(--radius-sm)',
'link-color': 'var(--color-content-secondary)',
'link-color-hover': 'var(--color-content-primary)',
'link-color-current': 'var(--color-content-primary)',
'link-font-weight': 'var(--font-weight-medium)',
'link-font-weight-current': 'var(--font-weight-semibold)',
'separator-color': 'var(--color-content-muted)',
'ellipsis-size': 'var(--control-height-xs)',
'ellipsis-radius': 'var(--radius-md)',
'ellipsis-border-width': 'var(--border-width)',
'ellipsis-border': 'transparent',
'ellipsis-border-hover': 'var(--color-border-subtle)',
'ellipsis-bg': 'transparent',
'ellipsis-bg-hover': 'var(--color-surface-raised)',
'ellipsis-color': 'var(--color-content-muted)',
'ellipsis-color-hover': 'var(--color-content-primary)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)'
},
card: {
// ── Padding scale ────────────────────────────────────────────────
'px-xs': 'var(--space-3)',
'px-sm': 'var(--space-4)',
'px-md': 'var(--space-5)',
'px-lg': 'var(--space-6)',
'px-xl': 'var(--space-8)',
'py-xs': 'var(--space-3)',
'py-sm': 'var(--space-4)',
'py-md': 'var(--space-5)',
'py-lg': 'var(--space-6)',
'py-xl': 'var(--space-8)',
// ── Gap between header / body / footer slots ─────────────────────
'gap-xs': 'var(--space-2)',
'gap-sm': 'var(--space-3)',
'gap-md': 'var(--space-4)',
'gap-lg': 'var(--space-5)',
'gap-xl': 'var(--space-6)',
// ── Typography ────────────────────────────────────────────────────
'font-family': 'var(--font-ui)',
'line-height': 'var(--leading-ui)',
'title-font-family': 'var(--font-heading)',
'title-font-size-xs': 'var(--font-size-sm)',
'title-font-size-sm': 'var(--font-size-md)',
'title-font-size-md': 'var(--font-size-lg)',
'title-font-size-lg': 'var(--font-size-xl)',
'title-font-size-xl': 'var(--font-size-2xl)',
'title-font-weight': 'var(--font-weight-semibold)',
'title-line-height': 'var(--leading-heading)',
'title-letter-spacing': 'var(--tracking-tight)',
'description-font-family': 'var(--font-ui)',
'description-font-size-xs': 'var(--font-size-xs)',
'description-font-size-sm': 'var(--font-size-sm)',
'description-font-size-md': 'var(--font-size-sm)',
'description-font-size-lg': 'var(--font-size-md)',
'description-font-size-xl': 'var(--font-size-md)',
'description-line-height': 'var(--leading-prose)',
'description-color': 'var(--color-content-secondary)',
'body-font-size-xs': 'var(--font-size-sm)',
'body-font-size-sm': 'var(--font-size-sm)',
'body-font-size-md': 'var(--font-size-md)',
'body-font-size-lg': 'var(--font-size-md)',
'body-font-size-xl': 'var(--font-size-lg)',
'body-line-height': 'var(--leading-prose)',
// ── Borders + radii ───────────────────────────────────────────────
'border-width': 'var(--border-width)',
'radius-sm': 'var(--radius-md)',
'radius-md': 'var(--radius-lg)',
'radius-lg': 'var(--radius-xl)',
// ── Inner slot spacing ────────────────────────────────────────────
'header-gap': 'var(--space-1-5)',
'footer-gap': 'var(--space-3)',
'footer-padding-start': 'var(--space-3)',
'footer-border-color': 'var(--color-border-subtle)',
// ── Animations ────────────────────────────────────────────────────
'emerge-duration': 'var(--duration-slow)',
'emerge-ease': 'var(--ease-out)',
'emerge-distance': '8px',
'hover-lift': '-2px',
'hover-shadow': 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'press-scale': '0.985',
'press-duration': '80ms',
'selected-ring-width': 'var(--border-width-medium)',
'disabled-opacity': 'var(--opacity-disabled)',
// ── Custom color (set inline by consumer via style) ───────────────
// The Svelte file emits `style="--card-color-custom: <value>;"`
// when the consumer passes a non-canonical CSS color. Declared
// here so the recipe-css-contract test sees a declaration.
'color-custom': 'currentColor',
'color-custom-contrast': 'white',
// ── Color palette (canonical 8-color × 5 slots) ──────────────────
'primary-solid': 'var(--color-primary-solid)',
'primary-text': 'var(--color-primary-text)',
'primary-contrast': 'var(--color-primary-contrast)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-text': 'var(--color-secondary-text)',
'secondary-contrast': 'var(--color-secondary-contrast)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-text': 'var(--color-neutral-text)',
'neutral-contrast': 'var(--color-neutral-contrast)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-text': 'var(--color-affirm-text)',
'affirm-contrast': 'var(--color-affirm-contrast)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-text': 'var(--color-fulfill-text)',
'fulfill-contrast': 'var(--color-fulfill-contrast)',
'risk-solid': 'var(--color-risk-solid)',
'risk-text': 'var(--color-risk-text)',
'risk-contrast': 'var(--color-risk-contrast)',
'threat-solid': 'var(--color-threat-solid)',
'threat-text': 'var(--color-threat-text)',
'threat-contrast': 'var(--color-threat-contrast)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-solid': 'var(--color-loss-solid)',
'loss-text': 'var(--color-loss-text)',
'loss-contrast': 'var(--color-loss-contrast)',
// Restored after accidental sed-removal during switch migration.
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)',
// Private palette (TSC v2.1). 8 colors, 5 slots. Default = neutral.
_track: {
declarations: [
{ value: 'var(--card-neutral-track)', scope: 'host' },
{ value: 'var(--card-primary-track)', scope: 'color:primary' },
{ value: 'var(--card-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--card-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--card-fulfill-track)', scope: 'color:fulfill' },
{ value: 'var(--card-risk-track)', scope: 'color:risk' },
{ value: 'var(--card-threat-track)', scope: 'color:threat' },
{ value: 'var(--card-loss-track)', scope: 'color:loss' }
]
},
_border: {
declarations: [
{ value: 'var(--card-neutral-border)', scope: 'host' },
{ value: 'var(--card-primary-border)', scope: 'color:primary' },
{ value: 'var(--card-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--card-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--card-fulfill-border)', scope: 'color:fulfill' },
{ value: 'var(--card-risk-border)', scope: 'color:risk' },
{ value: 'var(--card-threat-border)', scope: 'color:threat' },
{ value: 'var(--card-loss-border)', scope: 'color:loss' }
]
},
_solid: {
declarations: [
{ value: 'var(--card-neutral-solid)', scope: 'host' },
{ value: 'var(--card-primary-solid)', scope: 'color:primary' },
{ value: 'var(--card-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--card-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--card-fulfill-solid)', scope: 'color:fulfill' },
{ value: 'var(--card-risk-solid)', scope: 'color:risk' },
{ value: 'var(--card-threat-solid)', scope: 'color:threat' },
{ value: 'var(--card-loss-solid)', scope: 'color:loss' }
]
},
_text: {
declarations: [
{ value: 'var(--card-neutral-text)', scope: 'host' },
{ value: 'var(--card-primary-text)', scope: 'color:primary' },
{ value: 'var(--card-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--card-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--card-fulfill-text)', scope: 'color:fulfill' },
{ value: 'var(--card-risk-text)', scope: 'color:risk' },
{ value: 'var(--card-threat-text)', scope: 'color:threat' },
{ value: 'var(--card-loss-text)', scope: 'color:loss' }
]
},
_contrast: {
declarations: [
{ value: 'var(--card-neutral-contrast)', scope: 'host' },
{ value: 'var(--card-primary-contrast)', scope: 'color:primary' },
{ value: 'var(--card-secondary-contrast)', scope: 'color:secondary' },
{ value: 'var(--card-affirm-contrast)', scope: 'color:affirm' },
{ value: 'var(--card-fulfill-contrast)', scope: 'color:fulfill' },
{ value: 'var(--card-risk-contrast)', scope: 'color:risk' },
{ value: 'var(--card-threat-contrast)', scope: 'color:threat' },
{ value: 'var(--card-loss-contrast)', scope: 'color:loss' }
]
}
},
checkbox: {
'size-xs-box-size': '14px',
'size-xs-indicator-size': '8px',
'size-xs-indicator-stroke': '2px',
'size-sm-box-size': '16px',
'size-sm-indicator-size': '10px',
'size-sm-indicator-stroke': '2px',
'size-md-box-size': '18px',
'size-md-indicator-size': '12px',
'size-md-indicator-stroke': '2px',
'size-lg-box-size': '22px',
'size-lg-indicator-size': '14px',
'size-lg-indicator-stroke': '2px',
'size-xl-box-size': '26px',
'size-xl-indicator-size': '16px',
'size-xl-indicator-stroke': '2px',
radius: 'var(--radius-sm)',
'border-width': 'var(--border-width)',
'focus-ring-offset': 'var(--focus-ring-offset)',
'focus-ring-width': 'var(--focus-ring-width)',
'focus-ring-color': 'var(--focus-ring-color)',
'focus-ring-surface': 'var(--color-surface-default)',
'focus-ring':
'0 0 0 var(--checkbox-focus-ring-offset) var(--checkbox-focus-ring-surface), 0 0 0 calc(var(--checkbox-focus-ring-offset) + var(--checkbox-focus-ring-width)) var(--checkbox-focus-ring-color)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
// Checkmark stroke draw — synced with the box transition (--duration-fast).
// Was a hardcoded 220ms, which read as laggy next to the 120ms box fill.
'stroke-duration': 'var(--duration-fast)',
'stroke-ease': 'cubic-bezier(0.4, 0, 0.2, 1)',
'bg-off': 'var(--color-surface-default)',
'bg-off-hover':
'color-mix( in srgb, var(--color-surface-default) 82%, var(--color-surface-overlay) )',
'border-off': 'var(--color-border-default)',
'border-off-hover': 'var(--color-border-strong)',
'bg-disabled':
'color-mix( in srgb, var(--color-surface-overlay) 78%, var(--color-surface-default) )',
'border-disabled':
'color-mix( in srgb, var(--color-border-default) 72%, var(--color-border-subtle) )',
'disabled-opacity': 'var(--opacity-disabled)',
'indicator-color': 'var(--color-content-on-solid)',
'indicator-color-disabled':
'color-mix( in srgb, var(--color-content-on-solid) 82%, var(--color-surface-default) )',
'primary-solid': 'var(--color-primary-solid)',
'primary-solid-hover': 'var(--color-primary-solid-hover)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-solid-hover': 'var(--color-neutral-solid-hover)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-solid-hover': 'var(--color-secondary-solid-hover)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-solid-hover': 'var(--color-affirm-solid-hover)',
// Private palette (TSC v2.1 `_` prefix → emits `--_checkbox-palette-*`).
// Recipe-internal cascade: host default = primary, plus overrides
// for the 3 supported color values. Migrated from manual blocks
// in checkbox.css.
'_palette-solid': {
declarations: [
{ value: 'var(--checkbox-primary-solid)', scope: 'host' },
{ value: 'var(--checkbox-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--checkbox-neutral-solid)', scope: 'color:neutral' },
{ value: 'var(--checkbox-affirm-solid)', scope: 'color:affirm' }
]
},
'_palette-solid-hover': {
declarations: [
{ value: 'var(--checkbox-primary-solid-hover)', scope: 'host' },
{ value: 'var(--checkbox-secondary-solid-hover)', scope: 'color:secondary' },
{ value: 'var(--checkbox-neutral-solid-hover)', scope: 'color:neutral' },
{ value: 'var(--checkbox-affirm-solid-hover)', scope: 'color:affirm' }
]
},
'_palette-text': { value: 'var(--checkbox-indicator-color)', scope: 'host' },
'group-gap': 'var(--space-3)',
'group-label-font-family': 'var(--font-ui)',
'group-label-font-size': 'var(--font-size-sm)',
'group-label-font-weight': 'var(--font-weight-medium)',
'group-label-line-height': 'var(--leading-ui)',
'group-label-color': 'var(--color-content-primary)',
'group-label-color-disabled': 'var(--color-content-secondary)'
},
collapsible: {
'trigger-font-family': 'var(--font-ui)',
'trigger-font-size': 'var(--font-size-sm)',
'trigger-font-weight': 'var(--font-weight-medium)',
'trigger-color': 'var(--color-content-primary)',
'trigger-color-hover': 'var(--color-content-primary)',
'trigger-bg-hover': 'var(--color-surface-overlay)',
'trigger-radius': 'var(--radius-sm)',
'trigger-padding': 'var(--space-2) var(--space-3)',
'content-padding': 'var(--space-3)',
'transition-duration': 'var(--duration-normal)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
dialog: {
'trigger-font-size': 'var(--font-size-sm)',
'overlay-bg': 'var(--color-overlay)',
'overlay-opacity': '62%',
'overlay-blur': 'var(--blur-lg)',
'overlay-z': '70',
'content-bg': 'var(--color-surface-overlay)',
'content-color': 'var(--color-content-primary)',
'content-border': 'var(--color-border-default)',
'content-border-width': 'var(--border-width)',
'content-radius': 'var(--radius-xl)',
'content-shadow': 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'content-max-height': 'calc(100dvh - var(--space-8))',
'content-max-height-sheet': '85dvh',
'content-width-sm': '420px',
'content-width-md': '560px',
'content-width-lg': '760px',
'content-width-xl': '1024px',
'content-width-inset': 'var(--space-6)',
'content-full-inset': 'var(--space-3)',
'content-full-radius': 'var(--radius-xxl)',
'content-position-inset': 'var(--space-4)',
'content-padding-sm': 'var(--space-4)',
'content-padding-md': 'var(--space-5)',
'content-padding-lg': 'var(--space-6)',
'content-padding-full': 'var(--space-6)',
'stack-gap': 'var(--space-4)',
'header-gap': 'var(--space-3)',
'footer-gap': 'var(--space-3)',
'footer-margin-top': 'var(--space-2)',
'title-font-family': 'var(--font-heading)',
'title-font-size': 'var(--font-size-lg)',
'title-font-weight': 'var(--font-weight-semibold)',
'title-line-height': 'var(--leading-heading)',
'title-color': 'var(--color-content-primary)',
'description-font-family': 'var(--font-ui)',
'description-font-size': 'var(--font-size-sm)',
'description-line-height': 'var(--leading-ui)',
'description-color': 'var(--color-content-secondary)',
'close-size': 'var(--control-height-sm)',
'close-px': 'var(--space-2)',
'close-radius': 'var(--radius-md)',
'close-inset': 'var(--space-3)',
'close-bg': 'transparent',
'close-bg-hover': 'color-mix(in srgb, var(--color-neutral-track) 72%, transparent)',
'close-color': 'var(--color-content-secondary)',
'close-color-hover': 'var(--color-content-primary)',
'close-border': 'transparent',
'close-border-width': 'var(--border-width)',
'close-transition-duration': 'var(--duration-fast)',
'close-transition-ease': 'var(--ease-default)',
'close-focus-ring-offset': '1px',
'close-focus-ring-width': 'var(--focus-ring-width)',
'close-focus-ring-color': 'var(--focus-ring-color)'
},
field: {
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1-5)',
'gap-md': 'var(--space-2)',
'gap-lg': 'var(--space-2-5)',
'gap-xl': 'var(--space-3)',
'font-family': 'var(--font-ui)',
'label-gap': 'var(--space-1)',
// Label size is derived in field.css (control font − one step), not a
// per-size token — keeps it coherent with the segmented fields' calc.
'label-font-weight': 'var(--font-weight-regular)',
'label-line-height': 'var(--leading-ui)',
'label-color': 'var(--color-content-primary)',
'label-color-invalid': 'var(--color-risk-text)',
'label-color-disabled': 'var(--color-content-disabled)',
'layout-column-gap': 'var(--space-4)',
'required-color': 'var(--color-risk-text)',
'control-height-xs': 'var(--control-height-xs)',
'control-height-sm': 'var(--control-height-sm)',
'control-height-md': 'var(--control-height-md)',
'control-height-lg': 'var(--control-height-lg)',
'control-height-xl': 'var(--control-height-xl)',
'control-px-xs': 'var(--space-2)',
'control-px-sm': 'var(--space-2-5)',
'control-px-md': 'var(--space-3)',
'control-px-lg': 'var(--space-3-5)',
'control-px-xl': 'var(--space-4)',
'control-gap-xs': 'var(--space-1)',
'control-gap-sm': 'var(--space-1-5)',
'control-gap-md': 'var(--space-2)',
'control-gap-lg': 'var(--space-2-5)',
'control-gap-xl': 'var(--space-3)',
'control-font-size-xs': 'var(--font-size-xs)',
'control-font-size-sm': 'var(--font-size-sm)',
'control-font-size-md': 'var(--font-size-md)',
'control-font-size-lg': 'var(--font-size-lg)',
'control-font-size-xl': 'var(--font-size-xl)',
'control-line-height': 'var(--leading-ui)',
'control-radius': 'var(--radius-md)',
'control-border-width': 'var(--border-width)',
'control-border': 'var(--color-border-default)',
'control-border-hover': 'var(--color-border-strong)',
'control-border-focus': 'var(--color-focus-ring)',
'control-border-invalid': 'var(--color-risk-border)',
'control-bg': 'var(--color-surface-default)',
'control-ghost-border': 'transparent',
'control-ghost-bg': 'transparent',
'control-bg-readonly': 'var(--color-surface-muted)',
'control-bg-disabled': 'var(--color-surface-muted)',
'control-color': 'var(--color-content-primary)',
'control-placeholder-color': 'var(--color-content-muted)',
'control-focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'affix-color': 'var(--color-content-secondary)',
'message-line-height': 'var(--leading-ui)',
'helper-color': 'var(--color-content-secondary)',
'error-color': 'var(--color-risk-text)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
form: {
'gap-xs': 'var(--space-3)',
'gap-sm': 'var(--space-4)',
'gap-md': 'var(--space-5)',
'gap-lg': 'var(--space-6)',
'gap-xl': 'var(--space-7)',
'font-family': 'var(--font-ui)',
color: 'var(--color-content-primary)',
radius: 'var(--radius-lg)',
'border-width': 'var(--border-width)',
'panel-padding': 'var(--space-5)',
'panel-border': 'var(--color-border-default)',
'panel-bg': 'var(--color-surface-raised)',
'panel-shadow': 'var(--depth-raised-shadow), var(--depth-raised-halo)',
'grid-min': '18rem',
'grid-column-gap': 'var(--space-5)',
'action-height-xs': 'var(--control-height-xs)',
'action-height-sm': 'var(--control-height-sm)',
'action-height-md': 'var(--control-height-md)',
'action-height-lg': 'var(--control-height-lg)',
'action-height-xl': 'var(--control-height-xl)',
'action-px-xs': 'var(--space-2-5)',
'action-px-sm': 'var(--space-3)',
'action-px-md': 'var(--space-3-5)',
'action-px-lg': 'var(--space-4)',
'action-px-xl': 'var(--space-4-5)',
'action-font-size-xs': 'var(--font-size-xs)',
'action-font-size-sm': 'var(--font-size-xs)',
'action-font-size-md': 'var(--font-size-sm)',
'action-font-size-lg': 'var(--font-size-sm)',
'action-font-size-xl': 'var(--font-size-md)',
'action-font-weight': 'var(--font-weight-medium)',
'action-line-height': 'var(--leading-ui)',
'action-gap': 'var(--space-2)',
'action-radius': 'var(--radius-md)',
'action-border-width': 'var(--border-width)',
'error-summary-padding': 'var(--space-3)',
'error-summary-border-width': 'var(--border-width)',
'error-summary-radius': 'var(--radius-md)',
'error-summary-font-size': 'var(--font-size-sm)',
'error-summary-line-height': 'var(--leading-ui)',
'error-summary-list-indent': 'var(--space-4)',
'link-underline-offset': '0.18em',
'auto-fields-gap': 'var(--space-3)',
'auto-fields-array-padding': 'var(--space-3)',
'auto-fields-border-width': 'var(--border-width)',
'auto-fields-radius': 'var(--radius-md)',
'auto-fields-item-gap': 'var(--space-2)',
'auto-fields-item-padding': 'var(--space-3)',
'widget-px': 'var(--space-3)',
'widget-radius': 'var(--radius-md)',
'widget-border-width': 'var(--border-width)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
drawer: {
'overlay-bg': 'var(--color-overlay)',
'overlay-blur': 'var(--blur-lg)',
'overlay-opacity': '62%',
'overlay-z': '72',
'inline-z': '64',
'handle-bg': 'var(--color-neutral-track)',
'handle-bg-active': 'var(--color-content-secondary)',
'handle-radius': 'var(--radius-full)',
'handle-length': '36px',
'handle-thickness': '4px',
'handle-active-scale': '1.18',
'handle-transition-duration': 'var(--duration-fast)',
'handle-transition-ease': 'var(--ease-out)',
'content-bg': 'var(--color-surface-overlay)',
'content-color': 'var(--color-content-primary)',
'content-border': 'var(--color-border-default)',
'content-border-width': 'var(--border-width)',
'content-radius': 'var(--radius-xl)',
'content-shadow': 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'content-shadow-dragging':
'var(--shadow-overlay-strong, var(--depth-overlay-shadow)), var(--depth-overlay-halo)',
'content-ring-dragging-width': 'var(--border-width-medium)',
'content-ring-dragging-color':
'color-mix( in srgb, var(--color-content-secondary) 32%, transparent )',
'content-ring-dragging-offset': '2px',
'content-inset': 'var(--space-3)',
'content-width-sm': '360px',
'content-width-md': '440px',
'content-width-lg': '560px',
'content-height-sm': '260px',
'content-height-md': '360px',
'content-height-lg': '480px',
'content-padding-sm': 'var(--space-4)',
'content-padding-md': 'var(--space-5)',
'content-padding-lg': 'var(--space-6)',
'content-padding-full': 'var(--space-6)',
'stack-gap': 'var(--space-4)',
'header-gap': 'var(--space-3)',
'footer-gap': 'var(--space-3)',
'footer-margin-top': 'var(--space-2)',
'title-font-family': 'var(--font-heading)',
'title-font-size': 'var(--font-size-lg)',
'title-font-weight': 'var(--font-weight-semibold)',
'title-line-height': 'var(--leading-heading)',
'title-color': 'var(--color-content-primary)',
'description-font-family': 'var(--font-ui)',
'description-font-size': 'var(--font-size-sm)',
'description-line-height': 'var(--leading-ui)',
'description-color': 'var(--color-content-secondary)',
'close-size': 'var(--control-height-sm)',
'close-px': 'var(--space-2)',
'close-radius': 'var(--radius-md)',
'close-bg': 'transparent',
'close-bg-hover': 'color-mix(in srgb, var(--color-neutral-track) 72%, transparent)',
'close-color': 'var(--color-content-secondary)',
'close-color-hover': 'var(--color-content-primary)',
'close-border': 'transparent',
'close-border-width': 'var(--border-width)',
'close-transition-duration': 'var(--duration-fast)',
'close-transition-ease': 'var(--ease-default)',
'close-focus-ring-offset': '1px',
'close-focus-ring-width': 'var(--focus-ring-width)',
'close-focus-ring-color': 'var(--focus-ring-color)',
'close-disabled-opacity': '0.5'
},
icon: {
size: 'var(--icon-size-md)',
'stroke-width': 'var(--icon-stroke-width-md)'
},
image: {
// ── Size presets (square boxes) ──────────────────────────────────
'size-xs': '64px',
'size-sm': '96px',
'size-md': '160px',
'size-lg': '240px',
'size-xl': '360px',
// ── Radius scale ──────────────────────────────────────────────────
'radius-none': '0',
'radius-sm': 'var(--radius-sm)',
'radius-md': 'var(--radius-md)',
'radius-lg': 'var(--radius-lg)',
'radius-full': 'var(--radius-full)',
// ── Fade-in transition for the inner <img> ───────────────────────
'fade-duration': 'var(--duration-normal)',
'fade-ease': 'var(--ease-out)',
// ── Skeleton placeholder shimmer ──────────────────────────────────
'placeholder-base': 'var(--color-surface-raised)',
'shimmer-duration': '1.8s',
// ── Custom placeholder color (set inline by Svelte file) ─────────
'placeholder-color-custom': 'var(--color-neutral-track)',
// ── Fallback typography (for text-only fallback content) ─────────
'font-family': 'var(--font-ui)',
'fallback-color': 'var(--color-content-secondary)',
'fallback-font-size': 'var(--font-size-sm)',
'fallback-line-height': 'var(--leading-ui)',
// ── Error state ──────────────────────────────────────────────────
'error-bg': 'var(--color-surface-raised)',
'error-color': 'var(--color-content-muted)',
'error-font-size': 'var(--font-size-sm)',
'error-line-height': 'var(--leading-ui)'
},
meter: {
'height-xs': '2px',
'height-sm': '4px',
'height-md': '6px',
'height-lg': '8px',
'height-xl': '12px',
'radius-xs': 'var(--radius-full)',
'radius-sm': 'var(--radius-full)',
'radius-md': 'var(--radius-full)',
'radius-lg': 'var(--radius-full)',
'radius-xl': 'var(--radius-full)',
'ring-size-xs': '2rem',
'ring-size-sm': '2.5rem',
'ring-size-md': '3rem',
'ring-size-lg': '3.5rem',
'ring-size-xl': '4rem',
'ring-thickness-xs': '3px',
'ring-thickness-sm': '4px',
'ring-thickness-md': '6px',
'ring-thickness-lg': '8px',
'ring-thickness-xl': '10px',
'track-bg': 'var(--color-neutral-track)',
'ring-track-bg':
'color-mix(in srgb, var(--color-content-primary) 12%, var(--color-surface-default))',
'ring-center-bg': 'var(--color-surface-default)',
'indicator-bg-below': 'var(--color-risk-solid)',
'indicator-bg-optimum': 'var(--color-fulfill-solid)',
'indicator-bg-above': 'var(--color-threat-solid)',
'transition-duration': 'var(--duration-normal)',
'transition-ease': 'var(--ease-default)'
},
// spin-field — shared visual for the stepper-field family (number-field +
// css-field). Both reuse these tokens via structural identity (`data-spin-
// field*`) and the shared `spin-field.css`. A theme tints per component by
// scoping `[data-number-field] { --spin-field-… }`. See THEMING §34.
'spin-field': {
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-2-5)',
'px-md': 'var(--space-3)',
'px-lg': 'var(--space-3-5)',
'px-xl': 'var(--space-4)',
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'gap-xl': 'var(--space-2-5)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'border-focus': 'var(--color-focus-ring)',
'border-invalid': 'var(--color-risk-border)',
bg: 'var(--color-surface-default)',
color: 'var(--color-content-primary)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
// Steppers are square segments flush to the field edge (the field clips
// their outer corners), separated from the input by a divider on the
// inner side.
'control-radius': '0',
'control-border-width': 'var(--border-width)',
'control-border': 'transparent',
'control-divider': 'var(--color-border-default)',
'control-border-hover': 'var(--color-border-strong)',
'control-border-active': 'var(--color-primary-border)',
'control-bg': 'transparent',
// Higher-contrast hover/focus fill than the field surface.
'control-bg-hover': 'var(--color-neutral-hover)',
'control-bg-focus': 'var(--color-neutral-hover)',
'control-bg-active': 'var(--color-primary-track)',
'control-color': 'var(--color-content-secondary)',
'control-color-hover': 'var(--color-content-primary)',
'control-color-active': 'var(--color-primary-text)',
// Default stepper glyphs — themeable, decorative (the buttons carry an
// aria-label). Rendered via `:empty::before`, so passing children to a
// trigger overrides them. Split uses +/−; the stacked column uses up/down
// triangles. A theme retints/reshapes by overriding any of the four.
'control-increment-glyph': "'+'",
'control-decrement-glyph': "'\\2212'",
'control-increment-glyph-stacked': "'\\25B2'",
'control-decrement-glyph-stacked': "'\\25BC'",
'control-focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'control-disabled-opacity': '0.48',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'float-panel': {
bg: 'var(--color-surface-overlay)',
border: 'var(--color-border-default)',
'border-width': 'var(--border-width)',
radius: 'var(--radius-lg)',
shadow: 'var(--shadow-4)',
'shadow-topmost': 'var(--shadow-5)',
// Elevation worn WHILE grabbed (drag/resize): the panel LIFTS to the
// theme's highest elevation. Applied on pointerdown so the lift transition
// finishes before the move; the shadow is then static during the drag, so
// the GPU layer just moves (no per-frame shadow repaint). See float-panel.css.
'shadow-active': 'var(--shadow-6)',
color: 'var(--color-content-primary)',
'font-family': 'var(--font-ui)',
'font-size': 'var(--font-size-sm)',
'line-height': 'var(--leading-ui)',
'header-bg': 'var(--color-surface-raised)',
'header-px': 'var(--space-3)',
'header-py': 'var(--space-2)',
'header-gap': 'var(--space-2)',
'controls-gap': 'var(--space-1)',
'title-font-size': 'var(--font-size-sm)',
'title-font-weight': 'var(--font-weight-semibold)',
'title-color': 'var(--color-content-primary)',
'body-px': 'var(--space-3)',
'body-py': 'var(--space-3)',
'resize-size': 'var(--space-3-5)',
'resize-edge': 'var(--space-1-5)',
// Header icon-button (close / action / stage-trigger) hit target, and the
// se-corner resize grip — sized for comfort, not the tiny resize-handle.
'button-size': 'var(--space-7)',
'grip-size': 'var(--space-5)',
accent: 'var(--color-neutral-border)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)'
},
'image-adjustments': {
// Vertical rhythm: gap between adjustment rows, and within a row
// (label/value line → slider).
gap: 'var(--space-3)',
'row-gap': 'var(--space-1-5)',
'label-color': 'var(--color-content-secondary)',
'label-font-size': 'var(--font-size-sm)',
'label-font-weight': 'var(--font-weight-medium)',
// Read-out: tabular numerals so the value doesn't jitter while dragging.
'value-color': 'var(--color-content-tertiary)',
'value-font-size': 'var(--font-size-xs)',
'value-font-family': 'var(--font-mono)',
'reset-gap': 'var(--space-2)',
'reset-color': 'var(--color-content-secondary)',
'reset-font-size': 'var(--font-size-xs)',
// The `hue` row paints its slider track as a hue wheel so the control
// reads as a hue picker. A literal rainbow (the hues ARE the value) — kept
// as a token so it's themeable and stays out of the component CSS.
'hue-track':
'linear-gradient(to right, hsl(0 85% 60%), hsl(60 85% 60%), hsl(120 85% 60%), hsl(180 85% 60%), hsl(240 85% 60%), hsl(300 85% 60%), hsl(360 85% 60%))'
},
'image-picker': {
// Vertical stack: preview → adjustments.
gap: 'var(--space-3)',
// Preview box (ready state). The composed <Image> fills it.
'preview-radius': 'var(--radius-lg)',
'preview-bg': 'var(--color-surface-muted)',
'preview-border': 'var(--color-border-default)',
'preview-aspect': '4 / 3',
// Toolbar floats over the preview's top-right corner.
'toolbar-gap': 'var(--space-1-5)',
'toolbar-inset': 'var(--space-2)',
// Icon buttons (rotate / remove).
'button-size': 'var(--space-8)',
'button-bg': 'color-mix(in srgb, var(--color-surface-default) 88%, transparent)',
'button-fg': 'var(--color-content-primary)',
'button-border': 'var(--color-border-default)',
'button-radius': 'var(--radius-md)',
'button-font-size': 'var(--font-size-md)'
},
cropper: {
'viewport-bg': 'var(--color-surface-muted)',
'viewport-radius': 'var(--radius-lg)',
// The darken-outside mask + the thirds grid carry literal rgba/rgb (an
// overlay tint, not a theme color) — kept as tokens so they stay out of
// the component CSS (raw-color contract) and remain themeable.
'mask-bg': 'rgb(0 0 0 / 0.55)',
'grid-line': 'rgb(255 255 255 / 0.4)',
'selection-border': 'var(--color-surface-default)',
'selection-border-width': 'var(--border-width)',
'handle-size': 'var(--space-3-5)',
'handle-bg': 'var(--color-surface-default)',
'handle-border': 'var(--color-primary-solid)',
'handle-border-width': 'var(--border-width)',
'zoom-gap': 'var(--space-2)',
'zoom-btn-size': 'var(--space-7)',
'zoom-btn-bg': 'var(--color-surface-muted)',
'zoom-btn-fg': 'var(--color-content-primary)',
'zoom-btn-border': 'var(--color-border-default)'
},
'date-field': {
'stack-gap': 'var(--space-1-5)',
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-2-5)',
'px-md': 'var(--space-3)',
'px-lg': 'var(--space-3-5)',
'px-xl': 'var(--space-4)',
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-0-5)',
'gap-md': 'var(--space-1)',
'gap-lg': 'var(--space-1-5)',
'gap-xl': 'var(--space-2)',
'segment-px-xs': 'var(--space-0-5)',
'segment-px-sm': 'var(--space-0-5)',
'segment-px-md': 'var(--space-1)',
'segment-px-lg': 'var(--space-1-5)',
'segment-px-xl': 'var(--space-2)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'border-invalid': 'var(--color-risk-border)',
bg: 'var(--color-surface-default)',
color: 'var(--color-content-primary)',
'label-color': 'var(--color-content-secondary)',
'label-font-weight': 'var(--font-weight-regular)',
'placeholder-color': 'var(--color-content-muted)',
'literal-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'segment-min-width': '1.5ch',
'segment-height': 'calc(var(--_date-field-height) - var(--space-2))',
'segment-radius': 'var(--radius-sm)',
'segment-color': 'var(--color-content-primary)',
'segment-invalid-color': 'var(--color-risk-text)',
'segment-readonly-bg': 'var(--color-surface-overlay)',
'segment-readonly-color': 'var(--color-content-secondary)',
'segment-readonly-decoration': 'underline dotted var(--color-content-muted)',
'segment-hover-bg': 'color-mix(in srgb, var(--color-content-primary) 6%, transparent)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'time-field': {
'stack-gap': 'var(--space-1-5)',
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-2-5)',
'px-md': 'var(--space-3)',
'px-lg': 'var(--space-3-5)',
'px-xl': 'var(--space-4)',
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-0-5)',
'gap-md': 'var(--space-1)',
'gap-lg': 'var(--space-1-5)',
'gap-xl': 'var(--space-2)',
'segment-px-xs': 'var(--space-0-5)',
'segment-px-sm': 'var(--space-0-5)',
'segment-px-md': 'var(--space-1)',
'segment-px-lg': 'var(--space-1-5)',
'segment-px-xl': 'var(--space-2)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'border-invalid': 'var(--color-risk-border)',
bg: 'var(--color-surface-default)',
color: 'var(--color-content-primary)',
'label-color': 'var(--color-content-secondary)',
'label-font-weight': 'var(--font-weight-regular)',
'placeholder-color': 'var(--color-content-muted)',
'literal-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'segment-min-width': '1.5ch',
'segment-height': 'calc(var(--_time-field-height) - var(--space-2))',
'segment-radius': 'var(--radius-sm)',
'segment-color': 'var(--color-content-primary)',
'segment-invalid-color': 'var(--color-risk-text)',
'segment-readonly-bg': 'var(--color-surface-overlay)',
'segment-readonly-color': 'var(--color-content-secondary)',
'segment-readonly-decoration': 'underline dotted var(--color-content-muted)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'color-field': {
'stack-gap': 'var(--space-1-5)',
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-2-5)',
'px-md': 'var(--space-3)',
'px-lg': 'var(--space-3-5)',
'px-xl': 'var(--space-4)',
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-0-5)',
'gap-md': 'var(--space-1)',
'gap-lg': 'var(--space-1-5)',
'gap-xl': 'var(--space-2)',
'segment-px-xs': 'var(--space-0-5)',
'segment-px-sm': 'var(--space-0-5)',
'segment-px-md': 'var(--space-1)',
'segment-px-lg': 'var(--space-1-5)',
'segment-px-xl': 'var(--space-2)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'border-invalid': 'var(--color-risk-border)',
bg: 'var(--color-surface-default)',
color: 'var(--color-content-primary)',
'label-color': 'var(--color-content-secondary)',
'label-font-weight': 'var(--font-weight-regular)',
'placeholder-color': 'var(--color-content-muted)',
'literal-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'segment-min-width': '1.5ch',
'segment-height': 'calc(var(--_color-field-height) - var(--space-2))',
'segment-radius': 'var(--radius-sm)',
'segment-color': 'var(--color-content-primary)',
'segment-invalid-color': 'var(--color-risk-text)',
'segment-readonly-bg': 'var(--color-surface-overlay)',
'segment-readonly-color': 'var(--color-content-secondary)',
'segment-readonly-decoration': 'underline dotted var(--color-content-muted)',
'segment-focus-shadow': 'inset 0 0 0 var(--border-width) var(--_color-field-accent-border)',
'swatch-size-xs': 'calc(var(--control-height-xs) - var(--space-2))',
'swatch-size-sm': 'calc(var(--control-height-sm) - var(--space-2))',
'swatch-size-md': 'calc(var(--control-height-md) - var(--space-2))',
'swatch-size-lg': 'calc(var(--control-height-lg) - var(--space-2))',
'swatch-size-xl': 'calc(var(--control-height-xl) - var(--space-2))',
'format-select-color': 'var(--color-content-secondary)',
'format-select-bg': 'transparent',
'format-select-radius': 'var(--radius-sm)',
'format-select-px': 'var(--space-1)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'color-picker': {
// Popover content shell
'content-gap': 'var(--space-3)',
'content-padding-sm': 'var(--space-3)',
'content-padding-md': 'var(--space-3-5)',
'content-padding-lg': 'var(--space-4)',
'content-width-sm': '17rem',
'content-width-md': '19.5rem',
'content-width-lg': '22rem',
// Trigger button (compact field-shaped pill with a swatch dot)
'trigger-height-sm': 'var(--control-height-sm)',
'trigger-height-md': 'var(--control-height-md)',
'trigger-height-lg': 'var(--control-height-lg)',
'trigger-px-sm': 'var(--space-2)',
'trigger-px-md': 'var(--space-2-5)',
'trigger-px-lg': 'var(--space-3)',
'trigger-gap': 'var(--space-2)',
'trigger-radius': 'var(--radius-md)',
'trigger-border-width': 'var(--border-width)',
'trigger-border': 'var(--color-border-default)',
'trigger-bg': 'var(--color-surface-default)',
'trigger-color': 'var(--color-content-primary)',
'trigger-hover-bg': 'var(--color-surface-overlay)',
'trigger-hover-border': 'var(--color-border-strong)',
'trigger-swatch-size-sm': 'calc(var(--control-height-sm) - var(--space-2))',
'trigger-swatch-size-md': 'calc(var(--control-height-md) - var(--space-2))',
'trigger-swatch-size-lg': 'calc(var(--control-height-lg) - var(--space-2))',
'trigger-swatch-radius': 'var(--radius-full)',
'trigger-swatch-border': 'var(--color-border-subtle)',
'trigger-focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
// 2D area
'area-height': '11rem',
'area-radius': 'var(--radius-md)',
'area-border-width': 'var(--border-width)',
'area-border': 'var(--color-border-subtle)',
'area-thumb-size': '16px',
'area-thumb-radius': 'var(--radius-full)',
'area-thumb-border-width': 'var(--border-width-medium)',
'area-thumb-border': 'var(--color-surface-default)',
'area-thumb-shadow':
'0 0 0 1px color-mix(in srgb, var(--color-neutral-contrast) 45%, transparent), 0 1px 2px color-mix(in srgb, var(--color-neutral-contrast) 20%, transparent)',
// Channel sliders consume the generic `--slider-*` recipe tokens.
// Only the picker-specific decoration (gradient on track) lives
// in the picker CSS; sizes, shadows and radii come from the
// slider recipe so they stay in lockstep with the standalone
// Slider and the time-picker sliders.
// Transparency checker (alpha background)
'transparency-cell': 'var(--color-content-muted)',
'transparency-cell-alt': 'var(--color-surface-default)',
// Swatches
'swatch-size-sm': '1.25rem',
'swatch-size-md': '1.5rem',
'swatch-size-lg': '1.75rem',
'swatch-radius': 'var(--radius-sm)',
'swatch-border': 'var(--color-border-subtle)',
'swatch-checked-ring': 'var(--color-primary-solid)',
'swatch-gap': 'var(--space-1-5)',
// Eye-dropper button
'eye-dropper-size': 'var(--control-height-sm)',
'eye-dropper-radius': 'var(--radius-md)',
'eye-dropper-bg': 'transparent',
'eye-dropper-color': 'var(--color-content-secondary)',
'eye-dropper-hover-bg': 'var(--color-surface-overlay)',
'eye-dropper-hover-color': 'var(--color-content-primary)',
// Hue spectrum — drives the hue ring + saturation overlay. These
// sit in a sidecar file (color-picker-spectrum.css) so the raw
// hsl literals stay out of the main recipe; declaring them here
// keeps the recipe contract whole.
'hue-0': 'hsl(0, 100%, 50%)',
'hue-60': 'hsl(60, 100%, 50%)',
'hue-120': 'hsl(120, 100%, 50%)',
'hue-180': 'hsl(180, 100%, 50%)',
'hue-240': 'hsl(240, 100%, 50%)',
'hue-300': 'hsl(300, 100%, 50%)',
'hue-360': 'hsl(360, 100%, 50%)',
'saturation-floor': 'hsl(0, 0%, 50%)',
// Misc
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
button: {
// Size tokens
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-3)',
'px-md': 'var(--space-4)',
'px-lg': 'var(--space-5)',
'px-xl': 'var(--space-6)',
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1-5)',
'gap-md': 'var(--space-2)',
'gap-lg': 'var(--space-2-5)',
'gap-xl': 'var(--space-3)',
'font-family': 'var(--font-ui)',
'line-height': 'var(--leading-ui)',
'letter-spacing': 'var(--tracking-normal)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'font-weight-xs': 'var(--font-weight-medium)',
'font-weight-sm': 'var(--font-weight-medium)',
'font-weight-md': 'var(--font-weight-medium)',
'font-weight-lg': 'var(--font-weight-medium)',
'font-weight-xl': 'var(--font-weight-medium)',
// Icon follows the button's font-size 1:1 (canonical icon↔type correlation):
// the font now scales 1:1 with the size (no longer compact), so the icon
// matches at the same step. The parallel icon scale preserves icon ≈ font+2
// in the body sizes (md: font 16 → icon 18). Density-scaled; never hardcoded px.
'icon-size-xs': 'var(--icon-size-xs)',
'icon-size-sm': 'var(--icon-size-sm)',
'icon-size-md': 'var(--icon-size-md)',
'icon-size-lg': 'var(--icon-size-lg)',
'icon-size-xl': 'var(--icon-size-xl)',
'radius-sm': 'var(--radius-sm)',
'radius-md': 'var(--radius-md)',
'radius-lg': 'var(--radius-lg)',
'radius-xl': 'var(--radius-xl)',
'border-width': 'var(--border-width)',
shadow: 'none',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'spinner-duration': '720ms',
'disabled-opacity': 'var(--opacity-disabled)',
// Per-color palettes — 7 slots per color, uniform across all 8: track /
// element / border / solid / solid-hover / text / contrast. soft + surface
// + outline variants read track/border, so every color must define them
// (was only fulfill/loss — the others rendered with no bg/border).
'primary-track': 'var(--color-primary-track)',
'primary-element': 'var(--color-primary-element)',
'primary-border': 'var(--color-primary-border)',
'primary-solid': 'var(--color-primary-solid)',
'primary-solid-hover': 'var(--color-primary-solid-hover)',
'primary-text': 'var(--color-primary-text)',
'primary-contrast': 'var(--color-primary-contrast)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-element': 'var(--color-secondary-element)',
'secondary-border': 'var(--color-secondary-border)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-solid-hover': 'var(--color-secondary-solid-hover)',
'secondary-text': 'var(--color-secondary-text)',
'secondary-contrast': 'var(--color-secondary-contrast)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-element': 'var(--color-neutral-element)',
'neutral-border': 'var(--color-neutral-border)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-solid-hover': 'var(--color-neutral-solid-hover)',
'neutral-text': 'var(--color-neutral-text)',
'neutral-contrast': 'var(--color-neutral-contrast)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-element': 'var(--color-affirm-element)',
'affirm-border': 'var(--color-affirm-border)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-solid-hover': 'var(--color-affirm-solid-hover)',
'affirm-text': 'var(--color-affirm-text)',
'affirm-contrast': 'var(--color-affirm-contrast)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-element': 'var(--color-fulfill-element)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-solid-hover': 'var(--color-fulfill-solid-hover)',
'fulfill-text': 'var(--color-fulfill-text)',
'fulfill-contrast': 'var(--color-fulfill-contrast)',
'risk-track': 'var(--color-risk-track)',
'risk-element': 'var(--color-risk-element)',
'risk-border': 'var(--color-risk-border)',
'risk-solid': 'var(--color-risk-solid)',
'risk-solid-hover': 'var(--color-risk-solid-hover)',
'risk-text': 'var(--color-risk-text)',
'risk-contrast': 'var(--color-risk-contrast)',
'threat-track': 'var(--color-threat-track)',
'threat-element': 'var(--color-threat-element)',
'threat-border': 'var(--color-threat-border)',
'threat-solid': 'var(--color-threat-solid)',
'threat-solid-hover': 'var(--color-threat-solid-hover)',
'threat-text': 'var(--color-threat-text)',
'threat-contrast': 'var(--color-threat-contrast)',
'loss-track': 'var(--color-loss-track)',
'loss-element': 'var(--color-loss-element)',
'loss-border': 'var(--color-loss-border)',
'loss-solid': 'var(--color-loss-solid)',
'loss-solid-hover': 'var(--color-loss-solid-hover)',
'loss-text': 'var(--color-loss-text)',
'loss-contrast': 'var(--color-loss-contrast)',
// Active palette — 8 custom properties, each declared with 8
// declarations (host default = primary, plus per-color overrides
// for the 7 other supported values). Single source of truth via
// TSC v2 `declarations[]`. Replaces the per-color blocks that
// used to live in `button.css`.
'palette-track': {
declarations: [
{ value: 'var(--button-primary-track)', scope: 'host' },
{ value: 'var(--button-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--button-neutral-track)', scope: 'color:neutral' },
{ value: 'var(--button-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--button-fulfill-track)', scope: 'color:fulfill' },
{ value: 'var(--button-risk-track)', scope: 'color:risk' },
{ value: 'var(--button-threat-track)', scope: 'color:threat' },
{ value: 'var(--button-loss-track)', scope: 'color:loss' }
]
},
'palette-element': {
declarations: [
{ value: 'var(--button-primary-element)', scope: 'host' },
{ value: 'var(--button-secondary-element)', scope: 'color:secondary' },
{ value: 'var(--button-neutral-element)', scope: 'color:neutral' },
{ value: 'var(--button-affirm-element)', scope: 'color:affirm' },
{ value: 'var(--button-fulfill-element)', scope: 'color:fulfill' },
{ value: 'var(--button-risk-element)', scope: 'color:risk' },
{ value: 'var(--button-threat-element)', scope: 'color:threat' },
{ value: 'var(--button-loss-element)', scope: 'color:loss' }
]
},
'palette-border': {
declarations: [
{ value: 'var(--button-primary-border)', scope: 'host' },
{ value: 'var(--button-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--button-neutral-border)', scope: 'color:neutral' },
{ value: 'var(--button-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--button-fulfill-border)', scope: 'color:fulfill' },
{ value: 'var(--button-risk-border)', scope: 'color:risk' },
{ value: 'var(--button-threat-border)', scope: 'color:threat' },
{ value: 'var(--button-loss-border)', scope: 'color:loss' }
]
},
'palette-solid': {
declarations: [
{ value: 'var(--button-primary-solid)', scope: 'host' },
{ value: 'var(--button-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--button-neutral-solid)', scope: 'color:neutral' },
{ value: 'var(--button-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--button-fulfill-solid)', scope: 'color:fulfill' },
{ value: 'var(--button-risk-solid)', scope: 'color:risk' },
{ value: 'var(--button-threat-solid)', scope: 'color:threat' },
{ value: 'var(--button-loss-solid)', scope: 'color:loss' }
]
},
'palette-solid-hover': {
declarations: [
{ value: 'var(--button-primary-solid-hover)', scope: 'host' },
{ value: 'var(--button-secondary-solid-hover)', scope: 'color:secondary' },
{ value: 'var(--button-neutral-solid-hover)', scope: 'color:neutral' },
{ value: 'var(--button-affirm-solid-hover)', scope: 'color:affirm' },
{ value: 'var(--button-fulfill-solid-hover)', scope: 'color:fulfill' },
{ value: 'var(--button-risk-solid-hover)', scope: 'color:risk' },
{ value: 'var(--button-threat-solid-hover)', scope: 'color:threat' },
{ value: 'var(--button-loss-solid-hover)', scope: 'color:loss' }
]
},
'palette-text': {
declarations: [
{ value: 'var(--button-primary-text)', scope: 'host' },
{ value: 'var(--button-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--button-neutral-text)', scope: 'color:neutral' },
{ value: 'var(--button-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--button-fulfill-text)', scope: 'color:fulfill' },
{ value: 'var(--button-risk-text)', scope: 'color:risk' },
{ value: 'var(--button-threat-text)', scope: 'color:threat' },
{ value: 'var(--button-loss-text)', scope: 'color:loss' }
]
},
'palette-contrast': {
declarations: [
{ value: 'var(--button-primary-contrast)', scope: 'host' },
{ value: 'var(--button-secondary-contrast)', scope: 'color:secondary' },
{ value: 'var(--button-neutral-contrast)', scope: 'color:neutral' },
{ value: 'var(--button-affirm-contrast)', scope: 'color:affirm' },
{ value: 'var(--button-fulfill-contrast)', scope: 'color:fulfill' },
{ value: 'var(--button-risk-contrast)', scope: 'color:risk' },
{ value: 'var(--button-threat-contrast)', scope: 'color:threat' },
{ value: 'var(--button-loss-contrast)', scope: 'color:loss' }
]
}
},
calendar: {
gap: 'var(--space-3)',
'padding-xs': 'var(--space-2)',
'padding-sm': 'var(--space-3)',
'padding-md': 'var(--space-4)',
'padding-lg': 'var(--space-5)',
radius: 'var(--radius-lg)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-subtle)',
'invalid-border': 'var(--color-risk-border)',
bg: 'var(--color-surface-raised)',
'outline-bg': 'var(--color-surface-default)',
'outline-shadow': 'none',
color: 'var(--color-content-primary)',
shadow: 'var(--depth-raised-shadow), var(--depth-raised-halo)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'line-height': 'var(--leading-ui)',
'header-gap': 'var(--space-2)',
'heading-color': 'var(--color-content-primary)',
'heading-font-size': 'var(--font-size-sm)',
'heading-font-weight': 'var(--font-weight-semibold)',
'control-size-xs': 'var(--control-height-xxs)',
'control-size-sm': 'var(--control-height-xs)',
'control-size-md': 'var(--control-height-sm)',
'control-size-lg': 'var(--control-height-md)',
'control-radius': 'var(--radius-md)',
'control-border-width': 'var(--border-width)',
'control-border': 'transparent',
'control-border-hover': 'var(--color-border-subtle)',
'control-bg': 'transparent',
'control-bg-hover': 'var(--color-surface-overlay)',
'control-color': 'var(--color-content-secondary)',
'control-color-hover': 'var(--color-content-primary)',
'select-min-width': '8ch',
'select-px': 'var(--space-2)',
'grid-gap': 'var(--space-0-5)',
'weekday-size': 'var(--space-5)',
'weekday-color': 'var(--color-content-muted)',
'weekday-font-size': 'var(--font-size-xs)',
'weekday-font-weight': 'var(--font-weight-medium)',
'day-size-xs': 'var(--control-height-xxs)',
'day-size-sm': 'var(--control-height-xs)',
'day-size-md': 'var(--control-height-sm)',
'day-size-lg': 'var(--control-height-md)',
'day-radius': 'var(--radius-md)',
'day-border-width': 'var(--border-width)',
'day-border': 'transparent',
'day-border-hover': 'var(--color-border-subtle)',
'day-bg': 'transparent',
'day-bg-hover': 'var(--color-surface-overlay)',
'day-color': 'var(--color-content-primary)',
'day-color-hover': 'var(--color-content-primary)',
'day-font-weight': 'var(--font-weight-regular)',
'day-selected-color': 'var(--color-content-on-solid)',
'day-today-border': 'var(--color-border-strong)',
'day-today-color': 'var(--color-content-primary)',
'day-today-font-weight': 'var(--font-weight-semibold)',
'day-outside-color': 'var(--color-content-muted)',
'day-outside-opacity': '0.62',
'day-weekend-color': 'var(--color-content-secondary)',
'day-holiday-shadow': 'inset 0 -2px 0 var(--_calendar-accent-border)',
'day-unavailable-decoration': 'var(--color-risk-border)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'event-shadow':
'0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--_calendar-accent-border)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
chronos: {
// Component-semantic dimensions only. Spacing, typography and colors come
// straight from the eidos primitives in chronos.css (no per-component
// font/color tokens — those belong to the eidos foundation).
'cell-min-block-sm': 'calc(var(--control-height-md) * 2.6)',
'cell-min-block-md': 'calc(var(--control-height-md) * 3.2)',
'cell-min-block-lg': 'calc(var(--control-height-md) * 4)',
'num-row': 'var(--space-7)',
'lane-height-sm': 'var(--space-5)',
'lane-height-md': 'var(--space-6)',
'lane-height-lg': 'var(--space-7)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'disabled-opacity': 'var(--opacity-disabled)'
},
pagination: {
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'control-size-xs': 'var(--control-height-xxs)',
'control-size-sm': 'var(--control-height-xs)',
'control-size-md': 'var(--control-height-sm)',
'control-size-lg': 'var(--control-height-md)',
'control-px-xs': 'var(--space-1-5)',
'control-px-sm': 'var(--space-2)',
'control-px-md': 'var(--space-2-5)',
'control-px-lg': 'var(--space-3)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'line-height': 'var(--leading-ui)',
'control-radius': 'var(--radius-md)',
'control-border-width': 'var(--border-width)',
'control-border': 'transparent',
'control-border-hover': 'var(--color-border-subtle)',
'control-bg': 'transparent',
'control-bg-hover': 'var(--color-surface-raised)',
'control-color': 'var(--color-content-secondary)',
'control-color-hover': 'var(--color-content-primary)',
'selected-border': 'var(--color-primary-border)',
'selected-border-hover': 'var(--color-primary-border)',
'selected-bg': 'var(--color-primary-solid)',
'selected-bg-hover': 'var(--color-primary-solid-hover)',
'selected-color': 'var(--color-content-on-solid)',
'ellipsis-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
popover: {
'content-z': '75',
'overlay-z': '60',
'overlay-bg': 'var(--color-overlay)',
'trigger-font-size': 'var(--font-size-sm)',
gap: 'var(--space-3)',
'max-width': '32rem',
'min-width-sm': '14rem',
'min-width-md': '18rem',
'min-width-lg': '22rem',
'min-width-xl': '28rem',
'padding-y-sm': 'var(--space-2)',
'padding-x-sm': 'var(--space-2-5)',
'padding-y-md': 'var(--space-3)',
'padding-x-md': 'var(--space-3)',
'padding-y-lg': 'var(--space-3-5)',
'padding-x-lg': 'var(--space-4)',
'padding-y-xl': 'var(--space-4)',
'padding-x-xl': 'var(--space-5)',
'font-size': 'var(--font-size-sm)',
'font-family': 'var(--font-ui)',
'font-weight': 'var(--font-weight-regular)',
'line-height': 'var(--leading-text)',
'title-font-family': 'var(--font-heading)',
'title-font-size': 'var(--font-size-md)',
'title-font-weight': 'var(--font-weight-semibold)',
'title-line-height': 'var(--leading-heading)',
'title-color': 'var(--color-content-primary)',
'description-font-family': 'var(--font-ui)',
'description-font-size': 'var(--font-size-sm)',
'description-line-height': 'var(--leading-ui)',
'description-color': 'var(--color-content-secondary)',
radius: 'var(--radius-lg)',
'border-width': 'var(--border-width)',
bg: 'var(--color-surface-overlay)',
color: 'var(--color-content-primary)',
border: 'color-mix( in srgb, var(--color-border-default) 82%, var(--popover-bg) )',
shadow: 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'close-size': 'var(--control-height-xs)',
'close-font-size': 'var(--font-size-sm)',
'close-radius': 'var(--radius-md)',
'close-border-width': 'var(--border-width)',
'close-bg': 'transparent',
'close-bg-hover': 'color-mix( in srgb, var(--color-neutral-track) 72%, var(--popover-bg) )',
'close-color': 'var(--color-content-secondary)',
'close-border': 'color-mix(in srgb, var(--color-border-default) 72%, transparent)',
'close-border-hover': 'var(--color-border-strong)'
},
progress: {
'height-xs': '2px',
'height-sm': '4px',
'height-md': '6px',
'height-lg': '8px',
'height-xl': '12px',
'vertical-height': '10rem',
'radius-xs': 'var(--radius-full)',
'radius-sm': 'var(--radius-full)',
'radius-md': 'var(--radius-full)',
'radius-lg': 'var(--radius-full)',
'radius-xl': 'var(--radius-full)',
'ring-size-xs': '2rem',
'ring-size-sm': '2.5rem',
'ring-size-md': '3rem',
'ring-size-lg': '3.5rem',
'ring-size-xl': '4rem',
'ring-thickness-xs': '3px',
'ring-thickness-sm': '4px',
'ring-thickness-md': '6px',
'ring-thickness-lg': '8px',
'ring-thickness-xl': '10px',
'label-font-family': 'var(--font-ui)',
'label-font-size': 'var(--font-size-sm)',
'label-font-weight': 'var(--font-weight-medium)',
'label-line-height': 'var(--leading-ui)',
'label-color': 'var(--color-content-primary)',
'value-text-font-family': 'var(--font-ui)',
'value-text-font-size': 'var(--font-size-sm)',
'value-text-font-weight': 'var(--font-weight-regular)',
'value-text-line-height': 'var(--leading-ui)',
'value-text-color': 'var(--color-content-secondary)',
'track-bg':
'color-mix(in srgb, var(--color-content-primary) 12%, var(--color-surface-default))',
'ring-track-bg':
'color-mix(in srgb, var(--color-content-primary) 12%, var(--color-surface-default))',
'ring-center-bg': 'var(--color-surface-default)',
'indicator-bg': 'var(--color-primary-solid)',
'indicator-bg-loaded': 'var(--color-fulfill-solid)',
'transition-duration': 'var(--duration-normal)',
'transition-ease': 'var(--ease-default)',
'indeterminate-width': '35%',
'indeterminate-duration': '1.1s',
'indeterminate-ease': 'var(--ease-default)'
},
'radio-group': {
'gap-vertical': 'var(--space-3)',
'gap-horizontal': 'var(--space-4)',
'item-size-xs': '14px',
'item-size-sm': '16px',
'item-size-md': '18px',
'item-size-lg': '22px',
'item-size-xl': '26px',
'dot-size-xs': '5px',
'dot-size-sm': '6px',
'dot-size-md': '7px',
'dot-size-lg': '9px',
'dot-size-xl': '11px',
'item-gap': 'var(--space-3)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'font-weight': 'var(--font-weight-medium)',
'line-height': 'var(--leading-ui)',
'item-label-color': 'var(--color-content-primary)',
'item-label-color-disabled': 'var(--color-content-disabled)',
'indicator-radius': 'var(--radius-full)',
'indicator-border-width': 'var(--border-width)',
'indicator-bg': 'var(--color-surface-default)',
'indicator-border': 'var(--color-border-default)',
'indicator-border-hover': 'var(--color-border-strong)',
'indicator-bg-disabled':
'color-mix( in srgb, var(--color-surface-overlay) 78%, var(--color-surface-default) )',
'indicator-border-disabled':
'color-mix( in srgb, var(--color-border-default) 72%, var(--color-border-subtle) )',
'indicator-bg-checked-disabled':
'color-mix( in srgb, var(--color-content-disabled) 40%, var(--color-surface-overlay) )',
'dot-color': 'var(--color-content-on-solid)',
'dot-color-disabled':
'color-mix( in srgb, var(--color-content-on-solid) 82%, var(--color-surface-default) )',
'focus-ring-offset': 'var(--focus-ring-offset)',
'focus-ring-width': 'var(--focus-ring-width)',
'focus-ring-color': 'var(--focus-ring-color)',
'focus-ring-surface': 'var(--color-surface-default)',
'focus-ring':
'0 0 0 var(--radio-group-focus-ring-offset) var(--radio-group-focus-ring-surface), 0 0 0 calc(var(--radio-group-focus-ring-offset) + var(--radio-group-focus-ring-width)) var(--radio-group-focus-ring-color)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)',
'primary-solid': 'var(--color-primary-solid)',
'primary-solid-hover': 'var(--color-primary-solid-hover)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-solid-hover': 'var(--color-neutral-solid-hover)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-solid-hover': 'var(--color-secondary-solid-hover)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-solid-hover': 'var(--color-affirm-solid-hover)',
'label-font-family': 'var(--font-ui)',
'label-font-size': 'var(--font-size-sm)',
'label-font-weight': 'var(--font-weight-medium)',
'label-line-height': 'var(--leading-ui)',
'label-color': 'var(--color-content-primary)',
// Private palette (TSC v2.1). Declared on the [data-radio-group]
// host, inherited by [data-radio-group-item] descendants via
// CSS custom property inheritance. Migrated from per-color
// blocks in radio-group.css.
'_palette-solid': {
declarations: [
{ value: 'var(--radio-group-primary-solid)', scope: 'host' },
{ value: 'var(--radio-group-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--radio-group-neutral-solid)', scope: 'color:neutral' },
{ value: 'var(--radio-group-affirm-solid)', scope: 'color:affirm' }
]
},
'_palette-solid-hover': {
declarations: [
{ value: 'var(--radio-group-primary-solid-hover)', scope: 'host' },
{ value: 'var(--radio-group-secondary-solid-hover)', scope: 'color:secondary' },
{ value: 'var(--radio-group-neutral-solid-hover)', scope: 'color:neutral' },
{ value: 'var(--radio-group-affirm-solid-hover)', scope: 'color:affirm' }
]
},
'_palette-text': { value: 'var(--radio-group-dot-color)', scope: 'host' }
},
// QrCode. Default fg/bg are FIXED-TONE (dark modules on a light backing),
// not theme tokens: a QR must stay dark-on-light to scan — inverting it in
// dark mode would break scanners. Theming goes through the `color` /
// `background` props (consumer owns the contrast).
'qr-code': {
fg: '#18181b',
bg: '#ffffff',
'size-xs': '96px',
'size-sm': '128px',
'size-md': '160px',
'size-lg': '200px',
'size-xl': '256px',
radius: 'var(--radius-md)',
// White padding behind a center logo — FIXED-TONE for the same reason as
// fg/bg above (the quiet zone around the logo must stay light to scan).
'overlay-bg': '#ffffff',
'overlay-radius': 'var(--radius-sm)'
},
// RadioCards — card-style skin over RadioGroup. Reuses RadioGroup's
// palette (`--_radio-group-palette-solid`, declared on the shared
// [data-radio-group] host) and `--radio-group-focus-ring`; adds only the
// card chrome (padding, radius, surface, title/description type).
'radio-cards': {
gap: 'var(--space-3)',
// Min card width in a horizontal row before wrapping (rem → scales with
// root font-size / zoom).
'horizontal-basis': '12rem',
'padding-xs': 'var(--space-3)',
'padding-sm': 'var(--space-3)',
'padding-md': 'var(--space-4)',
'padding-lg': 'var(--space-4)',
'padding-xl': 'var(--space-5)',
radius: 'var(--radius-lg)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'border-hover': 'var(--color-border-strong)',
bg: 'var(--color-surface-default)',
'bg-hover': 'var(--color-surface-raised)',
'content-gap': 'var(--space-3)',
'text-gap': 'var(--space-0-5)',
'title-font-family': 'var(--font-ui)',
'title-font-size-xs': 'var(--font-size-sm)',
'title-font-size-sm': 'var(--font-size-sm)',
'title-font-size-md': 'var(--font-size-md)',
'title-font-size-lg': 'var(--font-size-md)',
'title-font-size-xl': 'var(--font-size-lg)',
'title-font-weight': 'var(--font-weight-medium)',
'title-line-height': 'var(--leading-ui)',
'title-color': 'var(--color-content-primary)',
'description-font-size-xs': 'var(--font-size-xs)',
'description-font-size-sm': 'var(--font-size-xs)',
'description-font-size-md': 'var(--font-size-sm)',
'description-font-size-lg': 'var(--font-size-sm)',
'description-font-size-xl': 'var(--font-size-md)',
'description-line-height': 'var(--leading-normal)',
'description-color': 'var(--color-content-secondary)',
// Icon follows the card's title font-size (canonical icon↔type correlation),
// not an inflated standalone size. A bigger card icon comes from a bigger
// title (the icon follows), never from inflating the icon by hand.
'icon-size-xs': 'var(--icon-size-sm)',
'icon-size-sm': 'var(--icon-size-sm)',
'icon-size-md': 'var(--icon-size-md)',
'icon-size-lg': 'var(--icon-size-md)',
'icon-size-xl': 'var(--icon-size-lg)',
'icon-color': 'var(--color-content-secondary)',
// NOTE: the checked treatment (active border + faint tint + icon tint)
// is NOT a token here. It references `--_radio-group-palette-solid`,
// which is undefined at :root (declared only on [data-radio-group]
// hosts) — a :root token referencing it is invalid-at-computed-value
// and poisons down the tree. It's inlined in radio-cards.css instead,
// applied on the card item where the palette IS in scope.
'focus-ring': 'var(--radio-group-focus-ring)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'rating-group': {
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'gap-xl': 'var(--space-2-5)',
'item-size-xs': '14px',
'item-size-sm': '18px',
'item-size-md': '22px',
'item-size-lg': '28px',
'item-size-xl': '34px',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
'item-color-inactive': 'var(--color-neutral-border)',
'item-color-active': 'var(--color-fulfill-solid)',
'item-color-partial': 'var(--color-fulfill-solid)',
'item-hover-offset': '-1px',
'focus-radius': 'var(--radius-md)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'search-field': {
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-2-5)',
'px-md': 'var(--space-3)',
'px-lg': 'var(--space-3-5)',
'px-xl': 'var(--space-4)',
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1-5)',
'gap-md': 'var(--space-2)',
'gap-lg': 'var(--space-2-5)',
'gap-xl': 'var(--space-3)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'border-focus': 'var(--color-focus-ring)',
'border-invalid': 'var(--color-risk-border)',
bg: 'var(--color-surface-default)',
color: 'var(--color-content-primary)',
'placeholder-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'clear-hidden-opacity': '0',
'icon-size-xs': 'var(--icon-size-xs)',
'icon-size-sm': 'var(--icon-size-sm)',
'icon-size-md': 'var(--icon-size-md)',
'icon-size-lg': 'var(--icon-size-lg)',
'icon-size-xl': 'var(--icon-size-xl)',
'icon-color': 'var(--color-content-muted)',
'icon-margin-inline-end': 'var(--space-1-5)',
'loading-indicator-size-xs': 'var(--icon-size-xs)',
'loading-indicator-size-sm': 'var(--icon-size-sm)',
'loading-indicator-size-md': 'var(--icon-size-md)',
'loading-indicator-size-lg': 'var(--icon-size-md)',
'loading-indicator-size-xl': 'var(--icon-size-lg)',
'loading-indicator-color': 'var(--color-content-muted)',
'loading-indicator-track': 'var(--color-border-subtle)',
'loading-indicator-thickness': '2px',
'loading-indicator-duration': '900ms',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'password-field': {
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-2-5)',
'px-md': 'var(--space-3)',
'px-lg': 'var(--space-3-5)',
'px-xl': 'var(--space-4)',
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1-5)',
'gap-md': 'var(--space-2)',
'gap-lg': 'var(--space-2-5)',
'gap-xl': 'var(--space-3)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-default)',
'border-focus': 'var(--color-focus-ring)',
'border-invalid': 'var(--color-risk-border)',
bg: 'var(--color-surface-default)',
color: 'var(--color-content-primary)',
'placeholder-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'trigger-radius': 'var(--radius-full)',
'trigger-border-width': 'var(--border-width)',
'trigger-border': 'transparent',
'trigger-border-hover': 'var(--color-border-subtle)',
'trigger-bg': 'transparent',
'trigger-bg-hover': 'var(--color-surface-raised)',
'trigger-color': 'var(--color-content-secondary)',
'trigger-color-hover': 'var(--color-content-primary)',
'trigger-focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'icon-size-xs': 'var(--control-height-xs)',
'icon-size-sm': 'var(--control-height-xs)',
'icon-size-md': 'var(--control-height-sm)',
'icon-size-lg': 'var(--control-height-sm)',
'icon-size-xl': 'var(--control-height-md)',
'caps-color': 'var(--color-risk-text)',
'caps-bg': 'color-mix(in srgb, var(--color-risk-track) 50%, transparent)',
'caps-font-size-xs': 'var(--font-size-xs)',
'caps-font-size-sm': 'var(--font-size-xs)',
'caps-font-size-md': 'var(--font-size-xs)',
'caps-font-size-lg': 'var(--font-size-sm)',
'caps-font-size-xl': 'var(--font-size-sm)',
'strength-segment-bg': 'var(--color-border-default)',
'strength-segment-gap': '4px',
'strength-segment-height': '6px',
'strength-radius': 'var(--radius-sm)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
select: {
'stack-gap': 'var(--space-2)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
color: 'var(--color-content-primary)',
// Per-color forwarders consumed only by the TSC v2.2 `_accent-*`
// cascade below. The `solid` slot is intentionally omitted —
// select.css doesn't read `_accent-solid`, so its forwarders
// would be orphans (caught by the recipe-css-contract guard).
'primary-text': 'var(--color-primary-text)',
'secondary-text': 'var(--color-secondary-text)',
'neutral-text': 'var(--color-neutral-text)',
'affirm-text': 'var(--color-affirm-text)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-text': 'var(--color-fulfill-text)',
'risk-text': 'var(--color-risk-text)',
'threat-text': 'var(--color-threat-text)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-text': 'var(--color-loss-text)',
'trigger-height-xs': 'var(--control-height-xs)',
'trigger-height-sm': 'var(--control-height-sm)',
'trigger-height-md': 'var(--control-height-md)',
'trigger-height-lg': 'var(--control-height-lg)',
'trigger-height-xl': 'var(--control-height-xl)',
'trigger-px-xs': 'var(--space-2)',
'trigger-px-sm': 'var(--space-2-5)',
'trigger-px-md': 'var(--space-3)',
'trigger-px-lg': 'var(--space-3-5)',
'trigger-px-xl': 'var(--space-4)',
'trigger-gap-xs': 'var(--space-1)',
'trigger-gap-sm': 'var(--space-1-5)',
'trigger-gap-md': 'var(--space-2)',
'trigger-gap-lg': 'var(--space-2-5)',
'trigger-gap-xl': 'var(--space-3)',
'trigger-border-width': 'var(--border-width)',
'trigger-border': 'var(--color-border-default)',
'trigger-border-hover': 'var(--color-border-strong)',
'trigger-radius': 'var(--radius-md)',
'trigger-bg': 'var(--color-surface-default)',
'trigger-bg-hover': 'var(--color-surface-raised)',
'trigger-shadow': 'var(--shadow-subtle)',
'trigger-color': 'var(--color-content-primary)',
'trigger-indicator-color': 'var(--color-content-secondary)',
'trigger-indicator-size': 'var(--icon-size-sm)',
'placeholder-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'disabled-border': 'var(--color-border-subtle)',
'disabled-bg': 'var(--color-surface-raised)',
'disabled-color': 'var(--color-content-disabled)',
'content-z': '80',
'content-py-xs': 'var(--space-0-5)',
'content-py-sm': 'var(--space-1)',
'content-py-md': 'var(--space-1-5)',
'content-py-lg': 'var(--space-2)',
'content-py-xl': 'var(--space-2-5)',
'content-px-xs': 'var(--space-0-5)',
'content-px-sm': 'var(--space-1)',
'content-px-md': 'var(--space-1-5)',
'content-px-lg': 'var(--space-2)',
'content-px-xl': 'var(--space-2-5)',
'content-min-width': '12rem',
'content-max-width': '24rem',
'content-max-height': 'min(20rem, calc(100dvh - var(--space-6)))',
'content-border-width': 'var(--border-width)',
'content-border': 'var(--color-border-default)',
'content-radius': 'var(--radius-lg)',
'content-bg': 'var(--color-surface-overlay)',
'content-color': 'var(--color-content-primary)',
'content-shadow': 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'content-outline-shadow': 'var(--depth-raised-shadow), var(--depth-raised-halo)',
'content-ghost-shadow': 'var(--depth-raised-shadow), var(--depth-raised-halo)',
'open-duration': 'var(--duration-normal)',
'close-duration': 'var(--duration-fast)',
'viewport-gap': 'var(--space-0-5)',
'viewport-padding': 'var(--space-0-5)',
'group-gap': 'var(--space-0-5)',
'group-py': 'var(--space-1)',
'group-heading-py': 'var(--space-1)',
'group-heading-px': 'var(--space-2)',
'group-heading-font-family': 'var(--font-ui)',
'group-heading-font-size': 'var(--font-size-xs)',
'group-heading-font-weight': 'var(--font-weight-semibold)',
'group-heading-letter-spacing': '0.04em',
'group-heading-color': 'var(--color-content-muted)',
'separator-size': 'var(--border-width)',
'separator-my': 'var(--space-1)',
'separator-bg': 'var(--color-border-subtle)',
'item-gap': 'var(--space-2)',
'item-min-height': 'var(--control-height-sm)',
'item-px': 'var(--space-2)',
'item-radius': 'var(--radius-md)',
'item-color': 'var(--color-content-primary)',
'item-bg-highlighted': 'var(--color-surface-raised)',
'item-color-highlighted': 'var(--color-content-primary)',
'item-indicator-size': 'var(--icon-size-sm)',
'item-focus-ring-width': 'var(--border-width-medium)',
'item-description-mt': '0.125rem',
'item-description-color': 'var(--color-content-muted)',
'item-description-font-size': '0.85em',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
// Restored after accidental sed-removal during switch migration.
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)',
// TSC v2.2 — multi-part palette cascade. Select's `data-color`
// attribute lives on the trigger AND content parts (NOT on the
// root `[data-select]`), because content portals out of the
// document tree and must carry the color independently. The
// `parts: ['trigger', 'content']` field tells the generator to
// emit each declaration as a comma-separated rule targeting both
// `[data-select-trigger]` and `[data-select-content]`.
//
// 1 host default (primary) + 7 per-color cascades for each of the
// 3 perceptual slots that select.css consumes (`track`, `border`,
// `text`). The legacy CSS also declared `--_select-accent-solid`
// but nothing in select.css read it; that orphan stayed out of
// the TSC migration on purpose.
'_accent-track': {
parts: ['trigger', 'content'],
declarations: [
{ value: 'var(--select-primary-track)', scope: 'host' },
{ value: 'var(--select-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--select-neutral-track)', scope: 'color:neutral' },
{ value: 'var(--select-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--select-fulfill-track)', scope: 'color:fulfill' },
{ value: 'var(--select-risk-track)', scope: 'color:risk' },
{ value: 'var(--select-threat-track)', scope: 'color:threat' },
{ value: 'var(--select-loss-track)', scope: 'color:loss' }
]
},
'_accent-border': {
parts: ['trigger', 'content'],
declarations: [
{ value: 'var(--select-primary-border)', scope: 'host' },
{ value: 'var(--select-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--select-neutral-border)', scope: 'color:neutral' },
{ value: 'var(--select-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--select-fulfill-border)', scope: 'color:fulfill' },
{ value: 'var(--select-risk-border)', scope: 'color:risk' },
{ value: 'var(--select-threat-border)', scope: 'color:threat' },
{ value: 'var(--select-loss-border)', scope: 'color:loss' }
]
},
'_accent-text': {
parts: ['trigger', 'content'],
declarations: [
{ value: 'var(--select-primary-text)', scope: 'host' },
{ value: 'var(--select-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--select-neutral-text)', scope: 'color:neutral' },
{ value: 'var(--select-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--select-fulfill-text)', scope: 'color:fulfill' },
{ value: 'var(--select-risk-text)', scope: 'color:risk' },
{ value: 'var(--select-threat-text)', scope: 'color:threat' },
{ value: 'var(--select-loss-text)', scope: 'color:loss' }
]
}
},
toolbar: {
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'padding-inline-xs': 'var(--space-0-5)',
'padding-inline-sm': 'var(--space-1)',
'padding-inline-md': 'var(--space-1-5)',
'padding-inline-lg': 'var(--space-2)',
'padding-block-xs': 'var(--space-0-5)',
'padding-block-sm': 'var(--space-1)',
'padding-block-md': 'var(--space-1)',
'padding-block-lg': 'var(--space-1-5)',
'control-height-xs': 'var(--control-height-xs)',
'control-height-sm': 'var(--control-height-sm)',
'control-height-md': 'var(--control-height-md)',
'control-height-lg': 'var(--control-height-lg)',
'control-px-xs': 'var(--space-1-5)',
'control-px-sm': 'var(--space-2)',
'control-px-md': 'var(--space-2-5)',
'control-px-lg': 'var(--space-3)',
'control-gap-xs': 'var(--space-0-5)',
'control-gap-sm': 'var(--space-1)',
'control-gap-md': 'var(--space-1-5)',
'control-gap-lg': 'var(--space-2)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'line-height': 'var(--leading-ui)',
radius: 'var(--radius-lg)',
bg: 'var(--color-surface-raised)',
border: 'var(--color-border-subtle)',
'border-width': 'var(--border-width)',
shadow: 'var(--shadow-subtle)',
color: 'var(--color-content-primary)',
'group-gap': 'var(--space-0-5)',
'control-radius': 'var(--radius-md)',
'control-border-width': 'var(--border-width)',
'control-border': 'transparent',
'control-border-hover': 'var(--color-border-subtle)',
'control-border-active': 'var(--color-primary-border)',
'control-bg': 'transparent',
'control-bg-hover': 'var(--color-surface-overlay)',
'control-bg-active': 'var(--color-primary-track)',
'control-color': 'var(--color-content-secondary)',
'control-color-hover': 'var(--color-content-primary)',
'control-color-active': 'var(--color-primary-text)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'separator-bg': 'var(--color-border-subtle)',
'separator-thickness': 'var(--border-width)',
'separator-length': 'var(--control-height-md)',
'separator-radius': 'var(--radius-full)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
'file-upload': {
'gap-sm': 'var(--space-2)',
'gap-md': 'var(--space-3)',
'gap-lg': 'var(--space-4)',
'padding-sm': 'var(--space-4)',
'padding-md': 'var(--space-5)',
'padding-lg': 'var(--space-6)',
'control-height-sm': 'var(--control-height-sm)',
'control-height-md': 'var(--control-height-md)',
'control-height-lg': 'var(--control-height-lg)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'preview-size-sm': '32px',
'preview-size-md': '40px',
'preview-size-lg': '48px',
'font-family': 'var(--font-ui)',
'line-height': 'var(--leading-ui)',
color: 'var(--color-content-primary)',
'label-color': 'var(--color-content-secondary)',
'label-font-weight': 'var(--font-weight-medium)',
'dropzone-gap': 'var(--space-2)',
'dropzone-min-height': '9rem',
'dropzone-border-width': 'var(--border-width)',
'dropzone-border': 'var(--color-border-default)',
'dropzone-radius': 'var(--radius-lg)',
'dropzone-bg': 'var(--color-surface-raised)',
'dropzone-color': 'var(--color-content-secondary)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'button-gap': 'var(--space-1-5)',
'button-px': 'var(--space-3)',
'button-border-width': 'var(--border-width)',
'button-border': 'var(--color-border-subtle)',
'button-border-hover': 'var(--color-border-default)',
'button-radius': 'var(--radius-md)',
'button-bg': 'var(--color-surface-raised)',
'button-bg-hover': 'var(--color-surface-overlay)',
'button-color': 'var(--color-content-secondary)',
'button-color-hover': 'var(--color-content-primary)',
'button-solid-color': 'var(--color-content-on-solid)',
'list-gap': 'var(--space-2)',
'item-gap': 'var(--space-3)',
'item-padding': 'var(--space-2)',
'item-border-width': 'var(--border-width)',
'item-border': 'var(--color-border-subtle)',
'item-radius': 'var(--radius-md)',
'item-bg': 'var(--color-surface-raised)',
'item-shadow': 'var(--shadow-subtle)',
'preview-radius': 'var(--radius-sm)',
'preview-bg': 'var(--color-neutral-track)',
'preview-color': 'var(--color-neutral-text)',
'item-name-color': 'var(--color-content-primary)',
'item-name-font-weight': 'var(--font-weight-medium)',
'item-size-color': 'var(--color-content-muted)',
'item-size-font-size': 'var(--font-size-xs)',
'progress-height': '4px',
'progress-radius': 'var(--radius-full)',
'progress-track-bg': 'var(--color-neutral-track)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)',
'primary-solid': 'var(--color-primary-solid)',
'primary-solid-hover': 'var(--color-primary-solid-hover)',
'primary-text': 'var(--color-primary-text)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-solid-hover': 'var(--color-secondary-solid-hover)',
'secondary-text': 'var(--color-secondary-text)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-solid-hover': 'var(--color-neutral-solid-hover)',
'neutral-text': 'var(--color-neutral-text)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-solid-hover': 'var(--color-affirm-solid-hover)',
'affirm-text': 'var(--color-affirm-text)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-solid-hover': 'var(--color-fulfill-solid-hover)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-text': 'var(--color-fulfill-text)',
'risk-solid': 'var(--color-risk-solid)',
'risk-solid-hover': 'var(--color-risk-solid-hover)',
'risk-text': 'var(--color-risk-text)',
'threat-solid': 'var(--color-threat-solid)',
'threat-solid-hover': 'var(--color-threat-solid-hover)',
'threat-text': 'var(--color-threat-text)',
'loss-solid': 'var(--color-loss-solid)',
'loss-solid-hover': 'var(--color-loss-solid-hover)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-text': 'var(--color-loss-text)',
// Restored after accidental sed-removal during switch migration.
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)',
// Private accent palette (TSC v2.1). 8 colors, 5 slots.
'_accent-solid': {
declarations: [
{ value: 'var(--file-upload-primary-solid)', scope: 'host' },
{ value: 'var(--file-upload-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--file-upload-neutral-solid)', scope: 'color:neutral' },
{ value: 'var(--file-upload-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--file-upload-fulfill-solid)', scope: 'color:fulfill' },
{ value: 'var(--file-upload-risk-solid)', scope: 'color:risk' },
{ value: 'var(--file-upload-threat-solid)', scope: 'color:threat' },
{ value: 'var(--file-upload-loss-solid)', scope: 'color:loss' }
]
},
'_accent-solid-hover': {
declarations: [
{ value: 'var(--file-upload-primary-solid-hover)', scope: 'host' },
{ value: 'var(--file-upload-secondary-solid-hover)', scope: 'color:secondary' },
{ value: 'var(--file-upload-neutral-solid-hover)', scope: 'color:neutral' },
{ value: 'var(--file-upload-affirm-solid-hover)', scope: 'color:affirm' },
{ value: 'var(--file-upload-fulfill-solid-hover)', scope: 'color:fulfill' },
{ value: 'var(--file-upload-risk-solid-hover)', scope: 'color:risk' },
{ value: 'var(--file-upload-threat-solid-hover)', scope: 'color:threat' },
{ value: 'var(--file-upload-loss-solid-hover)', scope: 'color:loss' }
]
},
'_accent-track': {
declarations: [
{ value: 'var(--file-upload-primary-track)', scope: 'host' },
{ value: 'var(--file-upload-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--file-upload-neutral-track)', scope: 'color:neutral' },
{ value: 'var(--file-upload-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--file-upload-fulfill-track)', scope: 'color:fulfill' },
{ value: 'var(--file-upload-risk-track)', scope: 'color:risk' },
{ value: 'var(--file-upload-threat-track)', scope: 'color:threat' },
{ value: 'var(--file-upload-loss-track)', scope: 'color:loss' }
]
},
'_accent-border': {
declarations: [
{ value: 'var(--file-upload-primary-border)', scope: 'host' },
{ value: 'var(--file-upload-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--file-upload-neutral-border)', scope: 'color:neutral' },
{ value: 'var(--file-upload-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--file-upload-fulfill-border)', scope: 'color:fulfill' },
{ value: 'var(--file-upload-risk-border)', scope: 'color:risk' },
{ value: 'var(--file-upload-threat-border)', scope: 'color:threat' },
{ value: 'var(--file-upload-loss-border)', scope: 'color:loss' }
]
},
'_accent-text': {
declarations: [
{ value: 'var(--file-upload-primary-text)', scope: 'host' },
{ value: 'var(--file-upload-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--file-upload-neutral-text)', scope: 'color:neutral' },
{ value: 'var(--file-upload-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--file-upload-fulfill-text)', scope: 'color:fulfill' },
{ value: 'var(--file-upload-risk-text)', scope: 'color:risk' },
{ value: 'var(--file-upload-threat-text)', scope: 'color:threat' },
{ value: 'var(--file-upload-loss-text)', scope: 'color:loss' }
]
}
},
'tag-group': {
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'item-height-xs': 'var(--control-height-xs)',
'item-height-sm': 'var(--control-height-sm)',
'item-height-md': 'var(--control-height-md)',
'item-height-lg': 'var(--control-height-lg)',
'item-px-xs': 'var(--space-1-5)',
'item-px-sm': 'var(--space-2)',
'item-px-md': 'var(--space-2-5)',
'item-px-lg': 'var(--space-3)',
'item-gap-xs': 'var(--space-0-5)',
'item-gap-sm': 'var(--space-1)',
'item-gap-md': 'var(--space-1-5)',
'item-gap-lg': 'var(--space-2)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'line-height': 'var(--leading-ui)',
color: 'var(--color-content-primary)',
'label-color': 'var(--color-content-secondary)',
'label-font-weight': 'var(--font-weight-medium)',
'item-radius': 'var(--radius-full)',
'item-border-width': 'var(--border-width)',
'item-border': 'var(--color-border-subtle)',
'item-border-hover': 'var(--color-border-default)',
'item-bg': 'var(--color-surface-raised)',
'item-bg-hover': 'var(--color-surface-overlay)',
'item-shadow': 'var(--shadow-subtle)',
'item-color': 'var(--color-content-secondary)',
'item-color-hover': 'var(--color-content-primary)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'solid-fg': 'var(--color-content-on-solid)',
'remove-size': 'var(--control-height-xs)',
'remove-radius': 'var(--radius-full)',
'remove-border-width': 'var(--border-width)',
'remove-border': 'transparent',
'remove-border-hover': 'var(--color-border-subtle)',
'remove-bg': 'transparent',
'remove-bg-hover': 'color-mix(in srgb, currentColor 12%, transparent)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)',
'primary-solid-bg': 'var(--color-primary-solid)',
'primary-solid-bg-hover': 'var(--color-primary-solid-hover)',
'primary-soft-bg': 'var(--color-primary-surface)',
'primary-soft-bg-hover': 'var(--color-primary-surface-hover)',
'primary-soft-fg': 'var(--color-primary-text)',
'primary-outline-border': 'var(--color-primary-border)',
'primary-outline-fg': 'var(--color-primary-text)',
'secondary-solid-bg': 'var(--color-secondary-solid)',
'secondary-solid-bg-hover': 'var(--color-secondary-solid-hover)',
'secondary-soft-bg': 'var(--color-secondary-surface)',
'secondary-soft-bg-hover': 'var(--color-secondary-surface-hover)',
'secondary-soft-fg': 'var(--color-secondary-text)',
'secondary-outline-border': 'var(--color-secondary-border)',
'secondary-outline-fg': 'var(--color-secondary-text)',
'neutral-solid-bg': 'var(--color-neutral-solid)',
'neutral-solid-bg-hover': 'var(--color-neutral-solid-hover)',
'neutral-soft-bg': 'var(--color-neutral-surface)',
'neutral-soft-bg-hover': 'var(--color-neutral-surface-hover)',
'neutral-soft-fg': 'var(--color-neutral-text)',
'neutral-outline-border': 'var(--color-neutral-border)',
'neutral-outline-fg': 'var(--color-neutral-text)',
'affirm-solid-bg': 'var(--color-affirm-solid)',
'affirm-solid-bg-hover': 'var(--color-affirm-solid-hover)',
'affirm-soft-bg': 'var(--color-affirm-surface)',
'affirm-soft-bg-hover': 'var(--color-affirm-surface-hover)',
'affirm-soft-fg': 'var(--color-affirm-text)',
'affirm-outline-border': 'var(--color-affirm-border)',
'affirm-outline-fg': 'var(--color-affirm-text)',
'fulfill-solid-bg': 'var(--color-fulfill-solid)',
'fulfill-solid-bg-hover': 'var(--color-fulfill-solid-hover)',
'fulfill-soft-bg': 'var(--color-fulfill-surface)',
'fulfill-soft-bg-hover': 'var(--color-fulfill-surface-hover)',
'fulfill-soft-fg': 'var(--color-fulfill-text)',
'fulfill-outline-border': 'var(--color-fulfill-border)',
'fulfill-outline-fg': 'var(--color-fulfill-text)',
'risk-solid-bg': 'var(--color-risk-solid)',
'risk-solid-bg-hover': 'var(--color-risk-solid-hover)',
'risk-soft-bg': 'var(--color-risk-surface)',
'risk-soft-bg-hover': 'var(--color-risk-surface-hover)',
'risk-soft-fg': 'var(--color-risk-text)',
'risk-outline-border': 'var(--color-risk-border)',
'risk-outline-fg': 'var(--color-risk-text)',
'threat-solid-bg': 'var(--color-threat-solid)',
'threat-solid-bg-hover': 'var(--color-threat-solid-hover)',
'threat-soft-bg': 'var(--color-threat-surface)',
'threat-soft-bg-hover': 'var(--color-threat-surface-hover)',
'threat-soft-fg': 'var(--color-threat-text)',
'threat-outline-border': 'var(--color-threat-border)',
'threat-outline-fg': 'var(--color-threat-text)',
'loss-solid-bg': 'var(--color-loss-solid)',
'loss-solid-bg-hover': 'var(--color-loss-solid-hover)',
'loss-soft-bg': 'var(--color-loss-surface)',
'loss-soft-bg-hover': 'var(--color-loss-surface-hover)',
'loss-soft-fg': 'var(--color-loss-text)',
'loss-outline-border': 'var(--color-loss-border)',
'loss-outline-fg': 'var(--color-loss-text)',
// Private selected palette (TSC v2.1). 8 colors, 7 slots
// (solid-bg + solid-bg-hover for solid variant, soft-bg +
// soft-bg-hover + soft-fg for soft variant, outline-border +
// outline-fg for outline variant). All 3 variants share the
// same per-color cascade keyed on `data-color`.
'_selected-solid-bg': {
declarations: [
{ value: 'var(--tag-group-primary-solid-bg)', scope: 'host' },
{ value: 'var(--tag-group-secondary-solid-bg)', scope: 'color:secondary' },
{ value: 'var(--tag-group-neutral-solid-bg)', scope: 'color:neutral' },
{ value: 'var(--tag-group-affirm-solid-bg)', scope: 'color:affirm' },
{ value: 'var(--tag-group-fulfill-solid-bg)', scope: 'color:fulfill' },
{ value: 'var(--tag-group-risk-solid-bg)', scope: 'color:risk' },
{ value: 'var(--tag-group-threat-solid-bg)', scope: 'color:threat' },
{ value: 'var(--tag-group-loss-solid-bg)', scope: 'color:loss' }
]
},
'_selected-solid-bg-hover': {
declarations: [
{ value: 'var(--tag-group-primary-solid-bg-hover)', scope: 'host' },
{ value: 'var(--tag-group-secondary-solid-bg-hover)', scope: 'color:secondary' },
{ value: 'var(--tag-group-neutral-solid-bg-hover)', scope: 'color:neutral' },
{ value: 'var(--tag-group-affirm-solid-bg-hover)', scope: 'color:affirm' },
{ value: 'var(--tag-group-fulfill-solid-bg-hover)', scope: 'color:fulfill' },
{ value: 'var(--tag-group-risk-solid-bg-hover)', scope: 'color:risk' },
{ value: 'var(--tag-group-threat-solid-bg-hover)', scope: 'color:threat' },
{ value: 'var(--tag-group-loss-solid-bg-hover)', scope: 'color:loss' }
]
},
'_selected-soft-bg': {
declarations: [
{ value: 'var(--tag-group-primary-soft-bg)', scope: 'host' },
{ value: 'var(--tag-group-secondary-soft-bg)', scope: 'color:secondary' },
{ value: 'var(--tag-group-neutral-soft-bg)', scope: 'color:neutral' },
{ value: 'var(--tag-group-affirm-soft-bg)', scope: 'color:affirm' },
{ value: 'var(--tag-group-fulfill-soft-bg)', scope: 'color:fulfill' },
{ value: 'var(--tag-group-risk-soft-bg)', scope: 'color:risk' },
{ value: 'var(--tag-group-threat-soft-bg)', scope: 'color:threat' },
{ value: 'var(--tag-group-loss-soft-bg)', scope: 'color:loss' }
]
},
'_selected-soft-bg-hover': {
declarations: [
{ value: 'var(--tag-group-primary-soft-bg-hover)', scope: 'host' },
{ value: 'var(--tag-group-secondary-soft-bg-hover)', scope: 'color:secondary' },
{ value: 'var(--tag-group-neutral-soft-bg-hover)', scope: 'color:neutral' },
{ value: 'var(--tag-group-affirm-soft-bg-hover)', scope: 'color:affirm' },
{ value: 'var(--tag-group-fulfill-soft-bg-hover)', scope: 'color:fulfill' },
{ value: 'var(--tag-group-risk-soft-bg-hover)', scope: 'color:risk' },
{ value: 'var(--tag-group-threat-soft-bg-hover)', scope: 'color:threat' },
{ value: 'var(--tag-group-loss-soft-bg-hover)', scope: 'color:loss' }
]
},
'_selected-soft-fg': {
declarations: [
{ value: 'var(--tag-group-primary-soft-fg)', scope: 'host' },
{ value: 'var(--tag-group-secondary-soft-fg)', scope: 'color:secondary' },
{ value: 'var(--tag-group-neutral-soft-fg)', scope: 'color:neutral' },
{ value: 'var(--tag-group-affirm-soft-fg)', scope: 'color:affirm' },
{ value: 'var(--tag-group-fulfill-soft-fg)', scope: 'color:fulfill' },
{ value: 'var(--tag-group-risk-soft-fg)', scope: 'color:risk' },
{ value: 'var(--tag-group-threat-soft-fg)', scope: 'color:threat' },
{ value: 'var(--tag-group-loss-soft-fg)', scope: 'color:loss' }
]
},
'_selected-outline-border': {
declarations: [
{ value: 'var(--tag-group-primary-outline-border)', scope: 'host' },
{ value: 'var(--tag-group-secondary-outline-border)', scope: 'color:secondary' },
{ value: 'var(--tag-group-neutral-outline-border)', scope: 'color:neutral' },
{ value: 'var(--tag-group-affirm-outline-border)', scope: 'color:affirm' },
{ value: 'var(--tag-group-fulfill-outline-border)', scope: 'color:fulfill' },
{ value: 'var(--tag-group-risk-outline-border)', scope: 'color:risk' },
{ value: 'var(--tag-group-threat-outline-border)', scope: 'color:threat' },
{ value: 'var(--tag-group-loss-outline-border)', scope: 'color:loss' }
]
},
'_selected-outline-fg': {
declarations: [
{ value: 'var(--tag-group-primary-outline-fg)', scope: 'host' },
{ value: 'var(--tag-group-secondary-outline-fg)', scope: 'color:secondary' },
{ value: 'var(--tag-group-neutral-outline-fg)', scope: 'color:neutral' },
{ value: 'var(--tag-group-affirm-outline-fg)', scope: 'color:affirm' },
{ value: 'var(--tag-group-fulfill-outline-fg)', scope: 'color:fulfill' },
{ value: 'var(--tag-group-risk-outline-fg)', scope: 'color:risk' },
{ value: 'var(--tag-group-threat-outline-fg)', scope: 'color:threat' },
{ value: 'var(--tag-group-loss-outline-fg)', scope: 'color:loss' }
]
}
},
editable: {
'gap-xs': 'var(--space-0-5)',
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'gap-xl': 'var(--space-2-5)',
'control-height-xs': 'var(--control-height-xs)',
'control-height-sm': 'var(--control-height-sm)',
'control-height-md': 'var(--control-height-md)',
'control-height-lg': 'var(--control-height-lg)',
'control-height-xl': 'var(--control-height-xl)',
'control-px-xs': 'var(--space-1-5)',
'control-px-sm': 'var(--space-2)',
'control-px-md': 'var(--space-2-5)',
'control-px-lg': 'var(--space-3)',
'control-px-xl': 'var(--space-3-5)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
color: 'var(--color-content-primary)',
'area-min-inline-size': '12ch',
'control-radius': 'var(--radius-md)',
'control-border-width': 'var(--border-width)',
'control-border': 'var(--color-border-subtle)',
'control-border-hover': 'var(--color-border-default)',
'control-bg': 'var(--color-surface-default)',
'control-shadow': 'var(--shadow-subtle)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'placeholder-color': 'var(--color-content-muted)',
'trigger-gap': 'var(--space-1)',
'trigger-px': 'var(--space-2-5)',
'trigger-radius': 'var(--radius-md)',
'trigger-border-width': 'var(--border-width)',
'trigger-border': 'var(--color-border-subtle)',
'trigger-border-hover': 'var(--color-border-default)',
'trigger-bg': 'var(--color-surface-raised)',
'trigger-bg-hover': 'var(--color-surface-overlay)',
'trigger-color': 'var(--color-content-secondary)',
'trigger-color-hover': 'var(--color-content-primary)',
'submit-color': 'var(--color-content-on-solid)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)',
'primary-solid': 'var(--color-primary-solid)',
'secondary-solid': 'var(--color-secondary-solid)',
'neutral-solid': 'var(--color-neutral-solid)',
'affirm-solid': 'var(--color-affirm-solid)',
'risk-solid': 'var(--color-risk-solid)',
'threat-solid': 'var(--color-threat-solid)',
// Restored after accidental sed-removal during switch migration.
'primary-border': 'var(--color-primary-border)',
'secondary-border': 'var(--color-secondary-border)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-border': 'var(--color-affirm-border)',
'risk-border': 'var(--color-risk-border)',
'threat-border': 'var(--color-threat-border)',
// Private accent palette (TSC v2.1). Only solid + border +
// solid-hover are consumed; track/text declared pre-TSC but unused.
'_accent-solid': {
declarations: [
{ value: 'var(--editable-primary-solid)', scope: 'host' },
{ value: 'var(--editable-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--editable-neutral-solid)', scope: 'color:neutral' },
{ value: 'var(--editable-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--editable-risk-solid)', scope: 'color:risk' },
{ value: 'var(--editable-threat-solid)', scope: 'color:threat' }
]
},
'_accent-border': {
declarations: [
{ value: 'var(--editable-primary-border)', scope: 'host' },
{ value: 'var(--editable-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--editable-neutral-border)', scope: 'color:neutral' },
{ value: 'var(--editable-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--editable-risk-border)', scope: 'color:risk' },
{ value: 'var(--editable-threat-border)', scope: 'color:threat' }
]
},
'_accent-solid-hover': {
declarations: [
{ value: 'var(--color-primary-solid-hover)', scope: 'host' },
{ value: 'var(--color-secondary-solid-hover)', scope: 'color:secondary' },
{ value: 'var(--color-neutral-solid-hover)', scope: 'color:neutral' },
{ value: 'var(--color-affirm-solid-hover)', scope: 'color:affirm' },
{ value: 'var(--color-risk-solid-hover)', scope: 'color:risk' },
{ value: 'var(--color-threat-solid-hover)', scope: 'color:threat' }
]
}
},
words: {
// xs is the small-control tier (inspector fields, swatches, chips, steppers):
// one step under `sm`. Replaces the ad-hoc 4px / var(--radius-2,4px) / 3px /
// var(--space-1) soup the inspector used for the same thing.
'radius-xs': 'var(--radius-sm)',
'radius-sm': 'var(--radius-md)',
'radius-md': 'var(--radius-lg)',
'radius-lg': 'var(--radius-xl)',
'border-width': 'var(--border-width)',
border: 'var(--color-border-subtle)',
'border-hover': 'var(--color-border-default)',
bg: 'var(--color-surface-raised)',
shadow: 'var(--shadow-subtle)',
color: 'var(--color-content-primary)',
'font-family': 'var(--font-ui)',
'font-size-sm': 'var(--font-size-xs)',
'font-size-md': 'var(--font-size-sm)',
'font-size-lg': 'var(--font-size-md)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'invalid-border': 'var(--color-risk-border)',
'disabled-opacity': 'var(--opacity-disabled)',
// Smallest doc-chrome size (breadcrumb, cell labels) — one step under `sm`.
'font-size-xs': '0.6875rem',
// Wash scale: ONE canonical set of color-mix opacities for the editor's
// accent/role tints (header washes, hover fills, glows, focus rings).
// Replaces the ad-hoc 4/8/12/14/16/22/30/35/40/45/50 % soup with 4 steps.
// They are percentages, applied to whatever base color each surface mixes.
'wash-faint': '6%',
'wash-subtle': '16%',
'wash-soft': '30%',
'wash-strong': '44%',
// Per-swatch hue, set inline by the bubble/inspector color grids; the
// declared default is the surface bg (an unset swatch reads as empty).
swatch: 'var(--words-bg)',
// The CHROME typeface (toolbar · bubble · drawer · inspector · menus). Always a
// UI sans, INDEPENDENT of `font-family` (the content/reading font, which a theme
// may set to serif). Keeps every control + label in one face.
'chrome-font': 'var(--font-ui)',
// The inline-control tier height — every control sitting on an inspector
// row (number-field, colour trigger, ToggleGroup chip) shares ONE height
// so a row of mixed controls aligns. 1.75rem (28px) was the number-field's
// magic height; named here and applied uniformly to kill the 26/28/36 mix.
'control-height': '1.75rem',
// Fixed width for the right-hand control in an inline inspector row so the
// panel's right edge reads as a clean column (number-field was 6.75rem,
// colour trigger 7.5rem — a ragged edge). 7.5rem fits the 8-char alpha hex.
'field-width': '7.5rem',
'command-gap': 'var(--space-1)',
'command-radius': 'var(--radius-md)',
'command-color': 'var(--color-content-secondary)',
'command-solid-color': 'var(--color-content-on-solid)',
'placeholder-color': 'var(--color-content-muted)',
'content-color': 'var(--color-content-primary)',
'content-py-sm': 'var(--space-2-5)',
'content-py-md': 'var(--space-3)',
'content-py-lg': 'var(--space-4)',
'content-min-block-size-sm': '10rem',
'content-min-block-size-md': '13rem',
'content-min-block-size-lg': '16rem',
'content-max-block-size-sm': '50vh',
'content-max-block-size-md': '60vh',
'content-max-block-size-lg': '70vh',
// Document rhythm: airier than the default UI gap so blocks read
// as paragraphs in a document, not stacked form rows. Headings
// derive 1.5x of this for their top margin.
'block-gap': 'var(--space-3)',
'heading-color': 'var(--color-content-primary)',
'heading-h1-font-size': 'var(--font-size-xl)',
'heading-h2-font-size': 'calc(var(--font-size-lg) + 4px)',
'heading-h3-font-size': 'var(--font-size-lg)',
'heading-font-weight': 'var(--font-weight-semibold)',
'heading-line-height': 'var(--leading-heading)',
'strong-font-weight': 'var(--font-weight-semibold)',
'code-radius': 'var(--radius-sm)',
'code-bg': 'var(--color-surface-overlay)',
'code-color': 'var(--color-content-primary)',
'code-font-family': 'var(--font-mono)',
'code-py': 'var(--space-0-5)',
'code-px': 'var(--space-1)',
'quote-px': 'var(--space-3)',
'quote-border-width': 'var(--border-width-medium)',
'quote-color': 'var(--color-content-secondary)',
'list-padding-inline-start': 'var(--space-5)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'primary-solid': 'var(--color-primary-solid)',
'primary-text': 'var(--color-primary-text)',
'secondary-text': 'var(--color-secondary-text)',
'affirm-text': 'var(--color-affirm-text)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-text': 'var(--color-fulfill-text)',
'risk-text': 'var(--color-risk-text)',
'threat-solid': 'var(--color-threat-solid)',
'threat-text': 'var(--color-threat-text)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-text': 'var(--color-loss-text)',
// Restored after accidental sed-removal during switch migration.
// Consumed by words.css for code/link/blockquote color cues.
'primary-border': 'var(--color-primary-border)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)'
},
'tags-input': {
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1-5)',
'gap-md': 'var(--space-2)',
'gap-lg': 'var(--space-2-5)',
'gap-xl': 'var(--space-3)',
'control-min-height-xs': 'var(--control-height-sm)',
'control-min-height-sm': 'var(--control-height-md)',
'control-min-height-md': 'var(--control-height-lg)',
'control-min-height-lg': 'calc(var(--control-height-lg) + var(--space-2))',
'control-min-height-xl': 'calc(var(--control-height-xl) + var(--space-2))',
'control-px-xs': 'var(--space-1-5)',
'control-px-sm': 'var(--space-2)',
'control-px-md': 'var(--space-2-5)',
'control-px-lg': 'var(--space-3)',
'control-px-xl': 'var(--space-3-5)',
'control-py-xs': 'var(--space-0-5)',
'control-py-sm': 'var(--space-1)',
'control-py-md': 'var(--space-1-5)',
'control-py-lg': 'var(--space-2)',
'control-py-xl': 'var(--space-2-5)',
'item-height-xs': 'var(--control-height-xs)',
'item-height-sm': 'var(--control-height-sm)',
'item-height-md': 'var(--control-height-md)',
'item-height-lg': 'var(--control-height-lg)',
'item-height-xl': 'var(--control-height-xl)',
'item-px-xs': 'var(--space-1-5)',
'item-px-sm': 'var(--space-2)',
'item-px-md': 'var(--space-2-5)',
'item-px-lg': 'var(--space-3)',
'item-px-xl': 'var(--space-3-5)',
'font-family': 'var(--font-ui)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'line-height': 'var(--leading-ui)',
color: 'var(--color-content-primary)',
'item-gap': 'var(--space-1-5)',
'control-radius': 'var(--radius-lg)',
'control-border-width': 'var(--border-width)',
'control-border': 'var(--color-border-default)',
'control-border-hover': 'var(--color-border-strong)',
'control-bg': 'var(--color-surface-default)',
'control-shadow': 'var(--shadow-subtle)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'input-min-inline-size': '8ch',
'placeholder-color': 'var(--color-content-muted)',
'item-radius': 'var(--radius-full)',
'item-border-width': 'var(--border-width)',
'item-border': 'var(--color-border-subtle)',
'item-bg': 'var(--color-surface-overlay)',
'item-color': 'var(--color-content-primary)',
'action-gap': 'var(--space-1)',
'action-px': 'var(--space-2-5)',
'action-radius': 'var(--radius-md)',
'action-border-width': 'var(--border-width)',
'action-border': 'transparent',
'action-border-hover': 'var(--color-border-subtle)',
'action-bg': 'transparent',
'action-bg-hover': 'color-mix(in srgb, currentColor 10%, transparent)',
'action-color': 'var(--color-content-secondary)',
'action-color-hover': 'var(--color-content-primary)',
'delete-size': 'var(--control-height-xs)',
'delete-radius': 'var(--radius-full)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)',
'primary-text': 'var(--color-primary-text)',
'secondary-text': 'var(--color-secondary-text)',
'neutral-text': 'var(--color-neutral-text)',
'affirm-text': 'var(--color-affirm-text)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-text': 'var(--color-fulfill-text)',
'risk-text': 'var(--color-risk-text)',
'threat-text': 'var(--color-threat-text)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-text': 'var(--color-loss-text)',
// Restored after accidental sed-removal during switch migration.
// Consumed by tags-input.css for tag chip color cues per intent.
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)',
// Private accent palette (TSC v2.1). 8 colors, 3 slots (track,
// border, text). `solid` was declared pre-TSC but never consumed.
'_accent-track': {
declarations: [
{ value: 'var(--tags-input-primary-track)', scope: 'host' },
{ value: 'var(--tags-input-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--tags-input-neutral-track)', scope: 'color:neutral' },
{ value: 'var(--tags-input-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--tags-input-fulfill-track)', scope: 'color:fulfill' },
{ value: 'var(--tags-input-risk-track)', scope: 'color:risk' },
{ value: 'var(--tags-input-threat-track)', scope: 'color:threat' },
{ value: 'var(--tags-input-loss-track)', scope: 'color:loss' }
]
},
'_accent-border': {
declarations: [
{ value: 'var(--tags-input-primary-border)', scope: 'host' },
{ value: 'var(--tags-input-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--tags-input-neutral-border)', scope: 'color:neutral' },
{ value: 'var(--tags-input-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--tags-input-fulfill-border)', scope: 'color:fulfill' },
{ value: 'var(--tags-input-risk-border)', scope: 'color:risk' },
{ value: 'var(--tags-input-threat-border)', scope: 'color:threat' },
{ value: 'var(--tags-input-loss-border)', scope: 'color:loss' }
]
},
'_accent-text': {
declarations: [
{ value: 'var(--tags-input-primary-text)', scope: 'host' },
{ value: 'var(--tags-input-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--tags-input-neutral-text)', scope: 'color:neutral' },
{ value: 'var(--tags-input-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--tags-input-fulfill-text)', scope: 'color:fulfill' },
{ value: 'var(--tags-input-risk-text)', scope: 'color:risk' },
{ value: 'var(--tags-input-threat-text)', scope: 'color:threat' },
{ value: 'var(--tags-input-loss-text)', scope: 'color:loss' }
]
}
},
stepper: {
'gap-sm': 'var(--space-3)',
'gap-md': 'var(--space-4)',
'gap-lg': 'var(--space-5)',
'indicator-size-sm': 'var(--control-height-sm)',
'indicator-size-md': 'var(--control-height-md)',
'indicator-size-lg': 'var(--control-height-lg)',
'font-family': 'var(--font-ui)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'line-height': 'var(--leading-ui)',
color: 'var(--color-content-primary)',
'list-gap': 'var(--space-1)',
'item-gap': 'var(--space-2)',
'trigger-gap': 'var(--space-2)',
'trigger-px': 'var(--space-2)',
'trigger-py': 'var(--space-1)',
'trigger-radius': 'var(--radius-md)',
'trigger-border-width': 'var(--border-width)',
'trigger-color': 'var(--color-content-secondary)',
'trigger-color-hover': 'var(--color-content-primary)',
'trigger-bg-hover': 'var(--color-surface-overlay)',
'indicator-radius': 'var(--radius-full)',
'indicator-border-width': 'var(--border-width)',
'indicator-border': 'var(--color-border-default)',
'indicator-bg': 'var(--color-surface-raised)',
'indicator-color': 'var(--color-content-secondary)',
'indicator-font-weight': 'var(--font-weight-semibold)',
'complete-color': 'var(--color-content-on-solid)',
'separator-min-size': '3rem',
'separator-thickness': 'var(--border-width-medium)',
'separator-radius': 'var(--radius-full)',
'separator-bg': 'var(--color-border-subtle)',
'content-padding': 'var(--space-4)',
'content-radius': 'var(--radius-lg)',
'content-border-width': 'var(--border-width)',
'content-border': 'var(--color-border-subtle)',
'content-bg': 'var(--color-surface-raised)',
'content-shadow': 'var(--shadow-subtle)',
'nav-height': 'var(--control-height-md)',
'nav-px': 'var(--space-3)',
'nav-radius': 'var(--radius-md)',
'nav-border-width': 'var(--border-width)',
'nav-border': 'var(--color-border-subtle)',
'nav-bg': 'var(--color-surface-raised)',
'nav-color': 'var(--color-content-primary)',
'nav-hover-brightness': '1.04',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)',
'primary-solid': 'var(--color-primary-solid)',
'primary-text': 'var(--color-primary-text)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-text': 'var(--color-secondary-text)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-text': 'var(--color-neutral-text)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-text': 'var(--color-affirm-text)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-text': 'var(--color-fulfill-text)',
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
// Private accent palette (TSC v2.1). Per-color cascade in config.
'_accent-solid': {
declarations: [
{ value: 'var(--stepper-primary-solid)', scope: 'host' },
{ value: 'var(--stepper-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--stepper-neutral-solid)', scope: 'color:neutral' },
{ value: 'var(--stepper-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--stepper-fulfill-solid)', scope: 'color:fulfill' }
]
},
'_accent-track': {
declarations: [
{ value: 'var(--stepper-primary-track)', scope: 'host' },
{ value: 'var(--stepper-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--stepper-neutral-track)', scope: 'color:neutral' },
{ value: 'var(--stepper-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--stepper-fulfill-track)', scope: 'color:fulfill' }
]
},
'_accent-border': {
declarations: [
{ value: 'var(--stepper-primary-border)', scope: 'host' },
{ value: 'var(--stepper-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--stepper-neutral-border)', scope: 'color:neutral' },
{ value: 'var(--stepper-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--stepper-fulfill-border)', scope: 'color:fulfill' }
]
},
'_accent-text': {
declarations: [
{ value: 'var(--stepper-primary-text)', scope: 'host' },
{ value: 'var(--stepper-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--stepper-neutral-text)', scope: 'color:neutral' },
{ value: 'var(--stepper-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--stepper-fulfill-text)', scope: 'color:fulfill' }
]
}
},
slider: {
'track-size-xs': '3px',
'track-size-sm': '4px',
'track-size-md': '6px',
'track-size-lg': '8px',
'track-size-xl': '10px',
'thumb-size-xs': '12px',
'thumb-size-sm': '16px',
'thumb-size-md': '20px',
'thumb-size-lg': '24px',
'thumb-size-xl': '28px',
'hit-size-xs': '24px',
'hit-size-sm': '28px',
'hit-size-md': '32px',
'hit-size-lg': '36px',
'hit-size-xl': '40px',
'tick-size-xs': '3px',
'tick-size-sm': '4px',
'tick-size-md': '5px',
'tick-size-lg': '6px',
'tick-size-xl': '7px',
'min-inline-size': '12rem',
'min-block-size': '10rem',
'track-radius': 'var(--radius-full)',
// The unfilled groove. `--color-neutral-track` equals the surface in the
// base theme (invisible against panels), so the track uses the same
// visible-track mix as Progress: a light tint of content-primary on the
// surface. Reads on white AND dark, and follows the theme.
'track-bg':
'color-mix(in srgb, var(--color-content-primary) 12%, var(--color-surface-default))',
'range-bg': 'var(--color-primary-solid)',
'thumb-radius': 'var(--radius-full)',
'thumb-border-width': 'var(--border-width)',
'thumb-border': 'var(--color-border-default)',
'thumb-bg': 'var(--color-surface-default)',
'thumb-shadow': 'var(--shadow-subtle)',
// Pickup lift WHILE grabbed (pointer :active): the thumb scales toward the
// viewer + rides a higher shadow — the draggable-surface lift doctrine
// (THEMING §14), scaled up from the surface `--motion-scale-lift` (1.02)
// because a ~20px thumb needs a larger multiplier to read as a lift.
'thumb-scale-active': '1.15',
'thumb-shadow-active': 'var(--shadow-3)',
'thumb-focus-shadow':
'0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color), var(--slider-thumb-shadow)',
'tick-radius': 'var(--radius-full)',
'tick-bg': 'var(--color-neutral-border)',
'tick-bg-active': 'var(--color-primary-solid)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
},
switch: {
'size-xs-track-width': '26px',
'size-xs-track-height': '15px',
'size-xs-thumb-size': '11px',
'size-xs-thumb-translate': '12px',
'size-sm-track-width': '32px',
'size-sm-track-height': '18px',
'size-sm-thumb-size': '14px',
'size-sm-thumb-translate': '14px',
'size-md-track-width': '38px',
'size-md-track-height': '22px',
'size-md-thumb-size': '18px',
'size-md-thumb-translate': '16px',
'size-lg-track-width': '46px',
'size-lg-track-height': '28px',
'size-lg-thumb-size': '24px',
'size-lg-thumb-translate': '18px',
'size-xl-track-width': '54px',
'size-xl-track-height': '34px',
'size-xl-thumb-size': '30px',
'size-xl-thumb-translate': '20px',
'track-radius': 'var(--radius-full)',
'track-border-width': 'var(--border-width)',
'thumb-inset': '1px',
'thumb-radius': 'var(--radius-full)',
'thumb-border-width': 'var(--border-width)',
'thumb-shadow': '0 1px 2px rgb(17 14 10 / 0.12)',
'focus-ring-offset': 'var(--focus-ring-offset)',
'focus-ring-width': 'var(--focus-ring-width)',
'focus-ring-color': 'var(--focus-ring-color)',
'focus-ring-surface': 'var(--color-surface-default)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'track-bg-off': 'var(--color-surface-overlay)',
'track-bg-off-hover':
'color-mix( in srgb, var(--color-surface-overlay) 82%, var(--color-surface-raised) )',
'track-border-off': 'var(--color-border-default)',
'track-border-off-hover': 'var(--color-border-strong)',
'track-bg-disabled':
'color-mix( in srgb, var(--color-surface-overlay) 70%, var(--color-surface-default) )',
'track-border-disabled':
'color-mix( in srgb, var(--color-border-default) 70%, var(--color-border-subtle) )',
'thumb-bg-disabled':
'color-mix( in srgb, var(--color-surface-default) 88%, var(--color-surface-raised) )',
'thumb-border-disabled':
'color-mix( in srgb, var(--color-border-subtle) 88%, var(--color-surface-overlay) )',
'disabled-opacity': 'var(--opacity-disabled)',
'thumb-bg': 'var(--color-surface-default)',
'thumb-border':
'color-mix( in srgb, var(--color-border-default) 82%, var(--color-surface-overlay) )',
'thumb-icon-color': 'var(--color-content-muted)',
'primary-solid': 'var(--color-primary-solid)',
'primary-solid-hover': 'var(--color-primary-solid-hover)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-solid-hover': 'var(--color-neutral-solid-hover)',
'risk-solid': 'var(--color-risk-solid)',
'risk-solid-hover': 'var(--color-risk-solid-hover)',
'threat-solid': 'var(--color-threat-solid)',
'threat-solid-hover': 'var(--color-threat-solid-hover)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-solid-hover': 'var(--color-secondary-solid-hover)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-solid-hover': 'var(--color-affirm-solid-hover)',
// Private palette (TSC v2.1). Host default = neutral, plus
// per-color overrides. Migrated from manual blocks in switch.css.
// Only solid/solid-hover are actively consumed; track/border
// existed in the pre-TSC defaults but were never referenced.
'_palette-solid': {
declarations: [
{ value: 'var(--switch-neutral-solid)', scope: 'host' },
{ value: 'var(--switch-primary-solid)', scope: 'color:primary' },
{ value: 'var(--switch-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--switch-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--switch-risk-solid)', scope: 'color:risk' },
{ value: 'var(--switch-threat-solid)', scope: 'color:threat' }
]
},
'_palette-solid-hover': {
declarations: [
{ value: 'var(--switch-neutral-solid-hover)', scope: 'host' },
{ value: 'var(--switch-primary-solid-hover)', scope: 'color:primary' },
{ value: 'var(--switch-secondary-solid-hover)', scope: 'color:secondary' },
{ value: 'var(--switch-affirm-solid-hover)', scope: 'color:affirm' },
{ value: 'var(--switch-risk-solid-hover)', scope: 'color:risk' },
{ value: 'var(--switch-threat-solid-hover)', scope: 'color:threat' }
]
}
},
tabs: {
'stack-gap': 'var(--space-4)',
'line-stack-gap': 'var(--space-3)',
'provider-radius': 'var(--radius-xl)',
'provider-border-width': 'var(--border-width)',
'provider-border': 'var(--color-border-default)',
'provider-bg': 'var(--color-surface-raised)',
'surface-provider-padding': 'var(--space-3)',
'surface-provider-shadow': 'var(--shadow-subtle)',
'list-gap': 'var(--space-1)',
'list-padding': 'var(--space-1)',
'list-radius': 'var(--radius-lg)',
'list-width': '220px',
'list-bg': 'transparent',
'list-border-width': 'var(--border-width)',
'list-border': 'var(--color-border-default)',
'line-list-padding-inline-end': 'var(--space-2)',
'surface-list-bg': 'color-mix(in srgb, var(--color-surface-default) 82%, transparent)',
'pills-list-bg': 'color-mix(in srgb, var(--color-surface-default) 90%, transparent)',
'trigger-height-xs': 'var(--control-height-xs)',
'trigger-height-sm': 'var(--control-height-sm)',
'trigger-height-md': 'var(--control-height-md)',
'trigger-height-lg': 'var(--control-height-lg)',
'trigger-px-xs': 'var(--space-2-5)',
'trigger-px-sm': 'var(--space-3)',
'trigger-px-md': 'var(--space-3-5)',
'trigger-px-lg': 'var(--space-4)',
'trigger-gap-xs': 'var(--space-1)',
'trigger-gap-sm': 'var(--space-1-5)',
'trigger-gap-md': 'var(--space-2)',
'trigger-gap-lg': 'var(--space-2)',
'trigger-radius': 'var(--radius-md)',
'trigger-border-width': 'var(--border-width)',
'trigger-font-family': 'var(--font-ui)',
'trigger-font-size': 'var(--font-size-sm)',
'trigger-font-weight': 'var(--font-weight-medium)',
'trigger-line-height': 'var(--leading-ui)',
'trigger-letter-spacing': 'var(--tracking-ui)',
'trigger-color': 'var(--color-content-secondary)',
'trigger-color-hover': 'var(--color-content-primary)',
'trigger-color-active': 'var(--color-content-primary)',
'trigger-bg': 'transparent',
'trigger-bg-hover': 'color-mix( in srgb, var(--color-surface-default) 84%, transparent )',
'trigger-bg-active': 'var(--color-surface-overlay)',
'trigger-border': 'transparent',
'trigger-border-active': 'var(--color-primary-border)',
'trigger-transition-duration': 'var(--duration-fast)',
'trigger-transition-ease': 'var(--ease-default)',
'trigger-focus-ring-offset': 'var(--focus-ring-offset)',
'trigger-focus-ring-width': 'var(--focus-ring-width)',
'trigger-focus-ring-color': 'var(--focus-ring-color)',
'surface-trigger-border-active':
'color-mix( in srgb, var(--tabs-trigger-border-active) 58%, transparent )',
'pills-trigger-radius': 'var(--radius-full)',
'pills-trigger-border-active':
'color-mix( in srgb, var(--tabs-trigger-border-active) 58%, transparent )',
'pills-trigger-shadow-active': 'var(--shadow-subtle)',
'content-padding-xs': 'var(--space-3)',
'content-padding-sm': 'var(--space-4)',
'content-padding-md': 'var(--space-5)',
'content-padding-lg': 'var(--space-6)',
'content-radius': 'var(--radius-lg)',
'content-bg': 'transparent',
'content-border-width': 'var(--border-width)',
'content-border': 'transparent',
'content-color': 'var(--color-content-primary)',
'surface-content-border': 'color-mix( in srgb, var(--tabs-provider-border) 84%, transparent )',
'surface-content-bg': 'color-mix( in srgb, var(--color-surface-overlay) 92%, transparent )',
'pills-content-padding-block-start': 'var(--space-2)',
'disabled-opacity': 'var(--opacity-disabled)',
'indicator-transition-duration': 'var(--duration-normal)',
'indicator-transition-ease': 'cubic-bezier(0.32, 0.72, 0, 1)',
'indicator-line-thickness': '2px',
'scrollable-fade': '16px'
},
's-text': {
'hint-color': 'var(--color-content-secondary)',
'hint-opacity': '0.8',
'hint-margin-top': 'var(--space-1)'
},
's-text-virtual-list': {
'line-height': 'var(--leading-prose)',
color: 'var(--color-content-primary)',
bg: 'var(--color-surface-default)',
'border-width': 'var(--border-width)',
'border-color': 'var(--color-border-subtle)',
radius: 'var(--radius-md)',
'row-px': 'var(--space-3)',
'row-border-width': 'var(--border-width)',
'row-border-color': 'var(--color-border-subtle)'
},
toast: {
'toaster-z': '1200',
'toaster-inset': 'var(--space-4)',
'toaster-gap': 'var(--space-3)',
'toaster-width': '420px',
'provider-min-width': '280px',
'provider-max-width': '420px',
'provider-gap': 'var(--space-3)',
'provider-stack-gap': 'var(--space-1)',
'provider-px': 'var(--space-4)',
'provider-py': 'var(--space-3)',
'provider-border-width': 'var(--border-width)',
'provider-radius': 'var(--radius-lg)',
'provider-shadow': 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'accent-width': 'var(--border-width-thick)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'closed-offset': '10px',
'closed-scale': '0.98',
'status-size': '32px',
'status-radius': 'var(--radius-full)',
'title-font-family': 'var(--font-ui)',
'title-font-size': 'var(--font-size-sm)',
'title-font-weight': 'var(--font-weight-semibold)',
'title-line-height': 'var(--leading-ui)',
'title-color': 'var(--color-content-primary)',
'message-color': 'var(--color-content-primary)',
'description-font-family': 'var(--font-ui)',
'description-font-size': 'var(--font-size-xs)',
'description-line-height': 'var(--leading-prose)',
'description-color': 'var(--color-content-secondary)',
'action-gap': 'var(--space-1)',
'action-px': 'var(--space-2)',
'action-py': 'var(--space-1)',
'action-margin-top': 'var(--space-1)',
'action-border-width': 'var(--border-width)',
'action-radius': 'var(--radius-md)',
'action-font-family': 'var(--font-ui)',
'action-font-size': 'var(--font-size-xs)',
'action-font-weight': 'var(--font-weight-medium)',
'action-line-height': 'var(--leading-ui)',
'action-focus-ring-offset': 'var(--focus-ring-offset)',
'action-focus-ring-width': 'var(--focus-ring-width)',
'action-focus-ring-color': 'var(--focus-ring-color)',
'close-size': '28px',
'close-border-width': 'var(--border-width)',
'close-radius': 'var(--radius-full)',
'close-bg': 'transparent',
'close-border': 'transparent',
'close-color': 'var(--color-content-secondary)',
'close-hover-bg': 'var(--color-surface-raised)',
'close-hover-border': 'var(--color-border-subtle)',
'close-hover-color': 'var(--color-content-primary)',
'close-focus-ring-offset': 'var(--focus-ring-offset)',
'close-focus-ring-width': 'var(--focus-ring-width)',
'close-focus-ring-color': 'var(--focus-ring-color)',
'default-surface':
'color-mix( in srgb, var(--color-surface-overlay) 92%, var(--color-neutral-track) )',
'default-border':
'color-mix( in srgb, var(--color-neutral-border) 72%, var(--color-border-default) )',
'default-accent': 'var(--color-neutral-border)',
'default-status-bg': 'var(--color-neutral-track)',
'default-status-color': 'var(--color-neutral-text)',
'default-action-color': 'var(--color-neutral-text)',
'default-action-hover-bg': 'var(--color-neutral-track)',
'affirm-surface':
'color-mix( in srgb, var(--color-surface-overlay) 90%, var(--color-affirm-track) )',
'affirm-border':
'color-mix( in srgb, var(--color-affirm-border) 70%, var(--color-border-default) )',
'affirm-accent': 'var(--color-affirm-border)',
'affirm-status-bg': 'var(--color-affirm-track)',
'affirm-status-color': 'var(--color-affirm-text)',
'affirm-action-color': 'var(--color-affirm-text)',
'affirm-action-hover-bg': 'var(--color-affirm-track)',
'fulfill-surface':
'color-mix( in srgb, var(--color-surface-overlay) 90%, var(--color-fulfill-track) )',
'fulfill-border':
'color-mix( in srgb, var(--color-fulfill-border) 70%, var(--color-border-default) )',
'fulfill-accent': 'var(--color-fulfill-border)',
'fulfill-status-bg': 'var(--color-fulfill-track)',
'fulfill-status-color': 'var(--color-fulfill-text)',
'fulfill-action-color': 'var(--color-fulfill-text)',
'fulfill-action-hover-bg': 'var(--color-fulfill-track)',
'risk-surface':
'color-mix( in srgb, var(--color-surface-overlay) 90%, var(--color-risk-track) )',
'risk-border':
'color-mix( in srgb, var(--color-risk-border) 72%, var(--color-border-default) )',
'risk-accent': 'var(--color-risk-border)',
'risk-status-bg': 'var(--color-risk-track)',
'risk-status-color': 'var(--color-risk-text)',
'risk-action-color': 'var(--color-risk-text)',
'risk-action-hover-bg': 'var(--color-risk-track)',
'threat-surface':
'color-mix( in srgb, var(--color-surface-overlay) 90%, var(--color-threat-track) )',
'threat-border':
'color-mix( in srgb, var(--color-threat-border) 72%, var(--color-border-default) )',
'threat-accent': 'var(--color-threat-border)',
'threat-status-bg': 'var(--color-threat-track)',
'threat-status-color': 'var(--color-threat-text)',
'threat-action-color': 'var(--color-threat-text)',
'threat-action-hover-bg': 'var(--color-threat-track)',
'loss-surface':
'color-mix( in srgb, var(--color-surface-overlay) 90%, var(--color-loss-track) )',
'loss-border':
'color-mix( in srgb, var(--color-loss-border) 72%, var(--color-border-default) )',
'loss-accent': 'var(--color-loss-border)',
'loss-status-bg': 'var(--color-loss-track)',
'loss-status-color': 'var(--color-loss-text)',
'loss-action-color': 'var(--color-loss-text)',
'loss-action-hover-bg': 'var(--color-loss-track)'
},
toggle: {
'height-xs': 'var(--control-height-xs)',
'height-sm': 'var(--control-height-sm)',
'height-md': 'var(--control-height-md)',
'height-lg': 'var(--control-height-lg)',
'height-xl': 'var(--control-height-xl)',
'px-xs': 'var(--space-2)',
'px-sm': 'var(--space-2-5)',
'px-md': 'var(--space-3-5)',
'px-lg': 'var(--space-4-5)',
'px-xl': 'var(--space-5)',
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1-5)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'gap-xl': 'var(--space-2-5)',
'font-family': 'var(--font-ui)',
'line-height': 'var(--leading-ui)',
'letter-spacing': 'var(--tracking-normal)',
'font-size-xs': 'var(--font-size-xs)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-size-xl': 'var(--font-size-xl)',
'font-weight-xs': 'var(--font-weight-medium)',
'font-weight-sm': 'var(--font-weight-medium)',
'font-weight-md': 'var(--font-weight-medium)',
'font-weight-lg': 'var(--font-weight-medium)',
'font-weight-xl': 'var(--font-weight-medium)',
'radius-xs': 'var(--radius-sm)',
'radius-sm': 'var(--radius-sm)',
'radius-md': 'var(--radius-md)',
'radius-lg': 'var(--radius-md)',
'radius-xl': 'var(--radius-md)',
'border-width': 'var(--border-width)',
shadow: 'none',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
// Per-color forwarders consumed by the palette-* cascades below.
// 8 slots per color (track / element / hover / border / solid /
// solid-hover / text / contrast) matching the 8 palette-* tokens
// the cascades target. Missing any forwarder makes the
// corresponding palette-{slot} resolve to empty for that color,
// which downstream collapses any derived token consuming it
// (e.g. ghost-on-bg uses palette-track).
'primary-track': 'var(--color-primary-track)',
'primary-element': 'var(--color-primary-element)',
'primary-hover': 'var(--color-primary-hover)',
'primary-border': 'var(--color-primary-border)',
'primary-solid': 'var(--color-primary-solid)',
'primary-solid-hover': 'var(--color-primary-solid-hover)',
'primary-text': 'var(--color-primary-text)',
'primary-contrast': 'var(--color-primary-contrast)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-element': 'var(--color-neutral-element)',
'neutral-hover': 'var(--color-neutral-hover)',
'neutral-border': 'var(--color-neutral-border)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-solid-hover': 'var(--color-neutral-solid-hover)',
'neutral-text': 'var(--color-neutral-text)',
'neutral-contrast': 'var(--color-neutral-contrast)',
'risk-track': 'var(--color-risk-track)',
'risk-element': 'var(--color-risk-element)',
'risk-hover': 'var(--color-risk-hover)',
'risk-border': 'var(--color-risk-border)',
'risk-solid': 'var(--color-risk-solid)',
'risk-solid-hover': 'var(--color-risk-solid-hover)',
'risk-text': 'var(--color-risk-text)',
'risk-contrast': 'var(--color-risk-contrast)',
'threat-track': 'var(--color-threat-track)',
'threat-element': 'var(--color-threat-element)',
'threat-hover': 'var(--color-threat-hover)',
'threat-border': 'var(--color-threat-border)',
'threat-solid': 'var(--color-threat-solid)',
'threat-solid-hover': 'var(--color-threat-solid-hover)',
'threat-text': 'var(--color-threat-text)',
'threat-contrast': 'var(--color-threat-contrast)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-element': 'var(--color-secondary-element)',
'secondary-hover': 'var(--color-secondary-hover)',
'secondary-border': 'var(--color-secondary-border)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-solid-hover': 'var(--color-secondary-solid-hover)',
'secondary-text': 'var(--color-secondary-text)',
'secondary-contrast': 'var(--color-secondary-contrast)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-element': 'var(--color-affirm-element)',
'affirm-hover': 'var(--color-affirm-hover)',
'affirm-border': 'var(--color-affirm-border)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-solid-hover': 'var(--color-affirm-solid-hover)',
'affirm-text': 'var(--color-affirm-text)',
'affirm-contrast': 'var(--color-affirm-contrast)',
// Palette tokens: 8 custom properties, each declared 7 times.
// One default declaration at scope 'host' (the neutral color), plus
// one override per supported color value (scope 'color:X'). The
// generator emits them as a `[data-toggle]` block (default) plus
// six `[data-toggle][data-color='X']` blocks (overrides). The CSS
// cascade picks the most specific match per element.
//
// This replaces the per-color blocks that used to live in
// `toggle.css` — the config now owns the entire palette cascade.
'palette-track': {
declarations: [
{ value: 'var(--toggle-neutral-track)', scope: 'host' },
{ value: 'var(--toggle-primary-track)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-track)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-track)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-track)', scope: 'color:threat' }
]
},
'palette-element': {
declarations: [
{ value: 'var(--toggle-neutral-element)', scope: 'host' },
{ value: 'var(--toggle-primary-element)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-element)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-element)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-element)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-element)', scope: 'color:threat' }
]
},
'palette-hover': {
declarations: [
{ value: 'var(--toggle-neutral-hover)', scope: 'host' },
{ value: 'var(--toggle-primary-hover)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-hover)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-hover)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-hover)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-hover)', scope: 'color:threat' }
]
},
'palette-border': {
declarations: [
{ value: 'var(--toggle-neutral-border)', scope: 'host' },
{ value: 'var(--toggle-primary-border)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-border)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-border)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-border)', scope: 'color:threat' }
]
},
'palette-solid': {
declarations: [
{ value: 'var(--toggle-neutral-solid)', scope: 'host' },
{ value: 'var(--toggle-primary-solid)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-solid)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-solid)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-solid)', scope: 'color:threat' }
]
},
'palette-solid-hover': {
declarations: [
{ value: 'var(--toggle-neutral-solid-hover)', scope: 'host' },
{ value: 'var(--toggle-primary-solid-hover)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-solid-hover)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-solid-hover)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-solid-hover)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-solid-hover)', scope: 'color:threat' }
]
},
'palette-text': {
declarations: [
{ value: 'var(--toggle-neutral-text)', scope: 'host' },
{ value: 'var(--toggle-primary-text)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-text)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-text)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-text)', scope: 'color:threat' }
]
},
'palette-contrast': {
declarations: [
{ value: 'var(--toggle-neutral-contrast)', scope: 'host' },
{ value: 'var(--toggle-primary-contrast)', scope: 'color:primary' },
{ value: 'var(--toggle-secondary-contrast)', scope: 'color:secondary' },
{ value: 'var(--toggle-affirm-contrast)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-contrast)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-contrast)', scope: 'color:threat' }
]
},
// Variant tokens that depend on the runtime `--toggle-palette-*`
// values are scoped to `host` so the var() reference resolves
// against the toggle element (where the palette override lives),
// not against `:root` (which would freeze them at neutral).
// The generator infers the `depends` set automatically from the
// `var(--toggle-palette-*)` references — no manual `depends`
// arrays needed. See Token Scope Contract in `lib/config-types.ts`.
'solid-bg': 'var(--color-surface-raised)',
'solid-fg': 'var(--color-content-primary)',
'solid-border': 'var(--color-border-default)',
'solid-hover-bg': {
value: 'color-mix( in srgb, var(--color-surface-overlay) 68%, var(--toggle-palette-track) )',
scope: 'host'
},
'solid-hover-border': {
value: 'color-mix( in srgb, var(--toggle-palette-border) 45%, var(--color-border-strong) )',
scope: 'host'
},
'solid-on-bg': { value: 'var(--toggle-palette-solid)', scope: 'host' },
'solid-on-fg': { value: 'var(--toggle-palette-contrast)', scope: 'host' },
'solid-on-border': { value: 'var(--toggle-palette-solid)', scope: 'host' },
'solid-on-hover-bg': { value: 'var(--toggle-palette-solid-hover)', scope: 'host' },
'solid-on-hover-border': { value: 'var(--toggle-palette-solid-hover)', scope: 'host' },
'outline-bg': 'transparent',
'outline-fg': { value: 'var(--toggle-palette-text)', scope: 'host' },
'outline-border': {
value: 'color-mix( in srgb, var(--toggle-palette-border) 88%, var(--color-border-default) )',
scope: 'host'
},
'outline-hover-bg': { value: 'var(--toggle-palette-track)', scope: 'host' },
'outline-hover-border': {
value: 'color-mix( in srgb, var(--toggle-palette-border) 100%, var(--color-border-default) )',
scope: 'host'
},
'outline-on-bg': {
value:
'color-mix( in srgb, var(--toggle-palette-element) 100%, var(--color-surface-raised) )',
scope: 'host'
},
'outline-on-fg': { value: 'var(--toggle-palette-text)', scope: 'host' },
'outline-on-border': { value: 'var(--toggle-palette-border)', scope: 'host' },
'outline-on-hover-bg': { value: 'var(--toggle-palette-hover)', scope: 'host' },
'outline-on-hover-border': { value: 'var(--toggle-palette-border)', scope: 'host' },
'ghost-bg': 'transparent',
'ghost-fg': { value: 'var(--toggle-palette-text)', scope: 'host' },
'ghost-border': 'transparent',
'ghost-hover-bg': {
value: 'color-mix(in srgb, var(--toggle-palette-track) 72%, transparent)',
scope: 'host'
},
'ghost-hover-border': 'transparent',
'ghost-on-bg': { value: 'var(--toggle-palette-track)', scope: 'host' },
'ghost-on-fg': { value: 'var(--toggle-palette-text)', scope: 'host' },
'ghost-on-border': {
value: 'color-mix( in srgb, var(--toggle-palette-border) 72%, transparent )',
scope: 'host'
},
'ghost-on-hover-bg': { value: 'var(--toggle-palette-element)', scope: 'host' },
'ghost-on-hover-border': {
value: 'color-mix( in srgb, var(--toggle-palette-border) 88%, transparent )',
scope: 'host'
},
'disabled-opacity': 'var(--opacity-disabled)'
},
tooltip: {
'content-z': '76',
'max-width': '22rem',
'padding-y-sm': 'var(--space-1)',
'padding-x-sm': 'var(--space-2)',
'padding-y-md': 'var(--space-1-5)',
'padding-x-md': 'var(--space-2-5)',
'padding-y-lg': 'var(--space-2)',
'padding-x-lg': 'var(--space-3)',
'font-size-sm': 'var(--font-size-sm)',
'font-size-md': 'var(--font-size-md)',
'font-size-lg': 'var(--font-size-lg)',
'font-family': 'var(--font-ui)',
'font-weight': 'var(--font-weight-regular)',
'line-height': 'var(--leading-text)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
bg: 'var(--color-surface-overlay)',
color: 'var(--color-content-primary)',
border: 'color-mix( in srgb, var(--color-border-default) 84%, var(--tooltip-bg) )',
shadow: 'var(--depth-overlay-shadow), var(--depth-overlay-halo)'
},
// ─────────────────────────────────────────────────────────────────────
// Layout primitives. Box, Flex, Grid, Float and Group expose their CSS
// variables as a pass-through channel: callers push per-instance values
// inline via `pushStyleVar`, and the recipe simply mirrors the CSS
// fallbacks so the contract sees each var as declared. `revert-layer`
// means "fall back to the underlying tag's natural rule" — the right
// default for unset Box props.
// ─────────────────────────────────────────────────────────────────────
box: {
display: 'revert-layer',
position: 'revert-layer',
width: 'revert-layer',
'min-width': '0',
'max-width': 'revert-layer',
height: 'revert-layer',
'min-height': 'revert-layer',
'max-height': 'revert-layer',
padding: 'revert-layer',
'padding-block': 'revert-layer',
'padding-inline': 'revert-layer',
'padding-top': 'revert-layer',
'padding-right': 'revert-layer',
'padding-bottom': 'revert-layer',
'padding-left': 'revert-layer',
margin: 'revert-layer',
'margin-block': 'revert-layer',
'margin-inline': 'revert-layer',
'margin-top': 'revert-layer',
'margin-right': 'revert-layer',
'margin-bottom': 'revert-layer',
'margin-left': 'revert-layer',
gap: 'revert-layer',
top: 'revert-layer',
right: 'revert-layer',
bottom: 'revert-layer',
left: 'revert-layer',
inset: 'revert-layer',
'inset-block': 'revert-layer',
'inset-inline': 'revert-layer',
overflow: 'revert-layer',
'overflow-x': 'revert-layer',
'overflow-y': 'revert-layer',
flex: 'revert-layer',
grow: 'revert-layer',
shrink: 'revert-layer',
basis: 'revert-layer',
order: 'revert-layer',
'align-self': 'revert-layer',
'justify-self': 'revert-layer',
'place-self': 'revert-layer',
'grid-column': 'revert-layer',
'grid-row': 'revert-layer',
'grid-area': 'revert-layer'
},
flex: {
direction: 'row',
align: 'stretch',
justify: 'flex-start',
wrap: 'nowrap',
'align-content': 'normal',
'row-gap': 'var(--box-gap, revert-layer)',
'column-gap': 'var(--box-gap, revert-layer)'
},
grid: {
'template-columns': 'none',
'template-rows': 'none',
'auto-columns': 'auto',
'auto-rows': 'auto',
'auto-flow': 'row',
align: 'stretch',
justify: 'start',
'align-content': 'normal',
'place-items': 'revert-layer',
'place-content': 'revert-layer',
gap: 'revert-layer',
'row-gap': 'var(--grid-gap, revert-layer)',
'column-gap': 'var(--grid-gap, revert-layer)'
},
float: {
gap: 'var(--space-3)'
},
group: {
'attached-overlap': '1px'
},
// ─────────────────────────────────────────────────────────────────────
// Banner — announcement strip. Public surface area is intentionally
// small (gap + radius); the rest of the chrome resolves from
// data-intent + data-variant.
// ─────────────────────────────────────────────────────────────────────
banner: {
gap: 'var(--space-3)',
radius: '0',
// Per-intent palette forwarders → Banner is retintable via
// `recipes.banner.{intent}-{slot}` like every other color component
// (button/badge/toggle). banner.css reads these instead of the raw
// foundation `--color-{intent}-*` tokens, so a per-component recipe
// override actually reaches it. (audit P1-5)
'primary-solid': 'var(--color-primary-solid)',
'primary-contrast': 'var(--color-primary-contrast)',
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'primary-text': 'var(--color-primary-text)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-contrast': 'var(--color-secondary-contrast)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'secondary-text': 'var(--color-secondary-text)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-contrast': 'var(--color-neutral-contrast)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'neutral-text': 'var(--color-neutral-text)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-contrast': 'var(--color-affirm-contrast)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'affirm-text': 'var(--color-affirm-text)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-contrast': 'var(--color-fulfill-contrast)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-text': 'var(--color-fulfill-text)',
'risk-solid': 'var(--color-risk-solid)',
'risk-contrast': 'var(--color-risk-contrast)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'risk-text': 'var(--color-risk-text)',
'threat-solid': 'var(--color-threat-solid)',
'threat-contrast': 'var(--color-threat-contrast)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)',
'threat-text': 'var(--color-threat-text)',
'loss-solid': 'var(--color-loss-solid)',
'loss-contrast': 'var(--color-loss-contrast)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-text': 'var(--color-loss-text)'
},
// ─────────────────────────────────────────────────────────────────────
// Container — full-bleed wrapper that caps `max-width` at the xl
// container token by default. Callers usually push `maxWidth` via Box,
// but the recipe needs the var declared.
// ─────────────────────────────────────────────────────────────────────
container: {
'width-xl': 'var(--layout-container-width-xl, 80rem)'
},
// ─────────────────────────────────────────────────────────────────────
// Section — vertical rhythm scale. Sizes map to the spacing primitives.
// ─────────────────────────────────────────────────────────────────────
section: {
'padding-block-sm': 'var(--space-8)',
'padding-block-md': 'var(--space-12)',
'padding-block-lg': 'var(--space-16)',
'padding-block-xl': 'calc(var(--space-16) * 1.5)'
},
// ─────────────────────────────────────────────────────────────────────
// Separator — purely geometric divider.
// ─────────────────────────────────────────────────────────────────────
separator: {
thickness: 'var(--border-width)',
color: 'var(--color-border-default)',
'min-length': 'var(--space-4)'
},
// ─────────────────────────────────────────────────────────────────────
// ToggleGroup — gap is component-owned; the color cascade modifies
// Toggle's `palette-*` tokens via TSC v2.2 cross-recipe composition
// (see `composition` block below).
// ─────────────────────────────────────────────────────────────────────
'toggle-group': {
gap: 'var(--space-1)',
// TSC v2.2 — cross-recipe composition. Items inside a toggle-group
// ARE toggles, so the group's `data-color` cascade must override
// Toggle's PUBLIC palette tokens (owned by Toggle's recipe — not by
// toggle-group) ONLY when reached through a toggle-group ancestor.
//
// `targetSelector: '[data-toggle-group-item]'` reaches the foreign
// part inside the host's cascade. The generator emits:
// [data-toggle-group][data-color='X'] [data-toggle-group-item] {
// --toggle-palette-{slot}: var(--toggle-X-{slot});
// }
//
// Replaces the 4 hand-written CSS blocks that used to live in
// `toggle-group.css` (lines 264-308 in the legacy file).
composition: {
toggle: {
targetSelector: '[data-toggle-group-item]',
tokens: {
'palette-track': {
declarations: [
{ value: 'var(--toggle-primary-track)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-track)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-track)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-track)', scope: 'color:threat' }
]
},
'palette-element': {
declarations: [
{ value: 'var(--toggle-primary-element)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-element)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-element)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-element)', scope: 'color:threat' }
]
},
'palette-hover': {
declarations: [
{ value: 'var(--toggle-primary-hover)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-hover)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-hover)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-hover)', scope: 'color:threat' }
]
},
'palette-border': {
declarations: [
{ value: 'var(--toggle-primary-border)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-border)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-border)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-border)', scope: 'color:threat' }
]
},
'palette-solid': {
declarations: [
{ value: 'var(--toggle-primary-solid)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-solid)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-solid)', scope: 'color:threat' }
]
},
'palette-solid-hover': {
declarations: [
{ value: 'var(--toggle-primary-solid-hover)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-solid-hover)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-solid-hover)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-solid-hover)', scope: 'color:threat' }
]
},
'palette-text': {
declarations: [
{ value: 'var(--toggle-primary-text)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-text)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-text)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-text)', scope: 'color:threat' }
]
},
'palette-contrast': {
declarations: [
{ value: 'var(--toggle-primary-contrast)', scope: 'color:primary' },
{ value: 'var(--toggle-affirm-contrast)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-contrast)', scope: 'color:risk' },
{ value: 'var(--toggle-threat-contrast)', scope: 'color:threat' }
]
}
}
}
}
},
// ─────────────────────────────────────────────────────────────────────
// PinInput — one-time code cells.
// ─────────────────────────────────────────────────────────────────────
'pin-input': {
gap: 'var(--space-2)',
'disabled-opacity': 'var(--opacity-disabled)',
'cell-size-sm': '2rem',
'cell-size-md': '2.5rem',
'cell-size-lg': '3rem',
'cell-size-xl': '3.5rem',
'cell-font-size-sm': 'var(--font-size-md)',
'cell-font-size-md': 'var(--font-size-lg)',
'cell-font-size-lg': 'var(--font-size-xl)',
'cell-font-size-xl': 'var(--font-size-xxl)',
'cell-font-weight': '500',
'cell-radius': 'var(--radius-md)',
'cell-border-width': 'var(--border-width)',
'cell-border-color': 'var(--color-border-default)',
'cell-bg': 'var(--color-surface-default)',
'cell-filled-bg': 'var(--color-primary-element)',
'cell-filled-border': 'var(--color-primary-border)',
'cell-active-border': 'var(--color-primary-solid)',
'cell-invalid-border': 'var(--color-threat-solid)'
},
// ─────────────────────────────────────────────────────────────────────
// ScrollArea — overlay scrollbars (macOS-style).
// ─────────────────────────────────────────────────────────────────────
'scroll-area': {
bg: 'transparent',
radius: 'var(--radius-md)',
'scrollbar-padding': 'var(--space-0-5)',
'scrollbar-size': 'var(--space-2-5)',
'track-bg': 'color-mix(in srgb, var(--color-neutral-track) 60%, transparent)',
'track-bg-hover': 'color-mix(in srgb, var(--color-neutral-track) 84%, transparent)',
'thumb-bg': 'var(--color-content-muted)',
'thumb-bg-hover': 'var(--color-content-secondary)',
'thumb-radius': 'var(--radius-full)',
'thumb-min-size': 'var(--space-3)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)'
},
// ─────────────────────────────────────────────────────────────────────
// Splitter — resizable panels with a draggable handle + grip pill.
// ─────────────────────────────────────────────────────────────────────
splitter: {
bg: 'var(--color-surface-default)',
radius: 'var(--radius-md)',
'border-width': 'var(--border-width)',
'border-color': 'var(--color-border-default)',
'disabled-opacity': 'var(--opacity-disabled)',
'panel-bg': 'transparent',
'handle-size': 'var(--space-2)',
'handle-bg': 'var(--color-border-subtle)',
'handle-bg-hover': 'var(--color-border-default)',
'handle-bg-active': 'var(--color-primary-solid)',
'grip-radius': 'var(--radius-full)',
'grip-bg': 'var(--color-content-muted)',
'grip-main': 'var(--space-4)',
'grip-cross': '2px',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)'
},
// ─────────────────────────────────────────────────────────────────────
// NavigationMenu — soma writes the active-trigger rect into these
// indicator vars; the recipe paints the underline.
// ─────────────────────────────────────────────────────────────────────
// Combobox — floating result list. Same overlay micro-band as `select` /
// `dropdown-menu` (the soma floating layer mirrors this computed z-index onto
// the positioner wrapper, so it must stay a low, themeable value — not the
// global `--z-index-*` scale).
combobox: {
'content-z': '80'
},
// DragDrop — the drag preview/ghost that tracks the cursor. Floats above
// local content during an active drag.
'drag-drop': {
'preview-z': '99'
},
'navigation-menu': {
// Inline (non-portaled) dropdown panel: stacks above normal page flow.
'content-z': '50',
'indicator-w': '0px',
'indicator-x': '0px'
},
// ─────────────────────────────────────────────────────────────────────
// DropdownMenu — panel chrome + item rows. Mirror tokens to context-menu.
// ─────────────────────────────────────────────────────────────────────
'dropdown-menu': {
// Floating menu panel stacks in the top overlay band (matches
// `select` — both are click-triggered list overlays). Without this
// the portaled panel inherits `z-index: auto` and any positioned
// page element with a positive z-index (e.g. a selected
// `[data-toggle-group-item]`, z-index 1) paints THROUGH it.
'content-z': '80',
'content-min-width': '12rem',
'content-max-height': 'min(60vh, 22rem)',
'content-padding': 'var(--space-1)',
'content-border-width': 'var(--border-width)',
'content-border-color': 'var(--color-border-default)',
'content-radius': 'var(--radius-md)',
'content-bg': 'var(--color-surface-overlay)',
'content-shadow': 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'content-font-size': 'var(--font-size-sm)',
'item-gap': 'var(--space-2)',
'item-height': '2rem',
'item-px': 'var(--space-2)',
'item-py': 'var(--space-1)',
'item-radius': 'var(--radius-sm)',
'item-bg-hover': 'var(--color-primary-element)',
'item-fg-hover': 'var(--color-content-primary)',
'item-disabled-opacity': '0.55',
'heading-px': 'var(--space-2)',
'heading-py': 'var(--space-1)',
'heading-font-size': 'var(--font-size-xs)',
'separator-bg': 'var(--color-border-subtle)',
'separator-my': 'var(--space-1)'
},
// ─────────────────────────────────────────────────────────────────────
// ContextMenu — same surface as DropdownMenu, with a span Trigger.
// ─────────────────────────────────────────────────────────────────────
'context-menu': {
'content-min-width': '12rem',
'content-max-height': 'min(60vh, 22rem)',
'content-padding': 'var(--space-1)',
'content-border-width': 'var(--border-width)',
'content-border-color': 'var(--color-border-default)',
'content-radius': 'var(--radius-md)',
'content-bg': 'var(--color-surface-overlay)',
'content-shadow': 'var(--depth-overlay-shadow), var(--depth-overlay-halo)',
'content-font-size': 'var(--font-size-sm)',
'item-gap': 'var(--space-2)',
'item-height': '2rem',
'item-px': 'var(--space-2)',
'item-py': 'var(--space-1)',
'item-radius': 'var(--radius-sm)',
'item-bg-hover': 'var(--color-primary-element)',
'item-fg-hover': 'var(--color-content-primary)',
'item-disabled-opacity': '0.55',
'heading-px': 'var(--space-2)',
'heading-py': 'var(--space-1)',
'heading-font-size': 'var(--font-size-xs)',
'separator-bg': 'var(--color-border-subtle)',
'separator-my': 'var(--space-1)'
},
// ─────────────────────────────────────────────────────────────────────
// DateRangeField — layout only; chrome inherits from date-field.
// ─────────────────────────────────────────────────────────────────────
'date-range-field': {
'row-gap': 'var(--space-3)',
'separator-color': 'var(--color-content-muted)',
'separator-glyph': "'—'"
},
// ─────────────────────────────────────────────────────────────────────
// TimeRangeField — layout only; chrome inherits from time-field.
// ─────────────────────────────────────────────────────────────────────
'time-range-field': {
'row-gap': 'var(--space-3)',
'separator-color': 'var(--color-content-muted)',
'separator-glyph': "'—'"
}
} as const satisfies RecipeTokenSet;

Powered by TurnKey Linux.