Tokens overview
The @otfdashkit/tokens design system — CSS variables, palettes, and runtime theme switching.
@otfdashkit/tokens is the source-of-truth for every visual decision in OTF. It ships as
CSS custom properties (consumed by @otfdashkit/ui) and as a JS object (consumed by
@otfdashkit/ui-native via Tamagui). One source, two outputs.
What's in it
| Token bucket | Examples |
|---|---|
| Color | --background, --foreground, --primary, --card, --border, --ring, --chart-1..5 |
| Typography | --font-sans, --font-heading, --font-mono, --font-size-{xs..4xl}, --font-weight-*, --line-height-*, --letter-spacing-* |
| Border radius | --radius, --radius-sm, --radius-md, --radius-lg, --radius-xl |
| Spacing scale | Implicit via Tailwind 4 + the OTF preset |
| Motion | Default ease curves + duration tokens |
Every component reads exclusively from these variables. Never hardcode hex outside the tokens package — @otfdashkit/eslint-plugin-otf-design will flag it.
Web setup
Import the CSS once in your app's globals, before the component CSS:
@import '@otfdashkit/tokens/web.css';
@import '@otfdashkit/ui/styles';That's it. The tokens cascade onto :root and every component picks them up.
Native setup
Import the JS export inside your Tamagui config:
import { tokens, themes } from '@otfdashkit/tokens/native'
import { createTamagui } from '@otfdashkit/ui-native'
export default createTamagui({
tokens,
themes,
})The four shipped themes
OTF ships four palettes. Each has a light + dark variant.
| Class | Vibe | When to use |
|---|---|---|
theme-slate | Cool slate, neutral grays | Default — works for almost any product |
theme-warm | Warm purple, cream backgrounds | Lifestyle / wellness / creator products |
theme-cosmic | Magenta-violet on near-black | Editorial / showcase / portfolio |
theme-terminal | Pure mono — black on green | Developer tools / terminal-shaped apps |
The fitness-kit defaults to its own brand override (warm orange) which is scoped to
:root:not([class*="theme-"]) — i.e. it's the fallback when no theme-* class is set.
Switching to any of the four shipped themes overrides the orange brand.
Switching themes at runtime
Set the class on <html>:
document.documentElement.className = 'theme-warm dark'For zero-flicker first paint, do this before React hydrates:
<script dangerouslySetInnerHTML={{ __html: `
const t = localStorage.getItem('otf-theme') || 'theme-slate'
const s = localStorage.getItem('otf-color-scheme') || 'dark'
document.documentElement.className = \`\${t} \${s}\`
` }} />The kits use <FloatingThemePicker /> (rendered globally) and <ThemeSwitch /> inside Settings
to persist + apply the choice.
Authoring a custom palette
Copy any of the four shipped palette blocks from
packages/tokens/src/web.css and rename:
.theme-mybrand {
--background: 0 0% 100%;
--foreground: 220 8% 12%;
--primary: 212 100% 47%;
--primary-foreground: 0 0% 100%;
--card: 0 0% 100%;
--card-foreground: 220 8% 12%;
--border: 220 8% 92%;
--ring: 212 100% 47%;
--chart-1: 212 100% 47%;
--chart-2: 28 100% 56%;
--chart-3: 160 70% 40%;
--chart-4: 340 80% 56%;
--chart-5: 260 60% 56%;
}
.theme-mybrand.dark,
.theme-mybrand .dark {
--background: 220 12% 7%;
--foreground: 0 0% 96%;
--primary: 212 100% 60%;
/* … */
}Drop the file into your app's CSS, set
document.documentElement.className = 'theme-mybrand dark', and every OTF component
re-paints to the new palette.
Tailwind preset
For Tailwind 4 projects, import the OTF preset to get token-aware utility classes
(bg-background, text-foreground, border-border, bg-card, bg-primary, …):
import otfPreset from '@otfdashkit/tokens/tailwind-preset'
export default {
presets: [otfPreset],
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
}The kits use these utility classes exclusively — never inline style={{ background: '#...' }}.
Charts
Charts always use chart tokens:
<BarChart
data={data}
dataKey={['issues', 'completed']}
colors={['hsl(var(--chart-1))', 'hsl(var(--chart-3))']}
stacked
/>Each palette ships a chart-1 through chart-5. They're tuned for legibility against the palette's background, both light and dark.