Skip to content

Theming & Customization

Refine exposes its entire visual system as CSS custom properties. Every color, spacing value, shadow, and transition is a variable you can override. Nothing requires !important.

New to Refine?

Read the Usage Guide first. This page covers customization only.

Quick Start

Change the primary color — and secondary adapts automatically:

css
:root {
  --color-primary-hue: 220deg; /* e.g. 160deg → teal, 35deg → amber, 0deg → red */
}

That single variable shifts every primary token (base, focus, backdrop, border…) and simultaneously derives the secondary family as a near-black / near-white tone with a matching hue tint. No other overrides are needed for a full rebrand.

Need finer control? Override individual sub-tokens after setting the hue:

css
:root {
  --color-primary-hue: 220deg;
  /* Optional — adjust lightness/chroma only for the primary base */
  --color-primary: light-dark(oklch(52% 0.2 var(--color-primary-hue)), oklch(65% 0.18 var(--color-primary-hue)));
}

Toggle dark mode:

javascript
document.documentElement.classList.add('dark');    // force dark
document.documentElement.classList.remove('dark'); // force light (falls back to OS)
document.documentElement.classList.toggle('dark'); // toggle

Dark mode works automatically from the OS preference. The .dark class overrides it. No @media (prefers-color-scheme) blocks needed — every color token uses light-dark() and resolves based on the active color-scheme.

How Overrides Work

All Refine tokens live inside @layer refine.tokens { … }. Any unlayered :root rule in your stylesheet takes precedence automatically:

css
/* This wins over refine.tokens without !important */
:root {
  --color-primary: light-dark(oklch(55% 0.18 220deg), oklch(62% 0.18 220deg));
  --rounded-md: 0.5rem;
}

If you need to scope overrides to a subtree, put them on a class instead of :root:

css
.my-section {
  --color-primary: light-dark(oklch(60% 0.2 35deg), oklch(66% 0.2 35deg));
}

Semantic Colors

Each semantic color ships with 7 coordinated sub-tokens. Always define all seven together — a partial override leaves some states using the wrong base color.

Sub-tokenPurpose
--color-{name}Base interactive color (buttons, links, highlights)
--color-{name}-focusHover and active state
--color-{name}-backdropTinted surface behind a colored element
--color-{name}-contentForeground text or icon on the base color
--color-{name}-contrastHigh-contrast inverse surface
--color-{name}-borderBorder at reduced opacity
--color-{name}-focus-shadowFocus ring box-shadow

Available families: neutral, primary, secondary, info, success, warning, error.

Secondary is auto-derived

secondary tokens are computed from --color-primary-hue — you never need to define them manually. Changing --color-primary-hue updates both the primary and secondary families at once. Override individual --color-secondary-* tokens only when you need the secondary color to diverge from the primary hue.

Overriding the Neutral Palette

Uncolored components (buttons, inputs, chips without a color attribute) use the neutral family. Override it via the --refine-color-neutral-* surface:

css
:root {
  --refine-color-neutral: light-dark(oklch(50% 0.01 240deg), oklch(68% 0.01 240deg));
  --refine-color-neutral-focus: light-dark(oklch(56% 0.01 240deg), oklch(74% 0.01 240deg));
  --refine-color-neutral-backdrop: light-dark(oklch(95% 0.005 240deg / 83%), oklch(25% 0.005 240deg / 60%));
  --refine-color-neutral-border: light-dark(oklch(84% 0.006 240deg / 75%), oklch(28% 0.006 240deg / 75%));
  --refine-color-neutral-content: light-dark(oklch(40% 0.01 240deg), oklch(80% 0.01 240deg));
  --refine-color-neutral-contrast: light-dark(oklch(99% 0 240deg), oklch(13% 0 240deg));
}

This affects every component that defaults to neutral — without needing color="primary" on each element.

Dark Mode

Every color token uses the CSS light-dark() function. Refine sets color-scheme: light dark on :root, so the OS preference applies automatically from day one.

Two rules on <html> let you override the OS:

css
html.dark       { color-scheme: dark; }
html:not(.dark) { color-scheme: light; }

Toggle the .dark class to switch modes. VitePress handles this automatically — no extra configuration needed when building docs.

Keeping Overrides Mode-Aware

A flat color override is always light (or always dark). Use light-dark() to keep it adaptive:

css
:root {
  /* ✓ Adapts to light and dark */
  --color-primary: light-dark(oklch(55% 0.18 220deg), oklch(62% 0.18 220deg));

  /* ⚠ Static — same in both modes */
  --color-primary: oklch(58% 0.18 220deg);
}

OKLCH is recommended for custom colors: lightness steps are perceptually uniform, contrast ratios stay consistent across the ramp, and the wide gamut covers P3 displays.

Custom Themes

Scoped Theme Class

Create a theme with a single hue variable — primary and secondary both adapt:

css
.theme-ocean  { --color-primary-hue: 220deg; }
.theme-sunset { --color-primary-hue: 35deg;  }
.theme-forest { --color-primary-hue: 160deg; }
.theme-rose   { --color-primary-hue: 0deg;   }

Apply it anywhere in the DOM:

html
<div class="theme-ocean">
  <ore-button color="primary">Save</ore-button>
  <ore-button color="secondary">Cancel</ore-button>
</div>

Need to override more than just the hue? Add sub-token overrides after --color-primary-hue:

css
.theme-ocean {
  --color-primary-hue: 220deg;
  --color-primary: light-dark(oklch(52% 0.2 var(--color-primary-hue)), oklch(65% 0.18 var(--color-primary-hue)));
}

Dynamic Theme Switching

Because the entire palette derives from a single hue, switching themes at runtime is a one-liner:

typescript
const THEMES = {
  ocean:  { '--color-primary-hue': '220deg' },
  sunset: { '--color-primary-hue': '35deg'  },
  forest: { '--color-primary-hue': '160deg' },
  rose:   { '--color-primary-hue': '0deg'   },
} as const;

function applyTheme(name: keyof typeof THEMES) {
  const root = document.documentElement;
  Object.entries(THEMES[name]).forEach(([prop, value]) => {
    root.style.setProperty(prop, value);
  });
}

The browser recomputes every var(--color-primary-hue) reference — including the derived secondary tokens — in a single style recalc.

Component-Level Overrides

Each component exposes CSS custom properties for fine-tuned control. Set them inline or in a stylesheet:

html
<ore-button style="
  --button-bg: linear-gradient(135deg, #667eea, #764ba2);
  --button-radius: 20px;
  --button-padding: 0.75rem 2rem;
">
  Gradient Button
</ore-button>

Common component variables:

css
/* ore-button */
--button-bg
--button-color
--button-border
--button-radius
--button-padding

/* ore-input */
--input-bg
--input-color
--input-border-color
--input-placeholder-color
--input-radius

/* ore-text */
--text-size
--text-weight
--text-color
--text-line-height
--text-letter-spacing

Refer to each component's documentation for its full variable list.

Token Reference

Color Palette

Brand Colors

PrimaryPeriwinkle violet — primary brand color
Base
Backdrop
Content
Contrast
Focus
Border
SecondaryInk/charcoal (light) · silver (dark) — contrast-driven adaptive
Base
Backdrop
Content
Contrast
Focus
Border
NeutralTrue gray — neutral UI surfaces
Base
Backdrop
Content
Contrast
Focus
Border

Semantic Colors

InfoCyan-blue — informational messages
Base
Backdrop
Content
Contrast
Focus
Border
SuccessTeal — positive outcomes & confirmations
Base
Backdrop
Content
Contrast
Focus
Border
WarningAmber — cautionary states & alerts
Base
Backdrop
Content
Contrast
Focus
Border
ErrorVermilion — destructive actions & errors
Base
Backdrop
Content
Contrast
Focus
Border

Contrast Scale

Adaptive gray scale — surfaces (50–400) and text (500–900). Automatically inverts in dark mode.

50
100
200
300
400
500
600
700
800
900

Contrast Scale

Refine uses an 11-step contrast scale driven by light-dark(). Values flip automatically between light and dark poles — no @media query needed.

Background range (50–400) — surfaces, borders, UI structure:

TokenLightDarkUsage
--color-contrast-50oklch(99% 0.001 264deg)oklch(17% 0.001 250deg)Canvas, page background
--color-contrast-100oklch(97% 0.001 264deg)oklch(21% 0.001 250deg)Cards, elevated surfaces
--color-contrast-150oklch(95.5% 0.001 264deg)oklch(23.5% 0.001 250deg)Midpoint — chip base, subtle fills
--color-contrast-200oklch(94% 0.001 264deg)oklch(26% 0.001 250deg)Nested cards, hover states
--color-contrast-300oklch(89% 0.002 264deg)oklch(32% 0.001 250deg)Borders, dividers
--color-contrast-400oklch(81% 0.002 264deg)oklch(40% 0.001 250deg)Disabled backgrounds, subtle UI

Text range (500–900) — readability and WCAG compliance:

TokenLightDarkWCAGUsage
--color-contrast-500oklch(49% 0.002 264deg)oklch(58% 0.001 250deg)AA (large text)Tertiary text, placeholders
--color-contrast-600oklch(40% 0.002 264deg)oklch(68% 0.001 250deg)AASecondary / muted text
--color-contrast-700oklch(32% 0.002 264deg)oklch(78% 0.001 250deg)AAASupplemental body text
--color-contrast-800oklch(22% 0.002 264deg)oklch(88% 0.001 250deg)AAADefault body text
--color-contrast-900oklch(12% 0.002 264deg)oklch(95% 0.001 250deg)AAAHeadings, highest contrast

Aliases:

TokenValueUsage
--color-canvascolor-mix(in oklch, var(--color-contrast-50) 85%, transparent)Default page/component background
--color-dividercolor-mix(in oklch, var(--color-contrast-300) 85%, transparent)Separator lines and borders
--color-contrastcolor-mix(in oklch, var(--color-contrast-900) 85%, transparent)Maximum-contrast text/icon color

Semantic Text Colors

TokenContrast stepUse
--text-color-heading--color-contrast-900Headings — AAA
--text-color-body--color-contrast-800Default body text — AAA
--text-color-secondary--color-contrast-600Secondary / muted text — AA
--text-color-tertiary--color-contrast-500Placeholder, hint text — AA large
--text-color-disabled--color-contrast-400Disabled state — decorative only
--text-color-contrast--color-contrast-100Text on dark/colored backgrounds

Prefer semantic tokens over raw contrast values — they stay meaningful in both modes:

css
/* ✓ Semantic — adapts automatically */
color: var(--text-color-body);

/* ✗ Avoid — raw value is less expressive */
color: var(--color-contrast-800);

Typography

Body scale (--text-*):

TokenValue
--text-2xs0.625rem (10px)
--text-xs0.75rem (12px)
--text-sm0.875rem (14px)
--text-base1rem (16px)
--text-lg1.125rem (18px)
--text-xl1.25rem (20px)
--text-2xl1.5rem (24px)
--text-3xl1.875rem (30px)

Heading scale (--heading-*) — used by <ore-text variant="heading">:

TokenValue
--heading-xs0.875rem (14px)
--heading-sm1rem (16px)
--heading-md1.5rem (24px)
--heading-lg2rem (32px)
--heading-xl3rem (48px)
--heading-2xl4rem (64px)

Font weights:

TokenValueNote
--font-thin100
--font-extralight200
--font-light300
--font-normal400Minimum for body text
--font-medium500
--font-semibold600
--font-bold700
--font-extrabold800
--font-black900

Letter spacing:

TokenValueUse
--tracking-tighter-0.05emTightest display headings
--tracking-tight-0.025emStandard headers, display text
--tracking-header-0.05emDefault for heading variant
--tracking-normal0emDefault body tracking
--tracking-wide0.05emLabels, badges, uppercase caps

Line heights:

TokenValueUse
--leading-none1
--leading-tight1.15Headings
--leading-snug1.375
--leading-normal1.5Body text (WCAG recommended)
--leading-relaxed1.625
--leading-loose2
--leading-30.75rem (12px)
--leading-41rem (16px)
--leading-51.25rem (20px)
--leading-61.5rem (24px)
--leading-71.75rem (28px)
--leading-82rem (32px)
--leading-92.25rem (36px)
--leading-102.5rem (40px)

Transitions & Animation

Duration scale:

TokenValueZeroed under reduced-motion
--duration-7575msYes
--duration-100100msYes
--duration-150150msYes
--duration-200200msYes
--duration-300300msYes
--duration-500500msYes
--duration-600600msNo — spinner rotation
--duration-700700msYes
--duration-10001000msYes
--duration-14001400msNo — looping indicator
--duration-15001500msNo — looping indicator

Easing:

TokenValueUse
--ease-linearlinear
--ease-incubic-bezier(0.4, 0, 1, 1)
--ease-outcubic-bezier(0, 0, 0.2, 1)
--ease-in-outcubic-bezier(0.4, 0, 0.2, 1)Default for most transitions
--ease-springcubic-bezier(0.22, 1, 0.36, 1)Fast start, soft landing
--ease-movecubic-bezier(0.25, 1, 0.5, 1)Drawers, panels, carousels
--ease-overshootcubic-bezier(0.34, 1.1, 0.64, 1)Toasts, popups

Pre-built transitions:

TokenValueUse
--transition-nonenone
--transition-fast150ms var(--ease-in-out)Hover states, toggles
--transition-normal200ms var(--ease-in-out)Default
--transition-slow300ms var(--ease-in-out)Panels, accordions
--transition-slower500ms var(--ease-in-out)
--transition-spring300ms var(--ease-spring)Bouncy interactions
--transition-exit300ms var(--ease-out)Overlay dismiss, alert collapse
--transition-loader600ms linearSpinner constant rotation

WARNING

--transition-all does not exist in Refine. Use explicit property transitions to avoid forcing layout recalculation on every style change:

css
transition: color var(--transition-fast), background var(--transition-fast);

All Token Categories

CategoryPrefixDescription
Spacing Scale--size-{n}4px-increment spacing (0 → 96)
Container Sizes--size-{2xs–7xl}Named width breakpoints (256px → 1280px)
Special Sizes--size-{full,fit,min,max,auto,none,prose}Keyword size utilities
Viewport & Breakpoints--size-screen-*Viewport units + breakpoint values
Aspect Ratios--aspect-*Common aspect ratios (square, video, wide…)
3D Perspective--perspective-*Transform perspective distances
Grid Templates--grid-{1–12}CSS Grid column repeat helpers
Border Widths--border-*Stroke widths (0, 1px, 2px, 4px, 8px)
Ring Utilities--ring-*Focus ring box-shadow helpers
Border Radius--rounded-*Corner rounding scale
Blur Effects--blur-*Blur filter scale
Box Shadows--shadow-*Elevation shadows (light-dark aware)
Inset Shadows--inset-shadow-*Inner shadow scale
Drop Shadows--drop-shadow-*CSS filter drop-shadows
Text Shadows--text-shadow-*Typographic text shadows
Halo Shadows--halo-shadow-*Branded glow per semantic color
Font Families--font-{sans,serif,mono}System font stacks
Semantic Colors--color-{name}-*7 sub-tokens per semantic color family
Contrast Scale--color-contrast-{50–900}11-step light/dark adaptive palette
Section Spacing--section-spacingDefault block section gap (2rem)
View theme.css
css
@layer refine.tokens {
  :root {
    /* ========================================
       Spacing Scale
       Based on 0.25rem (4px) increments
       ======================================== */
    --size-0: 0;
    --size-px: 1px;
    --size-0-5: 0.125rem; /* 2px */
    --size-1: 0.25rem; /* 4px */
    --size-1-5: 0.375rem; /* 6px */
    --size-2: 0.5rem; /* 8px */
    --size-2-5: 0.625rem; /* 10px */
    --size-3: 0.75rem; /* 12px */
    --size-3-5: 0.875rem; /* 14px */
    --size-4: 1rem; /* 16px */
    --size-5: 1.25rem; /* 20px */
    --size-6: 1.5rem; /* 24px */
    --size-7: 1.75rem; /* 28px */
    --size-8: 2rem; /* 32px */
    --size-9: 2.25rem; /* 36px */
    --size-10: 2.5rem; /* 40px */
    --size-11: 2.75rem; /* 44px */
    --size-12: 3rem; /* 48px */
    --size-14: 3.5rem; /* 56px */
    --size-16: 4rem; /* 64px */
    --size-20: 5rem; /* 80px */
    --size-24: 6rem; /* 96px */
    --size-28: 7rem; /* 112px */
    --size-32: 8rem; /* 128px */
    --size-36: 9rem; /* 144px */
    --size-40: 10rem; /* 160px */
    --size-44: 11rem; /* 176px */
    --size-48: 12rem; /* 192px */
    --size-52: 13rem; /* 208px */
    --size-56: 14rem; /* 224px */
    --size-60: 15rem; /* 240px */
    --size-64: 16rem; /* 256px */
    --size-72: 18rem; /* 288px */
    --size-80: 20rem; /* 320px */
    --size-96: 24rem; /* 384px */

    /* Section Spacing */
    --section-spacing: 2rem; /* 32px */

    /* Container Sizes */
    --size-2xs: 16rem; /* 256px */
    --size-xs: 20rem; /* 320px */
    --size-sm: 24rem; /* 384px */
    --size-md: 28rem; /* 448px */
    --size-lg: 32rem; /* 512px */
    --size-xl: 36rem; /* 576px */
    --size-2xl: 42rem; /* 672px */
    --size-3xl: 48rem; /* 768px */
    --size-4xl: 56rem; /* 896px */
    --size-5xl: 64rem; /* 1024px */
    --size-6xl: 72rem; /* 1152px */
    --size-7xl: 80rem; /* 1280px */

    /* Special Sizes */
    --size-full: 100%;
    --size-fit: fit-content;
    --size-min: min-content;
    --size-max: max-content;
    --size-auto: auto;
    --size-none: none;
    --size-prose: 65ch; /* Optimal reading width */

    /* Viewport Units */
    --size-screen-width: 100dvw;
    --size-screen-height: 100dvh;

    /* Breakpoints */
    --size-screen-xs: 480px;
    --size-screen-sm: 640px;
    --size-screen-md: 768px;
    --size-screen-lg: 1024px;
    --size-screen-xl: 1280px;
    --size-screen-2xl: 1536px;

    /* ========================================
       3D Perspective (for transforms)
       ======================================== */
    --perspective-dramatic: 100px;
    --perspective-near: 300px;
    --perspective-normal: 500px;
    --perspective-midrange: 800px;
    --perspective-distant: 1200px;

    /* ========================================
       Aspect Ratios
       ======================================== */
    --aspect-square: 1 / 1;
    --aspect-video: 16 / 9;
    --aspect-wide: 21 / 9;
    --aspect-ultrawide: 32 / 9;
    --aspect-portrait: 3 / 4;
    --aspect-photo: 4 / 3;

    /* ========================================
       Grid Template Columns
       ======================================== */
    --grid-1: repeat(1, minmax(0, 1fr));
    --grid-2: repeat(2, minmax(0, 1fr));
    --grid-3: repeat(3, minmax(0, 1fr));
    --grid-4: repeat(4, minmax(0, 1fr));
    --grid-5: repeat(5, minmax(0, 1fr));
    --grid-6: repeat(6, minmax(0, 1fr));
    --grid-7: repeat(7, minmax(0, 1fr));
    --grid-8: repeat(8, minmax(0, 1fr));
    --grid-9: repeat(9, minmax(0, 1fr));
    --grid-10: repeat(10, minmax(0, 1fr));
    --grid-11: repeat(11, minmax(0, 1fr));
    --grid-12: repeat(12, minmax(0, 1fr));

    /* ========================================
       Border Widths
       ======================================== */
    --border-0: 0;
    --border: 1px;
    --border-2: 2px;
    --border-4: 4px;
    --border-8: 8px;

    /* Ring Utilities (for focus states) */
    --ring-0: 0 0 0 0;
    --ring: 0 0 0 var(--border);
    --ring-2: 0 0 0 var(--border-2);
    --ring-4: 0 0 0 var(--border-4);
    --ring-8: 0 0 0 var(--border-8);

    /* ========================================
       Border Radius
       Optimized for accessibility - minimum 4px for touch targets
       ======================================== */
    --rounded-none: 0;
    --rounded-xs: 0.125rem; /* 2px - minimal */
    --rounded-sm: 0.25rem; /* 4px - small */
    --rounded: 0.25rem; /* 4px - default */
    --rounded-md: 0.375rem; /* 6px - medium */
    --rounded-lg: 0.5rem; /* 8px - large */
    --rounded-xl: 0.75rem; /* 12px - extra large */
    --rounded-2xl: 1rem; /* 16px - 2x extra large */
    --rounded-3xl: 1.5rem; /* 24px - 3x extra large */
    --rounded-full: 9999px; /* Full circle/pill */

    /* ========================================
       Blur Effects
       ======================================== */
    --blur-none: 0;
    --blur-xs: 4px;
    --blur-sm: 8px;
    --blur-md: 12px;
    --blur-lg: 16px;
    --blur-xl: 24px;
    --blur-2xl: 40px;
    --blur-3xl: 64px;

    /* ========================================
       Box Shadows
       All shadows use light-dark() to remain visible in dark mode.
       ======================================== */
    --shadow-none: 0 0 transparent;
    --shadow-2xs: light-dark(0 1px 2px rgb(0 0 0 / 3%), 0 1px 2px rgb(0 0 0 / 20%));
    --shadow-xs: light-dark(0 2px 4px rgb(0 0 0 / 3%), 0 2px 4px rgb(0 0 0 / 22%));
    --shadow-sm: light-dark(0 4px 10px -1px rgb(0 0 0 / 5%), 0 4px 10px -1px rgb(0 0 0 / 28%));
    --shadow: light-dark(0 8px 16px -2px rgb(0 0 0 / 6%), 0 8px 16px -2px rgb(0 0 0 / 32%));
    --shadow-md: light-dark(0 12px 24px -4px rgb(0 0 0 / 6%), 0 12px 24px -4px rgb(0 0 0 / 34%));
    --shadow-lg: light-dark(0 24px 32px -8px rgb(0 0 0 / 8%), 0 24px 32px -8px rgb(0 0 0 / 38%));
    --shadow-xl: light-dark(0 32px 64px -16px rgb(0 0 0 / 12%), 0 32px 64px -16px rgb(0 0 0 / 44%));
    --shadow-2xl: light-dark(0 48px 80px -20px rgb(0 0 0 / 20%), 0 48px 80px -20px rgb(0 0 0 / 52%));

    /* Inset Shadows — also light-dark() for dark mode depth */
    --inset-shadow-none: inset 0 0 transparent;
    --inset-shadow-2xs: light-dark(inset 0 1px rgb(0 0 0 / 5%), inset 0 1px rgb(255 255 255 / 4%));
    --inset-shadow-xs: light-dark(inset 0 1px 1px rgb(0 0 0 / 5%), inset 0 1px 1px rgb(255 255 255 / 4%));
    --inset-shadow-sm: light-dark(inset 0 2px 4px rgb(0 0 0 / 5%), inset 0 2px 4px rgb(0 0 0 / 25%));
    --inset-shadow: light-dark(inset 0 2px 4px rgb(0 0 0 / 6%), inset 0 2px 4px rgb(0 0 0 / 28%));

    /* Drop Shadows (for filters) */
    --drop-shadow-none: 0 0 transparent;
    --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 5%);
    --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 15%);
    --drop-shadow: 0 1px 2px rgb(0 0 0 / 10%), 0 1px 1px rgb(0 0 0 / 6%);
    --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 12%);
    --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 15%);
    --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 10%);
    --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 15%);

    /* Text Shadows (for readability) */
    --text-shadow-none: 0 0 transparent;
    --text-shadow-2xs: 0 1px 0 rgb(0 0 0 / 15%);
    --text-shadow-xs: 0 1px 1px rgb(0 0 0 / 20%);
    --text-shadow-sm: 0 1px 0 rgb(0 0 0 / 7.5%), 0 1px 1px rgb(0 0 0 / 7.5%), 0 2px 2px rgb(0 0 0 / 7.5%);
    --text-shadow: 0 1px 1px rgb(0 0 0 / 10%), 0 2px 2px rgb(0 0 0 / 10%);
    --text-shadow-md: 0 1px 1px rgb(0 0 0 / 10%), 0 1px 2px rgb(0 0 0 / 10%), 0 2px 4px rgb(0 0 0 / 10%);
    --text-shadow-lg: 0 1px 2px rgb(0 0 0 / 10%), 0 3px 2px rgb(0 0 0 / 10%), 0 4px 8px rgb(0 0 0 / 10%);

    /* ========================================
       Typography
       Optimized for WCAG AAA readability
       ======================================== */

    /* Font Family */
    --font-sans:
      'Inter Variable', 'Plus Jakarta Sans Variable', system-ui, -apple-system, blinkmacsystemfont, 'Segoe UI', roboto,
      sans-serif;
    --font-serif: ui-serif, georgia, cambria, 'Times New Roman', times, serif;
    --font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', menlo, consolas, 'Liberation Mono', monospace;

    /* Font Weights (Accessibility: minimum 400 for body text) */
    --font-thin: 100;
    --font-extralight: 200;
    --font-light: 300;
    --font-normal: 400; /* Minimum for body text */
    --font-medium: 500;
    --font-semibold: 600;
    --font-bold: 700;
    --font-extrabold: 800;
    --font-black: 900;

    /* Letter Spacing */
    --tracking-tighter: -0.05em;
    --tracking-tight: -0.025em; /* -2.5% — headers, display text */
    --tracking-header: var(--tracking-tighter);
    --tracking-normal: 0em; /* default body tracking */
    --tracking-wide: 0.05em; /* labels, badges, uppercase caps */

    /* Line Heights (Optimized for readability - WCAG recommends 1.5 for body text) */
    --leading-none: 1;
    --leading-tight: 1.15; /* 115% for headers */
    --leading-snug: 1.375;
    --leading-normal: 1.5; /* Optimal for body text (WCAG) */
    --leading-relaxed: 1.625;
    --leading-loose: 2;

    /* Absolute Line Heights */
    --leading-3: 0.75rem; /* 12px */
    --leading-4: 1rem; /* 16px */
    --leading-5: 1.25rem; /* 20px */
    --leading-6: 1.5rem; /* 24px */
    --leading-7: 1.75rem; /* 28px */
    --leading-8: 2rem; /* 32px */
    --leading-9: 2.25rem; /* 36px */
    --leading-10: 2.5rem; /* 40px */

    /* Component UI Font Sizes (7 steps — used inside components) */
    --text-2xs: 0.625rem; /* 10px - micro labels, dot-only badges */
    --text-xs: 0.75rem; /* 12px - small labels only */
    --text-sm: 0.875rem; /* 14px - secondary text */
    --text-base: 1rem; /* 16px - body text (minimum) */
    --text-lg: 1.125rem; /* 18px - large UI / headings */
    --text-xl: 1.25rem; /* 20px - subheadings */
    --text-2xl: 1.5rem; /* 24px - major headings */
    --text-3xl: 1.875rem; /* 30px - display / circular progress label */

    /* Document Heading Sizes (h1–h6 — use outside components, in page/doc context) */
    --heading-xs: 0.875rem;
    --heading-sm: 1rem;
    --heading-md: 1.5rem;
    --heading-lg: 2rem;
    --heading-xl: 3rem;
    --heading-2xl: 4rem;

    /* Semantic Text Colors (for accessibility)
       These adapt to light/dark mode automatically */
    --text-color-heading: var(--color-contrast-900);
    --text-color-body: var(--color-contrast-800);
    --text-color-secondary: var(--color-contrast-600);
    --text-color-tertiary: var(--color-contrast-500);
    --text-color-disabled: var(--color-contrast-400);
    --text-color-contrast: var(--color-contrast-100);

    /* ========================================
       Transitions & Animations
       Optimized for smooth, accessible motion
       ======================================== */

    /* Easing Functions */
    --ease-linear: linear;
    --ease-in: cubic-bezier(0.4, 0, 1, 1);
    --ease-out: cubic-bezier(0, 0, 0.2, 1);
    --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
    --ease-spring: cubic-bezier(0.22, 1, 0.36, 1); /* Fast start, soft landing — no overshoot */
    --ease-move: cubic-bezier(0.25, 1, 0.5, 1); /* Spatial slides — drawers, panels, carousels */
    --ease-overshoot: cubic-bezier(0.34, 1.1, 0.64, 1); /* Subtle spring overshoot — toasts, popups */

    /* Duration (respects prefers-reduced-motion) */
    --duration-75: 75ms;
    --duration-100: 100ms;
    --duration-150: 150ms;
    --duration-200: 200ms;
    --duration-300: 300ms;
    --duration-500: 500ms;
    --duration-600: 600ms;
    --duration-700: 700ms;
    --duration-1000: 1000ms;
    --duration-1400: 1400ms; /* Looping indicator cycle */
    --duration-1500: 1500ms; /* Looping indicator cycle */

    /* Combined Transitions (most commonly used) */
    --transition-none: none;
    --transition-fast: 150ms var(--ease-in-out);
    --transition-normal: 200ms var(--ease-in-out);
    --transition-slow: 300ms var(--ease-in-out);
    --transition-slower: 500ms var(--ease-in-out);
    --transition-spring: 300ms var(--ease-spring);
    --transition-exit: 300ms var(--ease-out); /* Exit animations — overlay dismiss, alert collapse */
    --transition-loader: 600ms linear; /* Spinner — constant rotation */

    /* Overlay entrance defaults — shared by @starting-style blocks */
    --overlay-enter-translate-y: -4px; /* dropdown slides down from */
    --overlay-enter-translate-y-up: 4px; /* bottom-anchored overlays slide up from */
    --overlay-enter-scale: 0.97; /* slight scale-in for menus / panels */
    --overlay-enter-scale-dialog: 0.96; /* slightly more pronounced for dialogs */

    /* ========================================
       CSS Color Scheme
       ======================================== */
    color-scheme: light dark;

    /* Contrast scale — Neutral OKLCH with a lifted, non-pure-black Dark Mode baseline */
    --color-contrast-50: light-dark(oklch(99% 0.001 264deg), oklch(17% 0.001 250deg)); /* Canvas, page background */
    --color-contrast-100: light-dark(oklch(97% 0.001 264deg), oklch(21% 0.001 250deg)); /* Cards, elevated surfaces */
    --color-contrast-150: light-dark(
      oklch(95.5% 0.001 264deg),
      oklch(23.5% 0.001 250deg)
    ); /* Midpoint — chip base, subtle fills */

    --color-contrast-200: light-dark(oklch(94% 0.001 264deg), oklch(26% 0.001 250deg)); /* Nested cards, hover states */
    --color-contrast-300: light-dark(oklch(89% 0.002 264deg), oklch(32% 0.001 250deg)); /* Borders, dividers */
    --color-contrast-400: light-dark(
      oklch(81% 0.002 264deg),
      oklch(40% 0.001 250deg)
    ); /* Disabled backgrounds, subtle UI */

    --color-contrast-500: light-dark(
      oklch(49% 0.002 264deg),
      oklch(58% 0.001 250deg)
    ); /* Tertiary text - AA compliant */

    --color-contrast-600: light-dark(
      oklch(40% 0.002 264deg),
      oklch(68% 0.001 250deg)
    ); /* Secondary text - AA compliant */

    --color-contrast-700: light-dark(oklch(32% 0.002 264deg), oklch(78% 0.001 250deg)); /* Body text - AAA compliant */
    --color-contrast-800: light-dark(oklch(22% 0.002 264deg), oklch(88% 0.001 250deg)); /* Headings - AAA compliant */
    --color-contrast-900: light-dark(
      oklch(12% 0.002 264deg),
      oklch(95% 0.001 250deg)
    ); /* High contrast text - AAA compliant */

    --color-canvas: color-mix(in oklch, var(--color-contrast-50) 85%, transparent);
    --color-divider: color-mix(in oklch, var(--color-contrast-300) 85%, transparent);
    --color-contrast: color-mix(in oklch, var(--color-contrast-900) 85%, transparent);

    /* Halo Shadows */
    --halo-shadow-neutral:
      inset 0 1px 1px light-dark(rgb(0 0 0 / 5%), rgb(0 0 0 / 15%)),
      0 0 var(--size-1) light-dark(rgb(255 255 255 / 10%), rgb(0 0 0 / 20%)) inset,
      0 var(--size-2) var(--size-6) light-dark(oklch(58% 0 264deg / 15%), oklch(72% 0 250deg / 15%)),
      0 var(--size-1) var(--size-3) light-dark(oklch(58% 0 264deg / 8%), oklch(72% 0 250deg / 8%));
    --halo-shadow-primary:
      inset 0 1px 1px light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 3%)),
      0 0 var(--size-1) light-dark(rgb(255 255 255 / 10%), rgb(255 255 255 / 12%)) inset,
      0 var(--size-2) var(--size-6) color-mix(in oklch, var(--color-primary) 15%, transparent),
      0 var(--size-1) var(--size-3) color-mix(in oklch, var(--color-primary) 8%, transparent);
    --halo-shadow-secondary:
      inset 0 1px 1px light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 3%)),
      0 0 var(--size-1) light-dark(rgb(255 255 255 / 10%), rgb(255 255 255 / 12%)) inset,
      0 var(--size-2) var(--size-6) color-mix(in oklch, var(--color-secondary) 12%, transparent),
      0 var(--size-1) var(--size-3) color-mix(in oklch, var(--color-secondary) 7%, transparent);
    --halo-shadow-info:
      inset 0 1px 1px light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 3%)),
      0 0 var(--size-1) light-dark(rgb(255 255 255 / 10%), rgb(255 255 255 / 12%)) inset,
      0 var(--size-2) var(--size-6) light-dark(oklch(60% 0.18 230deg / 15%), oklch(74% 0.16 230deg / 15%)),
      0 var(--size-1) var(--size-3) light-dark(oklch(60% 0.18 230deg / 8%), oklch(74% 0.16 230deg / 8%));
    --halo-shadow-success:
      inset 0 1px 1px light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 3%)),
      0 0 var(--size-1) light-dark(rgb(255 255 255 / 10%), rgb(255 255 255 / 12%)) inset,
      0 var(--size-2) var(--size-6) light-dark(oklch(62% 0.15 160deg / 15%), oklch(78% 0.14 160deg / 15%)),
      0 var(--size-1) var(--size-3) light-dark(oklch(62% 0.15 160deg / 8%), oklch(78% 0.14 160deg / 8%));
    --halo-shadow-warning:
      inset 0 1px 1px light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 3%)),
      0 0 var(--size-1) light-dark(rgb(255 255 255 / 10%), rgb(255 255 255 / 12%)) inset,
      0 var(--size-2) var(--size-6) light-dark(oklch(74% 0.18 70deg / 15%), oklch(84% 0.16 70deg / 15%)),
      0 var(--size-1) var(--size-3) light-dark(oklch(74% 0.18 70deg / 8%), oklch(84% 0.16 70deg / 8%));
    --halo-shadow-error:
      inset 0 1px 1px light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 3%)),
      0 0 var(--size-1) light-dark(rgb(255 255 255 / 10%), rgb(255 255 255 / 12%)) inset,
      0 var(--size-2) var(--size-6) light-dark(oklch(54% 0.2 29deg / 15%), oklch(66% 0.18 29deg / 15%)),
      0 var(--size-1) var(--size-3) light-dark(oklch(54% 0.2 29deg / 8%), oklch(66% 0.18 29deg / 8%));

    /* ── Neutral ─────────────────────────────────────────────────────────────── */
    --color-neutral: light-dark(oklch(56% 0 264deg), oklch(72% 0 250deg));
    --color-neutral-backdrop: light-dark(oklch(97% 0 264deg / 83%), oklch(27% 0 250deg / 60%));
    --color-neutral-content: light-dark(oklch(98% 0 264deg), oklch(15% 0 250deg));
    --color-neutral-contrast: light-dark(oklch(99% 0 264deg), oklch(13% 0 250deg));
    --color-neutral-focus: light-dark(oklch(62% 0 264deg), oklch(78% 0 250deg));
    --color-neutral-border: light-dark(oklch(86% 0 264deg / 75%), oklch(72% 0 250deg / 40%));
    --color-neutral-focus-shadow:
      0 0 0 4px color-mix(in oklch, var(--color-neutral) 40%, transparent), var(--shadow-sm);

    /* ── Primary hue — the single knob that themes both primary and secondary ──── */

    /* Override this one variable (e.g. 220deg for blue, 160deg for teal) to      */

    /* shift the entire primary palette and have secondary auto-derive from it.   */
    --color-primary-hue: 293deg;

    /* ── Primary (violet) ────────────────────────────────────────────────────── */
    --color-primary: light-dark(
      oklch(56% 0.22 var(--color-primary-hue)),
      oklch(72% 0.2 var(--color-primary-hue))
    ); /* Bright enough to read on dark canvas */

    --color-primary-backdrop: light-dark(
      oklch(93% 0.04 var(--color-primary-hue)),
      oklch(24% 0.1 var(--color-primary-hue) / 40%)
    );
    --color-primary-content: light-dark(
      oklch(98% 0.01 var(--color-primary-hue)),
      oklch(15% 0.08 var(--color-primary-hue))
    ); /* Dark ink on bright dark-mode button */

    --color-primary-contrast: light-dark(
      oklch(98% 0.01 var(--color-primary-hue)),
      oklch(14% 0.06 var(--color-primary-hue))
    );
    --color-primary-focus: light-dark(
      oklch(62% 0.22 var(--color-primary-hue)),
      oklch(78% 0.2 var(--color-primary-hue))
    );
    --color-primary-border: light-dark(
      oklch(56% 0.22 var(--color-primary-hue) / 60%),
      oklch(72% 0.2 var(--color-primary-hue) / 60%)
    );
    --color-primary-focus-shadow:
      0 0 0 4px color-mix(in oklch, var(--color-primary) 40%, transparent), var(--shadow-sm);

    /* ── Secondary — auto-derived from --color-primary-hue ─────────────────────── */

    /* Near-black (light) / near-white (dark) with a subtle tint of the primary    */

    /* hue. Mixing 7 % of the primary chroma/hue into an achromatic near-black or  */

    /* near-white base gives a visually grounded dark that breathes with the brand. */
    --color-secondary: light-dark(
      color-mix(in oklch, oklch(20% 0 none) 93%, oklch(56% 0.22 var(--color-primary-hue))),
      color-mix(in oklch, oklch(83% 0 none) 93%, oklch(72% 0.2 var(--color-primary-hue)))
    );
    --color-secondary-backdrop: light-dark(
      color-mix(in oklch, oklch(95% 0 none / 83%) 97%, oklch(56% 0.22 var(--color-primary-hue) / 83%)),
      color-mix(in oklch, oklch(20% 0 none / 60%) 97%, oklch(72% 0.2 var(--color-primary-hue) / 60%))
    );
    --color-secondary-content: light-dark(
      oklch(98% 0.002 var(--color-primary-hue)),
      oklch(15% 0.003 var(--color-primary-hue))
    );
    --color-secondary-contrast: light-dark(
      oklch(98% 0.002 var(--color-primary-hue)),
      oklch(13% 0.003 var(--color-primary-hue))
    );
    --color-secondary-focus: light-dark(
      color-mix(in oklch, oklch(28% 0 none) 93%, oklch(56% 0.22 var(--color-primary-hue))),
      color-mix(in oklch, oklch(90% 0 none) 93%, oklch(72% 0.2 var(--color-primary-hue)))
    );
    --color-secondary-border: color-mix(in oklch, var(--color-secondary) 60%, transparent);
    --color-secondary-focus-shadow:
      0 0 0 4px color-mix(in oklch, var(--color-secondary) 40%, transparent), var(--shadow-sm);

    /* ── Info (cyan-blue) ────────────────────────────────────────────────────── */
    --color-info: light-dark(oklch(60% 0.18 230deg), oklch(74% 0.16 230deg));
    --color-info-backdrop: light-dark(oklch(92% 0.06 230deg / 83%), oklch(24% 0.08 230deg / 50%));
    --color-info-content: light-dark(
      oklch(15% 0.08 230deg),
      oklch(15% 0.08 230deg)
    ); /* Base is bright in both modes, content stays dark */

    --color-info-contrast: light-dark(oklch(97% 0.02 230deg), oklch(18% 0.06 230deg));
    --color-info-focus: light-dark(oklch(66% 0.18 230deg), oklch(80% 0.16 230deg));
    --color-info-border: light-dark(oklch(60% 0.18 230deg / 60%), oklch(74% 0.16 230deg / 60%));
    --color-info-focus-shadow: 0 0 0 4px color-mix(in oklch, var(--color-info) 40%, transparent), var(--shadow-sm);

    /* ── Success (teal-green) ────────────────────────────────────────────────── */
    --color-success: light-dark(
      oklch(62% 0.15 160deg),
      oklch(78% 0.14 160deg)
    ); /* Kept brighter than error for colorblindness */

    --color-success-backdrop: light-dark(oklch(93% 0.04 160deg / 83%), oklch(22% 0.07 160deg / 50%));
    --color-success-content: light-dark(oklch(15% 0.06 160deg), oklch(15% 0.06 160deg));
    --color-success-contrast: light-dark(oklch(97% 0.02 160deg), oklch(17% 0.06 160deg));
    --color-success-focus: light-dark(oklch(66% 0.15 160deg), oklch(84% 0.14 160deg));
    --color-success-border: light-dark(oklch(62% 0.15 160deg / 60%), oklch(78% 0.14 160deg / 60%));
    --color-success-focus-shadow:
      0 0 0 4px color-mix(in oklch, var(--color-success) 40%, transparent), var(--shadow-sm);

    /* ── Warning (amber) ─────────────────────────────────────────────────────── */
    --color-warning: light-dark(oklch(74% 0.18 70deg), oklch(84% 0.16 70deg));
    --color-warning-backdrop: light-dark(oklch(93% 0.07 70deg / 83%), oklch(26% 0.08 70deg / 50%));
    --color-warning-content: light-dark(oklch(15% 0.08 70deg), oklch(15% 0.08 70deg));
    --color-warning-contrast: light-dark(oklch(96% 0.03 70deg), oklch(22% 0.06 70deg));
    --color-warning-focus: light-dark(oklch(79% 0.18 70deg), oklch(90% 0.16 70deg));
    --color-warning-border: light-dark(oklch(74% 0.18 70deg / 60%), oklch(84% 0.16 70deg / 60%));
    --color-warning-focus-shadow:
      0 0 0 4px color-mix(in oklch, var(--color-warning) 40%, transparent), var(--shadow-sm);

    /* ── Error (red-orange) ──────────────────────────────────────────────────── */
    --color-error: light-dark(oklch(54% 0.2 29deg), oklch(66% 0.18 29deg)); /* Deeper than success for colorblindness */
    --color-error-backdrop: light-dark(oklch(92% 0.06 29deg / 83%), oklch(20% 0.08 29deg / 50%));
    --color-error-content: light-dark(oklch(98% 0.03 29deg), oklch(15% 0.06 29deg));
    --color-error-contrast: light-dark(oklch(97% 0.02 29deg), oklch(16% 0.06 29deg));
    --color-error-focus: light-dark(oklch(60% 0.2 29deg), oklch(72% 0.18 29deg));
    --color-error-border: light-dark(oklch(54% 0.2 29deg / 60%), oklch(66% 0.18 29deg / 60%));
    --color-error-focus-shadow: 0 0 0 4px color-mix(in oklch, var(--color-error) 40%, transparent), var(--shadow-sm);
  }

  /* ========================================
     Reduced Motion
     ======================================== */

  @media (prefers-reduced-motion: reduce) {
    :root {
      --duration-75: 0ms;
      --duration-100: 0ms;
      --duration-150: 0ms;
      --duration-200: 0ms;
      --duration-300: 0ms;
      --duration-500: 0ms;
      --duration-700: 0ms;
      --duration-1000: 0ms;
      --transition-fast: none;
      --transition-normal: none;
      --transition-slow: none;
      --transition-slower: none;
      --transition-spring: none;
      --transition-exit: none;
      --transition-loader: none;
    }
  }

  /* VitePress dark theme: force dark color-scheme so light-dark() resolves to dark values */
  html.dark {
    color-scheme: dark;
  }

  /* VitePress light theme: force light color-scheme so light-dark() resolves to light values */
  html:not(.dark) {
    color-scheme: light;
  }
}