Creating Themes
How to create a Visor theme — the theme contract, 5-section template, standard vs creative themes, and the space theme as a reference implementation.
Visor themes are CSS files that override the design tokens from @loworbitstudio/visor-core. Any valid theme transforms every Visor component's appearance without touching component code.
Theme Contract
The theme contract defines which tokens a theme must override, which are optional, and how to add theme-specific extensions.
Required Tokens (~35)
A theme must override all of these for components to render correctly in both light and dark modes.
Text:
| Token | Purpose |
|---|---|
--text-primary | Primary body text |
--text-secondary | Supporting text |
--text-tertiary | Placeholder text |
--text-disabled | Disabled state |
--text-inverse | Text on inverted backgrounds |
--text-inverse-secondary | Secondary text on inverted backgrounds |
--text-link | Link text |
--text-link-hover | Link hover |
--text-success | Success feedback |
--text-warning | Warning feedback |
--text-error | Error feedback |
--text-info | Info feedback |
Surface:
| Token | Purpose |
|---|---|
--surface-page | Page background |
--surface-card | Card/panel background |
--surface-subtle | Subtle differentiation |
--surface-muted | Muted areas |
--surface-overlay | Overlay base color |
--surface-interactive-default | Interactive resting state |
--surface-interactive-hover | Interactive hover |
--surface-interactive-active | Interactive pressed |
--surface-selected | Persistent selected state (active nav item, currently-selected list row) |
Border:
| Token | Purpose |
|---|---|
--border-default | Standard borders |
--border-muted | Subtle borders |
--border-strong | Emphasized borders |
--border-focus | Focus ring color |
--border-disabled | Disabled borders |
Interactive:
| Token | Purpose |
|---|---|
--interactive-primary-bg | Primary button background |
--interactive-primary-bg-hover | Primary button hover |
--interactive-primary-text | Primary button text |
--interactive-secondary-bg | Secondary button background |
--interactive-secondary-bg-hover | Secondary button hover |
--interactive-secondary-text | Secondary button text |
--interactive-destructive-bg | Destructive button background |
--interactive-destructive-bg-hover | Destructive button hover |
--interactive-destructive-text | Destructive button text |
Optional Tokens (~15)
These have sensible defaults from visor-core. Override them to customize accent and status colors.
Includes: --surface-accent-subtle, --surface-accent-default, --surface-accent-strong, all status surfaces (success, warning, error, info in subtle and default variants), and status borders.
Letter-spacing ramp
The engine emits a 6-tier letter-spacing ramp from the theme's typography.letter-spacing
YAML block, falling back to the Visor default ramp per tier:
| Token | Default | Notes |
|---|---|---|
--letter-spacing-xl | 0.16em | Widest — eyebrows / overlines, uppercase |
--letter-spacing-lg | 0.1em | |
--letter-spacing-md | 0.05em | Anchor — body / default |
--letter-spacing-sm | 0.025em | |
--letter-spacing-xs | 0.01em | |
--letter-spacing-tight | -0.01em | Tightest — large display headings |
typography:
letter-spacing:
xl: "3.5rem" # any subset of tiers; omitted tiers use the default ramp
tight: "0.03rem"The legacy triad keys stay valid for back-compat and fold onto the ramp (normal→md,
wide→lg, tight→tight). The engine also emits --letter-spacing-normal (= md) and
--letter-spacing-wide (= lg) as aliases so older consumers keep resolving.
Extension Tokens (Namespaced)
Theme-specific tokens use a --{theme-name}-* prefix. Components must never depend on these — they are consumed only by theme-specific CSS.
/* Space theme extensions */
--space-glass: color-mix(in srgb, var(--accent) 15%, rgba(0, 0, 0, 0.3));
--space-surface-elevated: color-mix(in srgb, var(--accent) 8%, #121220);
/* Veronica theme extensions */
--veronica-warmth: hsl(24, 80%, 55%);5-Section Template
Every theme file follows this structure. Copy it as your starting point.
/* ── {Theme Name} — visor-core adapter ── */
/* ═══ Section 1: Shared tokens (mode-independent) ═══ */
.{theme-name}-theme {
--accent: #5b6fff;
/* --font-heading: "Custom Font", system-ui, sans-serif; */
min-height: 100vh;
background: var(--surface-page);
color: var(--text-primary);
}
/* ═══ Section 2: Dark mode overrides ═══ */
.dark .{theme-name}-theme {
--text-primary: /* dark value */;
--surface-page: /* dark value */;
--border-default: /* dark value */;
--interactive-primary-bg: /* dark value */;
/* ... all required tokens */
}
/* ═══ Section 3: Light mode overrides ═══ */
html:not(.dark) .{theme-name}-theme {
--text-primary: /* light value */;
--surface-page: /* light value */;
--border-default: /* light value */;
--interactive-primary-bg: /* light value */;
/* ... all required tokens */
}
/* ═══ Section 4: Framework bridge ═══ */
/* fumadocs HSL triplets, if applicable */
.dark .{theme-name}-theme {
--fd-background: /* H S% L% */;
/* ... */
}
/* ═══ Section 5: Creative extensions (creative themes only) ═══ */
/* Namespaced extension tokens + custom CSS */Theme Tiers
Standard Themes
Standard themes contain only token overrides — no custom CSS. They can be described entirely via .visor.yaml and are compatible with the theme builder (Phase 5).
name: "Corporate Blue"
tier: standard
tokens:
shared:
accent: "#1e40af"
dark:
text-primary: "#f3f4f6"
surface-page: "#0a0a0f"
# ... all required tokens
light:
text-primary: "#111827"
surface-page: "#ffffff"
# ... all required tokensUse standard themes when: you need brand-aligned colors and typography without visual effects.
Creative Themes
Creative themes include section 5 — visual effects, animations, and custom CSS that go beyond token overrides. They still satisfy the full theme contract.
Use creative themes when: you want gradients, glass effects, starfields, custom animations, or any visual treatment that cannot be expressed as a token value.
The space theme is Visor's reference creative theme. It extends the contract with:
- Starfield animation with twinkling stars
- Glass/translucent code blocks
- Sunrise gradient background (light mode)
- Custom font faces (Satoshi, Source Code Pro)
Distinguishing rule: If a theme needs anything in section 5, it is creative. If sections 1-4 fully describe it, it is standard.
Always Both Modes
Rule: Every theme that sets colors.neutral must also set colors-dark.neutral.
The theme engine derives surface.muted and surface.elev in dark mode from the neutral role at shades 700 and 800. If colors.neutral is set in the light palette but no colors-dark.neutral override exists, the engine falls back to its default neutral (#6B7280 — Tailwind Gray 500), meaning every brand that skips the dark override produces identical dark mid-surfaces regardless of brand color.
This is a first-class authoring contract: any theme that defines a custom neutral must define its dark-mode counterpart.
In your theme YAML
colors:
primary: "#6366f1"
neutral: "#7c6f9b" # brand-tinted neutral
colors-dark:
neutral: "#3d3554" # darkened + desaturated variantA good dark neutral is typically the light neutral shifted toward lower lightness (L 20–35%) and reduced chroma — not pure gray. Start with 70% lower luminance and 60% of the original chroma as a baseline, then adjust for contrast.
Enforcing the rule in CI
Use --strict-dark with visor theme validate to promote the DARK_LIGHT_PARITY warning to a hard error:
npx @loworbitstudio/visor theme validate .visor.yaml --strict-darkMigration path — opt-in to default:
- Add
colors-dark.neutralto all themes that setcolors.neutral. - Verify locally:
npx @loworbitstudio/visor theme validate .visor.yaml --strict-darkexits 0. - Add
--strict-darkto your CI validate step — once all themes pass, the flag is effectively the default.
Without --strict-dark, missing dark neutrals emit a DARK_LIGHT_PARITY warning (non-blocking). With it, they block CI until resolved.
Tips
- Use
color-mix()to derive surfaces from your accent color — this creates cohesive tinted surfaces. - Test both light and dark modes. A common mistake is only tuning one mode.
- Keep
--border-focushigh-contrast against both light and dark backgrounds. - Reference the space theme source as a working example.