@paradox-design/themes
v0.1.2
Published
Paradox Design System themes — overrides of the semantic token layer
Readme
@paradox-design/themes
Themes for Paradox Design System. A theme overrides only the semantic color layer — never the primitive palette and never dimension tokens.
Why
If a theme replaced --pdx-color-blue-600, it would change the look of every theme
that uses that palette. A theme says "what the surface background is", not "what blue is".
primitive (shared palette) ──► semantic (overridden by the THEME) ──► componentInstallation
npm install @paradox-design/tokens @paradox-design/themesInstall @paradox-design/tokens explicitly, even though it is already a dependency of this
package. Themes do not import the tokens themselves — your code does (below), and strict
package managers such as pnpm only resolve imports of direct dependencies.
Upgrade all
@paradox-design/*packages together, to their latest versions. In0.xa minor release may rename tokens; mixing minors leaves some variables undefined.
Usage
<html data-pdx-theme="dark"></html>@import '@paradox-design/tokens/css'; /* tokens — always first */
@import '@paradox-design/themes/css/default'; /* base values */
@import '@paradox-design/themes/css/dark'; /* variant */⚠️ Tokens must be loaded first. Themes contain only
var(--pdx-color-…)references — without the palette they resolve to empty values. Load the full@paradox-design/tokens/css: a theme overrides only semantic colors, so spacing, typography and the rest of the semantic layer still come from the tokens.
Automatic switching
@import '@paradox-design/themes/css/system';Applies the dark variant on prefers-color-scheme: dark until the user picks a theme
manually — an explicit data-pdx-theme on <html> has higher specificity and wins.
Available themes
| Theme | Mode | color-scheme | Purpose |
| --------- | -------- | -------------- | -------------------------------------- |
| default | complete | light | base theme |
| dark | complete | dark | dark variant |
| hc | complete | light | high contrast, AAA (7:1) threshold |
| jungle | partial | light | brand theme of the Jungle app |
complete vs partial
| | complete | partial |
| ----- | -------------------------------------------------------------------------- | ------------------------------------------------------------- |
| Scope | all semantic color tokens | only the differences from base |
| When | the theme changes lightness (dark, hc) | the theme changes the brand (action color, link) |
| Why | a missing token would fall back to the light palette → white text on white | copying unchanged values guarantees drift at the first change |
The mode is declared in themes.config.json; pnpm check enforces it automatically.
Custom theme
- Copy
dist/css/template.css. - Change the name in the
[data-pdx-theme='…']selector. - Override only what distinguishes your theme from the base.
- Set
color-scheme— native scrollbars and form controls depend on it.
The full list of tokens to override is in dist/css/default.css.
Quality checks
pnpm build # generates themes + runs the checks
pnpm check # checks onlyThe check-themes.mjs script verifies:
- completeness — a
completetheme covers the whole contract (currently 65 tokens), - no unknown tokens — a typo in a name never passes silently,
- WCAG contrast — 19 text/background and element/background pairs, with a 4.5:1 threshold
for text (7:1 in
hc) and 3:1 for non-text elements.
ℹ️
border-defaultis not checked for contrast,border-interactiveis. WCAG 1.4.11 applies to borders that identify a control (an input border, a checkbox outline) — that is whatborder-interactiveis for.border-defaultandborder-subtleare decorative (separators, card borders) and have no 3:1 requirement. Usingborder-defaultas a form field border is an accessibility bug.
Tokens with an alpha channel (e.g. surface-scrim) are skipped — their contrast depends on
a background that is unknown at build time.
License
MIT © Paradox Software
