Browse docs
Browse docs
Design tokens are the source of truth for the visual layer. In @dashforge/tw they are:
@dashforge/tw-tokens (a pure TypeScript package, no runtime).dashforgePreset() from @dashforge/tw-theme).<DashforgeTailwindProvider> at runtime.That triplet — types, utilities, CSS variables — is what makes a single token change ripple through your entire app without a rebuild.
You're already using tokens if you wrote bg-primary-600 or text-neutral-900. Those classes don't resolve to a hardcoded color; they resolve to a CSS variable the provider can repaint at runtime.
To override the primary palette for your whole app, do this once at boot:
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({
colors: {
primary: {
50: '#f0fdf4', 100: '#dcfce7', 200: '#bbf7d0',
300: '#86efac', 400: '#4ade80', 500: '#22c55e',
600: '#16a34a', 700: '#15803d', 800: '#166534',
900: '#14532d', 950: '#052e16',
},
},
});That's the full surface for "make our primary green". <Button color="primary">, bg-primary-500, dark:bg-primary-400 — all of them flip immediately.
Every theme is a TWTheme object. Top-level keys:
| Key | Type | What it controls |
|---|---|---|
colors | TWColorTokens | Seven semantic intents, each a 50–950 scale. |
spacing | TWSpacingScale | The p-, m-, gap-, space-x- etc. scale. |
radius | TWRadiusTokens | The rounded-* scale. |
fontSize | TWFontSizeTokens | The text-* scale. |
meta | TWThemeMeta | Mode (light / dark), name. |
The two ready-made themes — defaultTWThemeLight and defaultTWThemeDark — are exported from @dashforge/tw-tokens. You almost always patch them rather than replace them whole.
Seven semantic intents:
| Intent | Use for |
|---|---|
primary | Brand actions, primary buttons, focus outlines |
secondary | Secondary brand actions |
success | Confirmations, positive deltas |
warning | Cautionary states (non-blocking) |
danger | Destructive actions, errors |
info | Neutral informational accents |
neutral | Surfaces, borders, text |
Each intent is a 50–950 scale (Tailwind convention):
import type { TWColorScale } from '@dashforge/tw-tokens';
// TWColorScale = { '50': string, '100': string, ..., '900': string, '950': string }Use the same step across intents for the same emphasis — bg-primary-50 and bg-danger-50 are both very light backgrounds; text-primary-700 and text-danger-700 are both heavy foregrounds. This convention is what keeps the visual rhythm consistent across an app.
Rem-based scale matching Tailwind's defaults:
'0' '0.5' '1' '2' '3' '4' '6' '8' '12' '16' '24'Emitted as p-2, m-4, gap-6, etc. The defaults are deliberately conservative — we don't ship p-7 because there's almost no good reason to introduce a non-standard step. Extend the preset if your design system needs more.
none · sm · md · lg · xl · 2xl · fullEmitted as rounded, rounded-sm, rounded-md, ..., rounded-full. none is 0px, full is 9999px.
xs · sm · base · lg · xl · 2xl · 3xl · 4xlStandard Tailwind type scale. Line heights pair sensibly per step (leading-relaxed for prose, leading-tight for display).
┌────────────────────────────────┐
│ @dashforge/tw-tokens │ ← typed token surface
│ (defaultTWThemeLight, etc.) │
└──────────────┬─────────────────┘
│ consumed by
▼
┌────────────────────────────────┐
│ @dashforge/tw-theme │
│ • dashforgePreset() │ ← Tailwind preset (utilities)
│ • <DashforgeTailwindProvider> │ ← runtime (CSS vars on <html>)
└──────────────┬─────────────────┘
│ used by
▼
┌────────────────────────────────┐
│ @dashforge/tw │ ← components (Button, ...)
│ + your JSX │
└────────────────────────────────┘The preset emits utilities like bg-primary-500 whose value is var(--tw-color-primary-500). The provider sets --tw-color-primary-500 on <html>. Change the variable, the utility's resolved color changes — no re-render, no rebuild.
Smallest possible change. Useful for brand colors.
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({ colors: { primary: { 500: '#0d9488', 600: '#0f766e' } } });A partial scale is fine — unspecified steps fall back to the current theme's values.
Best for "we have a complete custom design system":
import { replaceTheme } from '@dashforge/tw-theme';
import { myCustomLightTheme, myCustomDarkTheme } from './theme';
replaceTheme({ light: myCustomLightTheme, dark: myCustomDarkTheme });replaceTheme swaps both modes at once, so you don't end up with a half-applied state during a transition.
classNameSometimes you want to break the rules locally — a campaign banner, a one-off "look at this" pill:
<Button color="primary" className="bg-fuchsia-600 hover:bg-fuchsia-700">
Limited offer
</Button>Merged via tailwind-merge so your one-off wins cleanly over the variant's default.
❌ Hardcoding hex colors in component code:
<div className="bg-[#22c55e]">…</div>The intent is lost. A theme override won't touch it. Use bg-success-500 and let the token system carry the weight.
✅ Use the token utility:
<div className="bg-success-500">…</div>❌ Reaching into the token object at render time:
import { defaultTWThemeLight } from '@dashforge/tw-tokens';
<div style={{ color: defaultTWThemeLight.colors.primary[600] }}>…</div>This reads the build-time default, not the current runtime theme. If the user patched the theme, this <div> won't follow.
✅ Read the live theme via the hook:
import { useDashTWTheme } from '@dashforge/tw-theme';
const { theme } = useDashTWTheme();
<div style={{ color: theme.colors.primary[600] }}>…</div>Or, better: use the Tailwind utility — it already does this for you, via CSS variables.
@dashforge/tw-tokens exports:
defaultTWThemeLight, defaultTWThemeDark, defaultTWTheme — the ready-made themesTWTheme, TWColorTokens, TWColorScale, TWSpacingScale, TWRadiusTokens, TWFontSizeTokens, TWThemeMeta — type contracts@dashforge/tw-theme exports:
dashforgePreset() — Tailwind preset<DashforgeTailwindProvider> — runtime provideruseDashTWTheme() — reactive hook (Valtio-backed)setMode('light' | 'dark'), toggleMode() — mode controlssetTheme(themeForMode), replaceTheme({ light, dark }), patchTheme(partial) — theme controlstwThemeCssVars(theme) — pure CSS-var map builderserverSideStyleTag(theme) — SSR helper, returns the <style> to inline before hydration (prevents FOUC)For mode-coordination with the docs lab's MUI side and for SSR notes, see the Theme System overview on the MUI docs — the principles are the same; only the underlying renderer differs.