Theming & Customization
New to Sigil?
Start with the Usage Guide to learn the basics of importing and using components before diving into theming.
Sigil is designed to be highly customizable through CSS Custom Properties (CSS variables). All design tokens are defined in a central theme file that you can reference and override.
Design Tokens
Sigil provides a comprehensive set of design tokens organized into the following categories:
| Category | Prefix | Description |
|---|---|---|
| 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 + ring utilities |
| Border Radius | --rounded-* | Corner rounding scale |
| Blur Effects | --blur-* | Blur filter scale |
| Box Shadows | --shadow-* | Elevation shadows |
| 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 shadows per semantic color |
| Font Families | --font-{sans,serif,mono} | System font stacks |
| Font Weights | --font-* | Numeric weight scale (100–900) |
| Letter Spacing | --tracking-* | Tracking utilities |
| Line Heights | --leading-* | Relative (named) + absolute (numeric) line height scale |
| Body Text Scale | --text-{2xs–3xl} | 8-step body font size scale (2xs through 3xl) |
| Heading Scale | --heading-{xs–2xl} | 6-step heading font size scale |
| Semantic Text Colors | --text-color-* | Role-based text color tokens |
| Transitions | --transition-* | Pre-built timing + easing combos |
| Durations | --duration-{75,100,150,200,300,500,700,1000} | Millisecond step scale (all zero under prefers-reduced-motion) |
| Easing Functions | --ease-* | Named cubic-bezier curves |
| Contrast Scale | --color-contrast-{50–900} | 11-step light/dark adaptive palette (includes 150) |
| Canvas / Contrast | --color-canvas, --color-contrast | Aliases for contrast-50 and contrast-900 |
| Semantic Colors | --color-{name}-* | 7 sub-tokens per semantic color |
| Section Spacing | --section-spacing | Default block section gap (2rem) |
| Neutral Overrides | --sigil-color-neutral-* | Global neutral palette override surface (6 tokens) |
Color Palette
Brand Colors
Semantic Colors
Contrast Scale
Adaptive gray scale — surfaces (50–400) and text (500–900). Automatically inverts in dark mode.
Theme Reference
View theme.css
@layer sigil.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-tight: -0.025em; /* -2.5% — headers, display text */
--tracking-header: -0.025em; /* alias for --tracking-tight */
--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) light-dark(oklch(56% 0.22 293deg / 15%), oklch(72% 0.2 293deg / 15%)),
0 var(--size-1) var(--size-3) light-dark(oklch(56% 0.22 293deg / 8%), oklch(72% 0.2 293deg / 8%));
--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) light-dark(oklch(22% 0.005 264deg / 12%), oklch(82% 0.004 250deg / 12%)),
0 var(--size-1) var(--size-3) light-dark(oklch(22% 0.005 264deg / 7%), oklch(82% 0.004 250deg / 7%));
--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 (violet) ────────────────────────────────────────────────────── */
--color-primary: light-dark(
oklch(56% 0.22 293deg),
oklch(72% 0.2 293deg)
); /* Bright enough to read on dark canvas */
--color-primary-backdrop: light-dark(oklch(93% 0.04 293deg), oklch(24% 0.1 293deg / 40%));
--color-primary-content: light-dark(
oklch(98% 0.01 293deg),
oklch(15% 0.08 293deg)
); /* Dark ink on bright dark-mode button */
--color-primary-contrast: light-dark(oklch(98% 0.01 293deg), oklch(14% 0.06 293deg));
--color-primary-focus: light-dark(oklch(62% 0.22 293deg), oklch(78% 0.2 293deg));
--color-primary-border: light-dark(oklch(56% 0.22 293deg / 60%), oklch(72% 0.2 293deg / 60%));
--color-primary-focus-shadow:
0 0 0 4px color-mix(in oklch, var(--color-primary) 40%, transparent), var(--shadow-sm);
/* ── Secondary (near-black / near-white) ─────────────────────────────────── */
--color-secondary: light-dark(oklch(22% 0.005 264deg), oklch(82% 0.004 250deg));
--color-secondary-backdrop: light-dark(oklch(95% 0.003 264deg / 83%), oklch(20% 0.004 250deg / 60%));
--color-secondary-content: light-dark(oklch(98% 0.002 264deg), oklch(15% 0.005 250deg));
--color-secondary-contrast: light-dark(oklch(98% 0.002 264deg), oklch(13% 0.005 250deg));
--color-secondary-focus: light-dark(oklch(30% 0.005 264deg), oklch(88% 0.003 250deg));
--color-secondary-border: light-dark(oklch(22% 0.005 264deg / 60%), oklch(82% 0.004 250deg / 60%));
--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;
}
}Contrast Scale
Sigil uses an 11-step contrast scale driven entirely by light-dark(). The values flip automatically between the light and dark poles — no @media query needed.
Background Range (50–400)
Optimized for surfaces, borders, and UI structure:
| Token | Light | Dark | Usage |
|---|---|---|---|
--color-contrast-50 | oklch(99% 0.004 264deg) | oklch(13% 0.005 250deg) | Canvas, page background |
--color-contrast-100 | oklch(97% 0.004 264deg) | oklch(17% 0.005 250deg) | Cards, elevated surfaces |
--color-contrast-150 | oklch(95.5% 0.0045 264deg) | oklch(19.5% 0.005 250deg) | Midpoint — chip base, subtle fills |
--color-contrast-200 | oklch(94% 0.005 264deg) | oklch(22% 0.005 250deg) | Nested cards, hover states |
--color-contrast-300 | oklch(89% 0.006 264deg) | oklch(29% 0.006 250deg) | Borders, dividers |
--color-contrast-400 | oklch(81% 0.007 264deg) | oklch(38% 0.006 250deg) | Disabled backgrounds, subtle UI |
Text Range (500–900)
Optimized for readability and WCAG compliance. OKLCH provides perceptually-uniform lightness steps so contrast ratios are consistent across the ramp.
| Token | Light | Dark | WCAG | Usage |
|---|---|---|---|---|
--color-contrast-500 | oklch(49% 0.008 264deg) | oklch(58% 0.006 250deg) | AA (large text) | Tertiary text, placeholders |
--color-contrast-600 | oklch(40% 0.008 264deg) | oklch(68% 0.005 250deg) | AA | Secondary / muted text |
--color-contrast-700 | oklch(32% 0.008 264deg) | oklch(78% 0.004 250deg) | AAA | Supplemental body text |
--color-contrast-800 | oklch(22% 0.008 264deg) | oklch(88% 0.004 250deg) | AAA | Default body text |
--color-contrast-900 | oklch(12% 0.008 264deg) | oklch(95% 0.003 250deg) | AAA | Headings, highest contrast |
Accessibility
All text color values (500–900) meet or exceed WCAG AA standards. Values 700–900 achieve AAA compliance for body text and headings.
Canvas & Contrast Aliases
Two convenience aliases are provided for the most common surface and text needs:
| Token | Resolves to | Usage |
|---|---|---|
--color-canvas | --color-contrast-50 | Default page/component background |
--color-contrast | --color-contrast-900 | Maximum-contrast text/icon color |
Semantic Text Colors
Six role-based text color tokens are derived from the contrast scale. Use these instead of raw contrast values so your overrides stay meaningful in both light and dark mode.
| Token | Contrast step | Intended use |
|---|---|---|
--text-color-heading | --color-contrast-900 | Headings — highest contrast, AAA |
--text-color-body | --color-contrast-800 | Default body text, AAA |
--text-color-secondary | --color-contrast-600 | Secondary / muted text, AA |
--text-color-tertiary | --color-contrast-500 | Placeholder, hint text — AA large |
--text-color-disabled | --color-contrast-400 | Disabled state — decorative only |
--text-color-contrast | --color-contrast-100 | Text on dark/colored backgrounds |
/* <sg-icon name="check" size="16"></sg-icon> Good — semantic token adapts to light and dark automatically */
color: var(--text-color-body);
/* <sg-icon name="x" size="16"></sg-icon> Avoid — raw contrast value is less expressive */
color: var(--color-contrast-800);Semantic Colors
Each semantic color ships with 7 coordinated sub-tokens that cover every common UI need. All values use light-dark() and adapt to the active color scheme.
| Sub-token | Purpose |
|---|---|
--color-{name} | Base interactive color (buttons, links, highlights) |
--color-{name}-backdrop | Tinted surface behind a colored element |
--color-{name}-content | Foreground text/icon on the base color |
--color-{name}-contrast | High-contrast surface — light in light mode, dark in dark mode |
--color-{name}-focus | Hover / active state shift of the base color |
--color-{name}-border | Border treatment at reduced opacity |
--color-{name}-focus-shadow | Focus ring box-shadow with spread + --shadow-sm |
Available semantic color families: neutral, primary, secondary, info, success, warning, error.
/* Example — primary color family */
--color-primary /* base */
--color-primary-backdrop /* tinted surface */
--color-primary-content /* text/icon on primary */
--color-primary-contrast /* inverse surface */
--color-primary-focus /* hover/active */
--color-primary-border /* border at 60% opacity */
--color-primary-focus-shadow /* focus ring */Halo Shadows
Each semantic color also provides a matching --halo-shadow-{name} value — a multi-layer box-shadow that creates a soft branded glow around interactive elements.
--halo-shadow-neutral
--halo-shadow-primary
--halo-shadow-secondary
--halo-shadow-info
--halo-shadow-success
--halo-shadow-warning
--halo-shadow-errorTypography Scale
Sigil uses two parallel font-size scales to cleanly separate body text sizing from display heading sizing.
Body Scale (--text-*)
Used by default for all non-heading text:
| Token | Value | Usage |
|---|---|---|
--text-2xs | 0.625rem (10px) | Micro labels, dot-only badges |
--text-xs | 0.75rem (12px) | Small labels, captions |
--text-sm | 0.875rem (14px) | Secondary text, labels |
--text-base | 1rem (16px) | Body text (minimum accessible size) |
--text-lg | 1.125rem (18px) | Large UI text |
--text-xl | 1.25rem (20px) | Subheadings |
--text-2xl | 1.5rem (24px) | Major display text |
--text-3xl | 1.875rem (30px) | Display / circular progress label |
Heading Scale (--heading-*)
Used exclusively by variant="heading" on <sg-text>:
| Token | Value | Usage |
|---|---|---|
--heading-xs | 0.875rem (14px) | Tiny UI heading |
--heading-sm | 1rem (16px) | Small heading |
--heading-md | 1.5rem (24px) | Default heading size |
--heading-lg | 2rem (32px) | Section heading |
--heading-xl | 3rem (48px) | Page heading |
--heading-2xl | 4rem (64px) | Hero / display heading |
Font Weights
--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;Line Heights
--leading-none: 1;
--leading-tight: 1.15; /* headings */
--leading-snug: 1.375;
--leading-normal: 1.5; /* body text — WCAG recommended */
--leading-relaxed: 1.625;
--leading-loose: 2;
/* Absolute (pixel-aligned) */
--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 */Letter Spacing
--tracking-tight: -0.025em; /* -2.5% — headers, display text */
--tracking-header: -0.025em; /* alias for --tracking-tight */
--tracking-normal: 0em; /* default body tracking */
--tracking-wide: 0.05em; /* labels, badges, uppercase caps */Cascade Layer
All Sigil tokens are defined inside @layer sigil.tokens { … }. This means any unlayered :root rule in your own stylesheet wins automatically — no !important needed for overrides.
/* <sg-icon name="check" size="16"></sg-icon> This beats sigil.tokens without !important */
:root {
--color-primary: light-dark(oklch(55% 0.18 220deg), oklch(62% 0.18 220deg));
}Dark Mode
Sigil uses the CSS light-dark() function to define every color token once with both a light and dark value. color-scheme: light dark on :root means the OS preference is respected automatically — no @media block required.
How it works
Every color variable in the theme is defined using OKLCH for perceptually-uniform lightness:
--color-primary: light-dark(oklch(56% 0.22 293deg), oklch(62% 0.22 293deg));OKLCH provides two key advantages over HSL: lightness steps that look visually equal across the ramp, and a wider gamut for P3 displays.
Two rules on <html> override the OS preference when needed:
html.dark {
color-scheme: dark;
} /* force dark */
html:not(.dark) {
color-scheme: light;
} /* force light */VitePress Integration
VitePress automatically adds .dark to <html> when the user toggles the theme. Sigil responds to this automatically — no extra configuration needed.
Manual Control
For other frameworks, toggle the .dark class on <html>:
// Switch to dark
document.documentElement.classList.add('dark');
// Switch to light (remove .dark — falls back to OS preference)
document.documentElement.classList.remove('dark');
// Toggle
document.documentElement.classList.toggle('dark');Transitions & Animations
Sigil provides pre-built transition and animation tokens. Under prefers-reduced-motion: reduce, all --duration-* tokens resolve to 0ms and all --transition-* tokens resolve to none — no extra configuration needed.
Duration Scale
--duration-75: 75ms;
--duration-100: 100ms;
--duration-150: 150ms;
--duration-200: 200ms;
--duration-300: 300ms;
--duration-500: 500ms;
--duration-700: 700ms;
--duration-1000: 1000ms;Easing
--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 */Pre-built Transitions
--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);WARNING
--transition-all has been removed. Use explicit property-specific transitions (transition: color var(--transition-fast), background var(--transition-fast)) to avoid forcing layout recalculation on every style change.
Global Customization
Override the default theme by setting CSS variables in your root stylesheet. A plain value always wins over light-dark() — use light-dark() yourself when you want the override to stay mode-aware:
:root {
/* Mode-aware override — adapts to light/dark automatically (OKLCH recommended) */
--color-primary: light-dark(oklch(55% 0.18 220deg), oklch(62% 0.18 220deg));
/* Static override — same in both modes */
--rounded-md: 0.5rem;
--size-4: 1.2rem;
}Component Customization
Each component exposes specific CSS custom properties for fine-tuned control. Set them inline or in a stylesheet scoped to your component.
<sg-button
style="
--button-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--button-radius: 20px;
--button-padding: 0.75rem 2rem;
">
Gradient Button
</sg-button>Component-Specific Variables
TIP
Refer to each component's documentation for the complete list of CSS custom properties.
/* sg-text */
--text-size /* font-size */
--text-weight /* font-weight */
--text-color /* color */
--text-line-height /* line-height */
--text-letter-spacing /* letter-spacing */
/* sg-button */
--button-bg
--button-color
--button-border
--button-radius
--button-padding
/* sg-input */
--input-bg
--input-color
--input-border-color
--input-placeholder-color
--input-radius
/* sg-checkbox */
--checkbox-size
--checkbox-bg
--checkbox-checked-bg
--checkbox-radiusAdvanced Theming
Overriding the Neutral Palette
All uncolored Sigil components (buttons, inputs, chips, etc. without a color attribute) use the neutral token family. Override it globally with the --sigil-color-neutral-* surface — no !important needed:
:root {
--sigil-color-neutral: light-dark(oklch(50% 0.01 240deg), oklch(68% 0.01 240deg));
--sigil-color-neutral-focus: light-dark(oklch(56% 0.01 240deg), oklch(74% 0.01 240deg));
--sigil-color-neutral-backdrop: light-dark(oklch(95% 0.005 240deg / 83%), oklch(25% 0.005 240deg / 60%));
--sigil-color-neutral-border: light-dark(oklch(84% 0.006 240deg / 75%), oklch(28% 0.006 240deg / 75%));
--sigil-color-neutral-content: light-dark(oklch(40% 0.01 240deg), oklch(80% 0.01 240deg));
--sigil-color-neutral-contrast: light-dark(oklch(99% 0 240deg), oklch(13% 0 240deg));
}This affects every component that defaults to neutral — buttons, chips, badges, inputs — without needing color="primary" on each element. Follow the same OKLCH + light-dark() pattern to keep both modes consistent.
TIP
The six --sigil-color-neutral-* tokens mirror the --color-{name}-* family shape. All six must be defined together to avoid partially-themed components.
Creating Custom Theme Variants
Create custom theme variants by defining new color palettes on a scoped selector. Use the full 7-token pattern to keep components consistent:
/* Ocean theme — OKLCH for perceptual uniformity, light-dark() for mode-awareness */
.theme-ocean {
--color-primary: light-dark(oklch(55% 0.18 220deg), oklch(62% 0.18 220deg));
--color-primary-backdrop: light-dark(oklch(93% 0.05 220deg), oklch(28% 0.08 220deg / 40%));
--color-primary-content: light-dark(oklch(22% 0.08 220deg), oklch(95% 0.02 220deg));
--color-primary-contrast: light-dark(oklch(98% 0.01 220deg), oklch(14% 0.05 220deg));
--color-primary-focus: light-dark(oklch(61% 0.18 220deg), oklch(68% 0.18 220deg));
--color-primary-border: light-dark(oklch(61% 0.18 220deg / 60%), oklch(68% 0.18 220deg / 60%));
--color-primary-focus-shadow: 0 0 0 4px color-mix(in oklch, var(--color-primary) 40%, transparent), var(--shadow-sm);
}
/* Sunset theme */
.theme-sunset {
--color-primary: light-dark(oklch(60% 0.2 35deg), oklch(66% 0.2 35deg));
--color-primary-backdrop: light-dark(oklch(93% 0.06 35deg), oklch(28% 0.09 35deg / 40%));
--color-primary-content: light-dark(oklch(22% 0.09 35deg), oklch(95% 0.02 35deg));
--color-primary-contrast: light-dark(oklch(98% 0.01 35deg), oklch(14% 0.06 35deg));
--color-primary-focus: light-dark(oklch(66% 0.2 35deg), oklch(72% 0.2 35deg));
--color-primary-border: light-dark(oklch(66% 0.2 35deg / 60%), oklch(72% 0.2 35deg / 60%));
--color-primary-focus-shadow: 0 0 0 4px color-mix(in oklch, var(--color-primary) 40%, transparent), var(--shadow-sm);
}Dynamic Theme Switching
type ThemeName = 'ocean' | 'sunset';
const themes: Record<ThemeName, Record<string, string>> = {
ocean: {
'--color-primary': 'light-dark(oklch(55% 0.18 220deg), oklch(62% 0.18 220deg))',
'--color-primary-backdrop': 'light-dark(oklch(93% 0.05 220deg), oklch(28% 0.08 220deg / 40%))',
'--color-primary-content': 'light-dark(oklch(22% 0.08 220deg), oklch(95% 0.02 220deg))',
'--color-primary-contrast': 'light-dark(oklch(98% 0.01 220deg), oklch(14% 0.05 220deg))',
'--color-primary-focus': 'light-dark(oklch(61% 0.18 220deg), oklch(68% 0.18 220deg))',
'--color-primary-border': 'light-dark(oklch(61% 0.18 220deg / 60%), oklch(68% 0.18 220deg / 60%))',
},
sunset: {
'--color-primary': 'light-dark(oklch(60% 0.20 35deg), oklch(66% 0.20 35deg))',
'--color-primary-backdrop': 'light-dark(oklch(93% 0.06 35deg), oklch(28% 0.09 35deg / 40%))',
'--color-primary-content': 'light-dark(oklch(22% 0.09 35deg), oklch(95% 0.02 35deg))',
'--color-primary-contrast': 'light-dark(oklch(98% 0.01 35deg), oklch(14% 0.06 35deg))',
'--color-primary-focus': 'light-dark(oklch(66% 0.20 35deg), oklch(72% 0.20 35deg))',
'--color-primary-border': 'light-dark(oklch(66% 0.20 35deg / 60%), oklch(72% 0.20 35deg / 60%))',
},
};
function applyTheme(name: ThemeName) {
const theme = themes[name];
Object.entries(theme).forEach(([key, value]) => {
document.documentElement.style.setProperty(key, value);
});
}
// Usage
applyTheme('ocean');Best Practices
Use Semantic Tokens
Prefer semantic tokens over raw contrast values:
/* <sg-icon name="check" size="16"></sg-icon> Good — semantic tokens */
color: var(--text-color-body);
background: var(--color-primary-backdrop);
/* <sg-icon name="x" size="16"></sg-icon> Avoid — raw contrast values */
color: var(--color-contrast-800);
background: hsl(260deg 85% 65% / 20%);Respect the Contrast Scale
Use the background range (50–400) for surfaces and the text range (500–900) for text:
/* <sg-icon name="check" size="16"></sg-icon> Good */
background: var(--color-contrast-100); /* card surface */
color: var(--text-color-body); /* body text */
/* <sg-icon name="x" size="16"></sg-icon> Avoid — text-range value used as background */
background: var(--color-contrast-700);Override with light-dark() for Mode-Aware Colors
Flat overrides are always light (or always dark). Wrap in light-dark() to keep an override mode-aware:
:root {
/* <sg-icon name="check" size="16"></sg-icon> Adapts to light and dark (OKLCH recommended) */
--color-primary: light-dark(oklch(55% 0.18 220deg), oklch(62% 0.18 220deg));
/* <sg-icon name="triangle-alert" size="16"></sg-icon> Static — always the same regardless of color scheme */
--color-primary: oklch(58% 0.18 220deg);
}Maintain WCAG Compliance
When customizing colors, verify contrast ratios:
- AA: 4.5:1 for normal text, 3:1 for large text
- AAA: 7:1 for normal text, 4.5:1 for large text
:root {
/* OKLCH — perceptually uniform, wide gamut, light-dark() aware */
--custom-bg: light-dark(oklch(99% 0.004 264deg), oklch(13% 0.005 250deg));
--custom-text: light-dark(oklch(12% 0.008 264deg), oklch(95% 0.003 250deg)); /* ~17:1 in both modes — AAA ✓ */
}Set the Full Token Set for Custom Colors
When introducing a brand color, define all 7 sub-tokens so every component renders correctly in both modes:
.my-brand {
/* Use OKLCH for perceptual uniformity; wrap in light-dark() for automatic mode-switching */
--color-primary: light-dark(oklch(L% C H), oklch(L% C H));
--color-primary-backdrop: light-dark(oklch(93% C H), oklch(28% C H / 40%));
--color-primary-content: light-dark(oklch(22% C H), oklch(95% C H));
--color-primary-contrast: light-dark(oklch(98% C H), oklch(14% C H));
--color-primary-focus: light-dark(oklch((L + 6) % C H), oklch((L + 6) % C H));
--color-primary-border: light-dark(oklch((L + 6) % C H / 60%), oklch((L + 6) % C H / 60%));
--color-primary-focus-shadow: 0 0 0 4px color-mix(in oklch, var(--color-primary) 40%, transparent), var(--shadow-sm);
}