Skip to content

Design Tokens

Design tokens are the atomic values that define Voidable’s visual language. Every color, spacing step, font size, shadow, radius, and motion curve is expressed as a CSS custom property prefixed with --void-. Components consume these tokens rather than hard-coding values, so a single override propagates everywhere.

Tokens ship in the @voidable/theme package. After importing it, all tokens are available as standard CSS custom properties:

@import '@voidable/theme';

The theme is organized into two tiers:

  • Primitives (primitives.css) — Raw scales with no semantic meaning. Color palettes, spacing steps, font stacks.
  • Semantic tokens (tokens.css) — Purpose-driven aliases like --void-color-bg and --void-color-text that reference primitives and adapt based on the active theme.

Primitive color palettes are always available on :root regardless of the active theme. They exist so semantic tokens (and your own overrides) can reference them.

TokenValue
--void-stone-50#fafaf9
--void-stone-100#f5f5f4
--void-stone-200#e7e5e4
--void-stone-300#d6d3d1
--void-stone-400#a8a29e
--void-stone-450#908a85
--void-stone-500#78716c
--void-stone-600#57534e
--void-stone-700#44403c
--void-stone-800#292524
--void-stone-900#1c1917
--void-stone-950#0c0a09
TokenValue
--void-red-50#fef2f2
--void-red-100#fee2e2
--void-red-200#fecaca
--void-red-300#fca5a5
--void-red-400#f87171
--void-red-500#ef4444
--void-red-600#dc2626
--void-red-700#b91c1c
--void-red-800#991b1b
--void-red-900#7f1d1d
--void-red-950#450a0a
TokenValue
--void-amber-50#fefae4
--void-amber-100#fdf1be
--void-amber-200#f9e182
--void-amber-300#f1cb48
--void-amber-400#e5c03c
--void-amber-500#cca93c
--void-amber-600#a88a2a
--void-amber-700#846b20
--void-amber-800#655118
--void-amber-900#503f13
--void-amber-950#30260b
TokenValue
--void-green-50#f0fdf4
--void-green-100#dcfce7
--void-green-200#bbf7d0
--void-green-300#86efac
--void-green-400#4ade80
--void-green-500#22c55e
--void-green-600#16a34a
--void-green-700#15803d
--void-green-800#166534
--void-green-900#14532d
--void-green-950#052e16
TokenValue
--void-blue-50#eff6ff
--void-blue-100#dbeafe
--void-blue-200#bfdbfe
--void-blue-300#93c5fd
--void-blue-400#60a5fa
--void-blue-500#3b82f6
--void-blue-600#2563eb
--void-blue-700#1d4ed8
--void-blue-800#1e40af
--void-blue-900#1e3a8a
--void-blue-950#172554
TokenValue
--void-purple-50#faf5ff
--void-purple-100#f3e8ff
--void-purple-200#e9d5ff
--void-purple-300#d8b4fe
--void-purple-400#c084fc
--void-purple-500#a855f7
--void-purple-600#9333ea
--void-purple-700#7e22ce
--void-purple-800#6b21a8
--void-purple-900#581c87
--void-purple-950#3b0764
TokenValue
--void-pink-50#fdf2f8
--void-pink-100#fce7f3
--void-pink-200#fbcfe8
--void-pink-300#f9a8d4
--void-pink-400#f472b6
--void-pink-500#ec4899
--void-pink-600#db2777
--void-pink-700#be185d
--void-pink-800#9d174d
--void-pink-900#831843
--void-pink-950#500724
TokenValuePurpose
--void-black#000000Pure black
--void-white#ffffffPure white
--void-dark-1#0a0a0aDark surface step 1 (near-black elevation)
--void-dark-2#0b0b0bDark surface step 2
--void-dark-3#111111Dark surface step 3
--void-dark-4#141414Dark surface step 4

Semantic tokens carry purpose — “the background color” rather than “stone-900”. They change automatically between dark and light themes via the data-theme attribute on your root element.

Dark mode is the default. Set data-theme="light" on <html> to activate light mode.

TokenDark valueLight value
--void-color-bgvar(--void-black)var(--void-white)
--void-color-bg-secondaryvar(--void-dark-1)var(--void-stone-100)
--void-color-bg-elevatedvar(--void-dark-3)var(--void-white)
--void-color-bg-hovervar(--void-dark-4)var(--void-stone-200)
--void-color-bg-accentvar(--void-dark-2)var(--void-stone-100)
--void-color-bg-overlayblack 70% / transparentblack 30% / transparent
TokenDark valueLight value
--void-color-textvar(--void-stone-100)var(--void-stone-900)
--void-color-text-secondaryvar(--void-stone-400)var(--void-stone-600)
--void-color-text-mutedvar(--void-stone-500)var(--void-stone-500)
--void-color-text-tertiaryvar(--void-stone-450)var(--void-stone-400)
--void-color-text-disabledvar(--void-stone-600)var(--void-stone-300)
--void-color-text-on-accentvar(--void-black)var(--void-white)
TokenDark valueLight value
--void-color-borderwhite 14% / transparentvar(--void-stone-300)
--void-color-border-strongwhite 22% / transparentvar(--void-stone-400)
--void-color-border-focuswhite 55% / transparentvar(--void-stone-600)
TokenDark valueLight value
--void-color-accentvar(--void-white)var(--void-stone-900)
--void-color-accent-hovervar(--void-stone-200)var(--void-stone-950)

Status tokens map to the primitive palettes. Dark mode uses the -500 stop; light mode uses -600 for better contrast on white backgrounds.

TokenDark valueLight value
--void-color-errorvar(--void-red-500)var(--void-red-600)
--void-color-warningvar(--void-amber-500)var(--void-amber-600)
--void-color-successvar(--void-green-500)var(--void-green-600)
--void-color-infovar(--void-blue-500)var(--void-blue-600)
--void-color-noticevar(--void-purple-500)var(--void-purple-600)
--void-color-highlightvar(--void-pink-500)var(--void-pink-600)

Subtle status colors use color-mix() to create low-opacity tinted backgrounds. Dark mode uses 14% opacity; light mode uses 8%.

TokenDark valueLight value
--void-color-error-subtleerror 14% / transparenterror 8% / transparent
--void-color-warning-subtlewarning 14% / transparentwarning 8% / transparent
--void-color-success-subtlesuccess 14% / transparentsuccess 8% / transparent
--void-color-info-subtleinfo 14% / transparentinfo 8% / transparent
--void-color-notice-subtlenotice 14% / transparentnotice 8% / transparent
--void-color-highlight-subtlehighlight 14% / transparenthighlight 8% / transparent

Spacing tokens use a numeric scale based on a 0.25rem (4px) grid. Named aliases provide T-shirt sizing for common use.

TokenValue
--void-space-10.25rem (4px)
--void-space-1h0.375rem (6px)
--void-space-20.5rem (8px)
--void-space-2h0.625rem (10px)
--void-space-30.75rem (12px)
--void-space-41rem (16px)
--void-space-51.25rem (20px)
--void-space-61.5rem (24px)
--void-space-71.75rem (28px)
--void-space-82rem (32px)
--void-space-92.25rem (36px)
--void-space-102.5rem (40px)
--void-space-123rem (48px)
--void-space-143.5rem (56px)
--void-space-153.75rem (60px)
--void-space-164rem (64px)
--void-space-205rem (80px)
--void-space-246rem (96px)
--void-space-287rem (112px)
--void-space-307.5rem (120px)
--void-space-328rem (128px)
--void-space-358.75rem (140px)
--void-space-4010rem (160px)
TokenMaps to
--void-space-xs--void-space-1 (0.25rem)
--void-space-sm--void-space-2 (0.5rem)
--void-space-md--void-space-3 (0.75rem)
--void-space-lg--void-space-4 (1rem)
--void-space-xl--void-space-6 (1.5rem)

Defined in tokens.css, these mirror the named spacing primitives:

TokenMaps to
--void-spacing-xsvar(--void-space-xs)
--void-spacing-smvar(--void-space-sm)
--void-spacing-mdvar(--void-space-md)
--void-spacing-lgvar(--void-space-lg)
--void-spacing-xlvar(--void-space-xl)

TokenValue
--void-text-2xs0.625rem (10px)
--void-text-xs0.75rem (12px)
--void-text-sm0.8125rem (13px)
--void-text-base0.875rem (14px)
--void-text-md1rem (16px)
--void-text-lg1.125rem (18px)
--void-text-xl1.25rem (20px)
--void-text-2xl1.5rem (24px)
--void-text-3xl1.875rem (30px)
--void-text-4xl2.25rem (36px)
--void-text-5xl3rem (48px)
TokenValue
--void-weight-normal400
--void-weight-medium500
--void-weight-semibold600
--void-weight-bold700
TokenValueNotes
--void-leading-display0.92Large display headings
--void-leading-none1No extra leading
--void-leading-snug1.05Slightly more than none
--void-leading-tight1.2
--void-leading-normal1.5
--void-leading-relaxed1.7
--void-leading-loose1.75Extra loose for code blocks
TokenValueUse case
--void-tracking-tightest-0.05emDisplay text, wordmarks
--void-tracking-tighter-0.04emLarge headings
--void-tracking-tight-0.025emHeadings
--void-tracking-snug-0.01emSubheadings, card titles
--void-tracking-normal0emBody text (default)
--void-tracking-wide0.04emTags, badges
--void-tracking-wider0.06emLabels
--void-tracking-widest0.1emUppercase labels, section headers
TokenValue
--void-font-sans'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif
--void-font-mono'JetBrains Mono', ui-monospace, 'SF Mono', 'Fira Code', monospace

TokenValue
--void-radius-none0
--void-radius-xs4px
--void-radius-sm6px
--void-radius-md8px
--void-radius-lg12px
--void-radius-xl16px
--void-radius-full9999px

The data-shape="sharp" attribute on a parent element resets all radius tokens to 0, giving every component a sharp-cornered appearance:

<html data-shape="sharp">

Shadows use white-based rgba values for glow-style elevation on dark surfaces.

TokenValue
--void-shadow-sm0 1px 3px rgba(255,255,255,.14)
--void-shadow-md0 2px 6px rgba(255,255,255,.12), 0 10px 24px rgba(255,255,255,.20)
--void-shadow-lg0 4px 10px rgba(255,255,255,.16), 0 22px 50px rgba(255,255,255,.28)
--void-shadow-xl0 6px 14px rgba(255,255,255,.20), 0 40px 88px rgba(255,255,255,.38)

Subtle 1px ring effects for card edges and elevated surfaces:

TokenValue
--void-ring-hair0 0 0 1px rgba(255,255,255,.06)
--void-ring-hair-strong0 0 0 1px rgba(255,255,255,.12)

TokenValue
--void-duration-fast100ms
--void-duration-normal200ms
--void-duration-slow300ms
TokenValue
--void-ease-incubic-bezier(0.4, 0, 1, 0.2)
--void-ease-outcubic-bezier(0, 0, 0.2, 1)
--void-ease-in-outcubic-bezier(0.4, 0, 0.2, 1)

A fixed stacking order so overlapping elements layer predictably:

TokenValue
--void-z-dropdown100
--void-z-sticky200
--void-z-modal300
--void-z-toast400
--void-z-tooltip500

TokenValue
--void-icon-stroke-width1.5

TokenValue
--void-sidebar-width15rem
--void-sidebar-collapsed-width3.75rem

Many Voidable components use an internal --tone custom property pattern for color theming. Instead of writing separate rulesets for every color variant, each component defines:

void-badge {
--tone: var(--void-color-accent);
background: var(--tone);
}
void-badge[color="error"] { --tone: var(--void-color-error); }
void-badge[color="warning"] { --tone: var(--void-color-warning); }
void-badge[color="success"] { --tone: var(--void-color-success); }

Some components derive additional tones for subtle backgrounds and borders:

void-stat {
--tone: var(--void-color-accent);
--tone-subtle: color-mix(in srgb, var(--tone) 14%, transparent);
--tone-border: color-mix(in srgb, var(--tone) 36%, transparent);
background: var(--tone-subtle);
border: 1px solid var(--tone-border);
}

You set the tone from HTML using the color attribute:

<void-badge color="error">3</void-badge>
<void-stat color="success">+12%</void-stat>

The available color values are: error, warning, success, info, notice, and highlight. The default (no attribute) uses --void-color-accent.


Reference any token as a standard CSS custom property:

.my-card {
background: var(--void-color-bg-elevated);
border: 1px solid var(--void-color-border);
border-radius: var(--void-radius-md);
padding: var(--void-space-4);
font-family: var(--void-font-sans);
font-size: var(--void-text-sm);
color: var(--void-color-text);
box-shadow: var(--void-shadow-md);
transition: background var(--void-duration-normal) var(--void-ease-out);
}
.my-card:hover {
background: var(--void-color-bg-hover);
}

Tokens compose naturally. Build a status banner using semantic colors:

.status-banner {
padding: var(--void-space-3) var(--void-space-4);
border-radius: var(--void-radius-sm);
font-size: var(--void-text-sm);
font-weight: var(--void-weight-medium);
line-height: var(--void-leading-normal);
}
.status-banner.error {
background: var(--void-color-error-subtle);
color: var(--void-color-error);
}
.status-banner.success {
background: var(--void-color-success-subtle);
color: var(--void-color-success);
}

Override tokens at any scope to customize a subtree:

.branded-section {
--void-color-accent: #3b82f6;
--void-color-accent-hover: #2563eb;
--void-color-text-on-accent: #ffffff;
}

Every Voidable component inside .branded-section will pick up the new accent color automatically.