VisorVisor
Themes

Token Rules

The 9 prescriptive rules for authoring Visor components — fallbacks, shadows, spacing, motion, overlays, focus rings, color formats, theme structure, and no magic numbers.

Every component in Visor must comply with these 9 rules. They ensure consistency across the design system and guarantee that components work correctly with any compliant theme.

1. Fallback Rule

All CSS var() fallbacks must use Tailwind Gray palette hex values — not Slate, Zinc, or any other neutral scale. The Gray palette in @loworbitstudio/visor-core is the single source of truth.

/* Correct */
color: var(--text-primary, #111827);
border-color: var(--border-default, #e5e7eb);

/* Wrong — Slate palette */
color: var(--text-primary, #0f172a);
border-color: var(--border-default, #e2e8f0);
Gray palette reference
TokenHex
gray-50#f9fafb
gray-100#f3f4f6
gray-200#e5e7eb
gray-300#d1d5db
gray-400#9ca3af
gray-500#6b7280
gray-600#4b5563
gray-700#374151
gray-800#1f2937
gray-900#111827
gray-950#030712

2. Shadow Rule

All box-shadow declarations must use var(--shadow-*) tokens. No inline rgba shadow values.

/* Correct */
box-shadow: var(--shadow-md);

/* Wrong */
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);

Available tokens: --shadow-xs, --shadow-sm, --shadow-md, --shadow-lg, --shadow-xl.

Exception: Focus ring shadows using the color-mix() pattern are acceptable (see Rule 6).

3. Spacing Rule

All padding, gap, and margin values must use spacing tokens on the 4px grid.

/* Correct */
padding: var(--spacing-4);       /* 16px */
gap: var(--spacing-2);           /* 8px */

/* Wrong */
padding: 1rem;
gap: 8px;
TokenValue
--spacing-14px
--spacing-28px
--spacing-312px
--spacing-416px
--spacing-520px
--spacing-624px
--spacing-832px
--spacing-1040px
--spacing-1248px
--spacing-1664px

Exception: Component sizing (height, width) may use explicit values until sizing tokens are introduced.

3b. Semantic Alias Surface (VI-451)

In addition to the prefixed semantic tokens (--text-*, --surface-*, --border-*, --interactive-*), Visor's theme generator emits a bare-name alias surface in the visor-semantic cascade layer so consumers can reference brand/feedback colors, alpha-based hairlines, and discrete pixel-named scales directly.

Intent aliases (bare names):

TokenDefaultNotes
--primaryprimary-500Brand primary action color
--primary-text#ffffffText color paired with --primary backgrounds
--accentaccent-500
--successsuccess-500
--warningwarning-500
--destructiveerror-500shadcn naming
--infoinfo-500

Hairline aliases (alpha-based):

TokenLightDark
--hairlinergba(0,0,0,0.06)rgba(255,255,255,0.06)
--hairline-strongrgba(0,0,0,0.10)rgba(255,255,255,0.10)

Surface + text extensions: --surface-screen (deepest backdrop), --surface-elev (singular mid-elevation), --text-muted (slots between tertiary and disabled).

Discrete scales (mode-agnostic):

  • --text-N font sizes: 11, 13, 14, 16, 20, 24, 32, 40, 48 (px) — distinct value space from --text-primary (color)
  • --space-N 4px-grid: 1, 2, 3, 4, 5, 6, 8, 10, 12, 16 — derive from spacing.base

Override convention: Themes use flat keys for intent, prefixed for the rest:

overrides:
  dark:
    primary: "#6BEBA5"
    primary-text: "#1E1F21"
    destructive: "#FE5D8B"
    hairline: "rgba(255, 255, 255, 0.06)"
    surface-screen: "#0B0C0D"
    text-muted: "rgba(250, 252, 254, 0.32)"

Bare primary resolves to the intent group; text-primary (prefixed) continues to route to the text group.

4. Motion Rule

All transitions must use var(--motion-duration-*) for timing and var(--motion-easing-*) for easing.

/* Correct */
transition: color var(--motion-duration-fast) var(--motion-easing-default);

/* Wrong */
transition: color 0.15s ease;

Duration tokens:

SemanticValueUse case
--motion-duration-fast100msTooltips, hovers
--motion-duration-150150msSmall state changes
--motion-duration-normal200msModals, drawers
--motion-duration-slow500msPage transitions

Easing tokens:

SemanticUse case
--motion-easing-defaultGeneral purpose (ease-in-out)
--motion-easing-enterElements appearing (ease-out)
--motion-easing-exitElements leaving (ease-in)
--motion-easing-springBouncy interactions

5. Overlay Rule

Modal and dialog backdrops must use var(--overlay-bg).

/* Correct */
background: var(--overlay-bg);

/* Wrong */
background: rgba(0, 0, 0, 0.5);

6. Focus Ring Rule

Focus rings must use var(--focus-ring-width) and var(--focus-ring-offset). Two patterns are supported:

Outline-based (buttons, tabs, nav items):

.button:focus-visible {
  outline: var(--focus-ring-width) solid var(--border-focus);
  outline-offset: var(--focus-ring-offset);
}

Box-shadow-based (form inputs):

.input:focus-visible {
  border-color: var(--border-focus);
  box-shadow: 0 0 0 var(--focus-ring-width)
    color-mix(in srgb, var(--border-focus) 25%, transparent);
}

7. Color Format Rule

  • Hex from Tailwind Gray for var() fallbacks
  • color-mix() for dynamic derivation in theme files
  • rgba() only in primitive token definitions (shadows, overlay)
  • HSL deferred to Phase 5 (theme builder)

8. Theme Structure Rule

All themes follow the 5-section template:

  1. Shared tokens — accent colors, fonts, theme-specific primitives
  2. Dark mode overrides — all visor-core token overrides for dark
  3. Light mode overrides — all visor-core token overrides for light
  4. Framework bridge — framework-specific values (e.g., fumadocs HSL triplets)
  5. Creative extensions — animations, glass, gradients (creative themes only)

See Creating Themes for the full template and theme contract.

9. No Magic Numbers Rule

Every value must trace to a token or be documented as intentional.

/* Wrong */
padding: 0.625rem;

/* Correct — token */
padding: var(--spacing-3);

/* Correct — documented */
height: 2.25rem; /* 36px — size-md, sizing tokens deferred */