Theme & tokens

Every visual value in Joy runs through CSS custom properties. Override a token after loading joy-tokens.css and the whole system — buttons, cards, dock, modal — picks up the change.

Color tokens

--joy-accent#cd0000
--joy-accent-hover#a30000
--joy-accent-softaccent at 10% (chips, badges)
--joy-bgpage background
--joy-bg-altsecondary page background
--joy-surfacecard background
--joy-textprimary text
--joy-text-mutedsecondary text
--joy-text-on-accenttext drawn on top of --joy-accent

Which one to reach for

TokenUse it for
--joy-accentsolid buttons, active/selected states, links, focus rings — the one color that means "this is interactive or selected"
--joy-accent-softa light wash behind something accent-colored — chip backgrounds, badge backgrounds, hover fills
--joy-textheadings and primary body copy
--joy-text-mutedsecondary copy, captions, placeholder-style text, anything that should read as less important
--joy-text-on-accenttext or icons placed directly on an --joy-accent background, so it stays readable in both themes
--joy-bg / --joy-surfacepage background vs. a card/panel sitting on top of it — keeping them as two tokens is what gives cards their contrast

Glass tokens

The translucent-blur look (topbar, dock, modal, .joy-glass from Cards & layout) is also token-driven:

TokenRole
--joy-glass-bgdefault translucent fill
--joy-glass-bg-strongmore opaque variant, used where legibility matters more than the effect (hovered dock item, modal)
--joy-glass-borderthe hairline border every glass surface gets
--joy-glass-shadowthe soft drop shadow underneath

Blur scale

Not tied to any single component — reach for these directly in your own CSS with backdrop-filter: blur(var(--joy-blur-md)) or similar:

TokenValue
--joy-blur-sm10px
--joy-blur-md16px
--joy-blur-lg24px

Building your own theme

A theme override is just CSS loaded after joy-tokens.css that redefines some of its variables — no build step, no config file, no fork of the source. Here's a complete example: a purple brand color, plus a matching dark-mode variant:

:root {
    /* Light mode — loads after joy-tokens.css, before joy-dark.css */
    --joy-accent: #6d28d9;
    --joy-accent-hover: #5b21b6;
    --joy-accent-glow: rgba(109, 40, 217, 0.35);
    --joy-accent-soft: rgba(109, 40, 217, 0.1);
}

/* Dark mode — joy-dark.css only redefines color tokens under :root, with
   no extra selector guarding it (see "How the switch works" below), so
   your override needs the same specificity: plain :root, placed AFTER
   the <link id="joy-dark-theme"> tag in the <head> order. */
:root {
    --joy-accent-soft: rgba(109, 40, 217, 0.18);
}

Order in the <head> is what decides the outcome, since every rule here is a plain :root selector (same specificity). Load your override stylesheet after both joy-tokens.css and joy-dark.css so it wins regardless of which theme is active.

Dark mode: how the switch actually works

joy-dark.css is a second stylesheet tag, always present in the page but starting disabled. Almost all of it is :root variable redefinitions — the same variable names as joy-tokens.css, different values — plus a small number of component-specific tweaks where a plain token swap wasn't enough (the background blobs, and the dock's selected-item text color). It never touches layout or spacing, so enabling it can't rearrange anything, only recolor it.

<link id="joy-dark-theme" rel="stylesheet" href="https://cdn.vaneltonmedia.com/joy/styles/joy-dark.css" disabled>

Toggling the theme is one line: flip that disabled attribute. JoyTheme.toggle() does exactly this, plus saves the choice:

ActionHow
Togglewindow.JoyTheme.toggle() — or click a button with id="joy-theme-toggle", which joy.js wires up on its own
Force a themewindow.JoyTheme.apply('dark') or .apply('light')
Persistencesaved to localStorage['selectedTheme'], read automatically on the next visit
No saved choicefalls back to the OS prefers-color-scheme

Which tokens actually change between the two themes — everything else (spacing, radius, easing, accent unless you override it) stays identical:

TokenLightDark
--joy-bg#f0f2f5#141417
--joy-bg-alt#eaeef2#1a1a1f
--joy-surface#ffffff#1c1c22
--joy-text#14141a#f5f5f7
--joy-text-muted#55555f#a8a8b3
--joy-glass-bgrgba(255,255,255,.6)rgba(28,28,34,.55)
--joy-glass-bg-strongrgba(255,255,255,.85)rgba(28,28,34,.85)
--joy-glass-borderrgba(255,255,255,.8)rgba(255,255,255,.08)
--joy-accent-softrgba(205,0,0,.1)rgba(205,0,0,.18)

--joy-accent itself doesn't change between themes by default — the same brand red reads fine on both. If your brand color needs to shift for dark mode too (a very light color might not have enough contrast on a dark surface, for instance), redefine it inside your own dark-mode block, the same way joy-dark.css redefines --joy-accent-soft above.

No-flash-of-wrong-theme script — reads the saved choice before first paint and flips the attribute immediately, so there's no visible flicker on load. This goes right after the <link id="joy-dark-theme"> tag:

<script>(function(){var t=localStorage.getItem('selectedTheme')||(matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');if(t==='dark'){document.getElementById('joy-dark-theme').disabled=false;}})();</script>

Spacing scale

TokenValue
--joy-space-14px
--joy-space-28px
--joy-space-312px
--joy-space-416px
--joy-space-524px
--joy-space-632px
--joy-space-748px
--joy-space-864px

Every margin/padding/gap utility in Sizing & spacing and Flexbox & grid maps to one of these — override the scale here and every one of those classes follows.

Border radius

TokenValue
--joy-radius-sm14px
--joy-radius-md20px
--joy-radius-lg28px
--joy-radius-xl40px
--joy-radius-pill999px

Easing

TokenValueUsed for
--joy-easecubic-bezier(0.25, 0.8, 0.25, 1)Default transitions
--joy-ease-bouncecubic-bezier(0.175, 0.885, 0.32, 1.275)Hover/click bounce (buttons, dock)

Font

Everything uses --joy-font: 'Poppins', sans-serif;. To swap it, override the token and load whichever font you prefer instead of the Google Fonts link (see Getting started). For the full size scale — proportional steps and fixed-pixel options — see Typography.