@waukeshamakerspace/design-tokens
v2.0.0
Published
Waukesha Makerspace brand + semantic design tokens: CSS variables with automatic light/dark theming, Tailwind (v3 + v4) and MUI adapters
Downloads
747
Maintainers
Readme
@waukeshamakerspace/design-tokens
Waukesha Makerspace brand + semantic design tokens, extracted from the token
system pioneered in makers-cms. One package, consumed by every WMI UI, so
the brand hexes live in exactly one place.
- Brand layer (fixed): Burnt Orange
#E8600A, Charcoal#2B2B2B, Golden Yellow#F5A623, Warm White#F7F3EE— frommarketing/brand/brand-guidelines.md. - Semantic layer (themable):
surface,foreground,border,primary,secondary,accent,link,ring,warning/success/danger, each with light and dark values. Components use these, never brand hexes. Since v2,accentfollows the shadcn contract (subtle hover/selected background for menus and lists); for golden yellow use the brand layer (bg-golden-yellow/var(--brand-golden-yellow)) orwarning. - Fonts: Oswald (headings) + Open Sans (body), wired via
--font-heading/--font-body.
Cross-cutting UI rules (mobile-first, device-theme behavior, accessibility)
live in the architecture repo: waukesha-makers-architecture/shared/ui-guidelines.md.
Install
Published on the public npm registry at npmjs.com/package/@waukeshamakerspace/design-tokens — install it like any other public package. No auth, no custom registry config, no git/file dependency. Consuming projects must depend on the npm package, never on a local checkout of this repo.
npm install @waukeshamakerspace/design-tokensEnable Renovate or Dependabot in consuming apps so new token releases arrive as automatic bump PRs.
Dark mode model
css/tokens.css resolves the theme in this order:
.dark/.lightclass on<html>wins (set pre-paint by the theme-init script, or by an app-level toggle).- No class present → the OS
prefers-color-schemeapplies via media query.
So plain-CSS consumers follow the device theme with zero JS. Apps using
class-based styling (Tailwind dark:, MUI color schemes) inline the
theme-init script so the class follows the device too:
// Next.js: in app/layout.tsx <head>, before first paint
import { themeInitScript } from '@waukeshamakerspace/design-tokens/theme-init';
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />// SPA: top of the entry module
import { initTheme } from '@waukeshamakerspace/design-tokens/theme-init';
initTheme();?theme= testing override
?theme=light|dark in any URL wins over everything for that page load — the
standard WMI testing knob, built into the init script and initTheme(). It
sets the same override flag as setTheme, so OS-change syncing stays
suppressed until reload.
Non-standard setups
buildThemeInitScript(options) bakes options into the inline script:
import { buildThemeInitScript } from '@waukeshamakerspace/design-tokens/theme-init';
// Digital signage: force dark (never follow a TV's prefers-color-scheme);
// ?theme= still wins for testing.
buildThemeInitScript({ defaultMode: 'dark' });
// A sub-tree owns its own theming (it inlines its own script): tell the
// root-layout script to leave those paths alone.
buildThemeInitScript({ skipPathPrefixes: ['/signage'] });initTheme(window, { defaultMode }) accepts the same defaultMode for SPAs.
On-the-fly theme switching
Every app must expose a simple toggle so both modes can be exercised during
development (see shared/ui-guidelines.md). Build it on the exported
helpers so it coordinates with the OS-change listener instead of fighting it:
import { toggleTheme, setTheme, getTheme } from '@waukeshamakerspace/design-tokens/theme-init';
<button onClick={() => toggleTheme()}>Theme</button>
// or explicit: setTheme('dark') / setTheme('light') / setTheme('system')An explicit choice persists across visits (localStorage key wm-theme) and
is restored pre-paint by the init script, so there is no flash of the wrong
theme. setTheme('system') clears the stored choice and resumes following
the device, which remains the default for users who never chose. Choices
made in one tab are mirrored live into other open tabs, in any app that
called initTheme() or has a subscribeTheme() listener mounted (the
inline script alone applies stored choices at load, not live).
Reacting to theme changes
Every applied change (a setTheme call, an OS-theme change while following
the device, a choice from another tab) dispatches a wm-theme-change
CustomEvent on window. subscribeTheme wraps all the listeners:
import { subscribeTheme, getThemeMode, getTheme } from '@waukeshamakerspace/design-tokens/theme-init';
const unsubscribe = subscribeTheme(({ mode, theme }) => {
// mode: 'light' | 'dark' | 'system' (the user's choice)
// theme: 'light' | 'dark' (what's actually applied)
});React apps should prefer the useThemeMode() hook from
@waukeshamakerspace/react-components, which is built on this.
Usage
Tailwind v4 (makers-cms style)
/* globals.css */
@import "tailwindcss";
@import "@waukeshamakerspace/design-tokens/css/tailwind.css";Gives every token as a utility (bg-surface, text-foreground-muted,
bg-primary hover:bg-primary-hover, font-heading) plus a class-based
dark: variant.
shadcn components (Tailwind v4 only)
css/tokens.css ships shadcn's CSS variable contract, so vendored shadcn
source (as in @waukeshamakerspace/react-components) themes correctly in
both modes with no per-component CSS:
- Bridge aliases in
:root:--background,--card,--popover,--muted,--destructive,--input, and their-foregroundpartners, each pointing at the matching semantic token.--foreground,--primary,--secondary,--accent,--border, and--ringare native semantic tokens whose names and meanings already match shadcn's. --radius: 0.625rem, with the v4 layer deriving the shadcnrounded-sm/md/lg/xlscale from it. Note this overrides Tailwind's default radius scale for those four sizes.font-sansmaps to the brand body font (Open Sans).
Deliberate omissions: chart-* and sidebar-* variables (added when an app
needs them), and no bridge in the Tailwind v3 preset (react-components
requires v4).
Caveat: the alias names are generic; an app that defines its own
--background (or any other bridge name) on :root after importing
tokens.css will retheme shadcn components. To re-skin a subtree, override the
alias name (--card), not the token behind it (--surface-raised); alias
substitution happens on :root, so subtree overrides of the underlying token
don't flow through.
Tailwind v3
// tailwind.config.js
module.exports = {
presets: [require('@waukeshamakerspace/design-tokens/tailwind-preset')],
// ...
};/* global stylesheet */
@import "@waukeshamakerspace/design-tokens/css/tokens.css";Same utility names as v4. Caveat: opacity modifiers (bg-primary/50) don't
work on var()-based colors in v3.
MUI v6+
import { createMakersTheme } from '@waukeshamakerspace/design-tokens/mui';
const theme = createMakersTheme(); // pass overrides to deep-merge
<ThemeProvider theme={theme}>...</ThemeProvider>Light/dark schemes flip on the same .light/.dark class (via MUI
cssVariables.colorSchemeSelector), so MUI stays in sync with the CSS tokens.
themeOptions is also exported if you'd rather call createTheme yourself.
Plain CSS / anything else
@import "@waukeshamakerspace/design-tokens/css/tokens.css";
.card {
background: var(--surface-raised);
color: var(--foreground);
border: 1px solid var(--border);
}Fonts
Next.js apps: load Oswald + Open Sans with next/font and map them to
--font-heading / --font-body (see makers-cms layout.tsx).
Everything else: @import "@waukeshamakerspace/design-tokens/css/fonts.css";
(loads Google Fonts and wires the variables).
Brand assets (logos + favicons)
The package ships the logo masters and a favicon set generated from them:
| Path | What it is |
| --- | --- |
| assets/logo.png | Full logo, dark artwork, transparent, 1500×1500 |
| assets/logo-white.png | Full logo, white artwork, transparent, 1500×1500 |
| assets/logo-white-bg.png | Full logo on opaque white, 1500×1500 — favicon-set master |
| assets/favicon.ico | 16/32/48 multi-size, white background |
| assets/apple-touch-icon.png | 180×180, white background |
| assets/icon-192.png / assets/icon-512.png | PWA / web-manifest icons, white background |
Bundler apps (Vite, webpack, Next <Image>/metadata): import the file —
the bundler hashes the URL, so rebrands bust caches automatically.
import logo from '@waukeshamakerspace/design-tokens/assets/logo.png';// Next.js app router: app/layout.tsx
import favicon from '@waukeshamakerspace/design-tokens/assets/favicon.ico';
import appleIcon from '@waukeshamakerspace/design-tokens/assets/apple-touch-icon.png';
export const metadata = {
icons: { icon: favicon.src, apple: appleIcon.src },
};Anything that needs the files in public/: copy at build time, e.g.
// package.json
"prebuild": "cp node_modules/@waukeshamakerspace/design-tokens/assets/favicon.ico public/"Token values in JS
import { brand, light, dark, fonts, radius, shadcnAliases } from '@waukeshamakerspace/design-tokens';Editing tokens
index.js is the single source of truth for values; the masters for imagery
are assets/logo.png / assets/logo-white.png (app-facing) and
assets/logo-white-bg.png (favicon set). Edit those, then:
npm run build # regenerates css/tokens.css + the assets/ favicon setNever edit css/tokens.css or the generated icons (favicon.ico,
apple-touch-icon.png, icon-*.png) by hand. Brand-layer changes must go
through marketing/brand/brand-guidelines.md first.
Releasing
Automated by release-please. Write Conventional Commits
(fix: → patch, feat: → minor, feat!: → major); pushes to main accumulate
into a release PR, and merging it publishes to npm. Don't bump version by
hand.
