Design Tokens
Design tokens are the single source of truth for all visual values across the VENI-AI platform — colors, border radius, font sizes, and status colors. They are defined once in packages/design-tokens/ and distributed to every app as a committed, vendored CSS file.
1. Why Vendored, Not a Package?
Each SCS app is fully independent — its own repository, Docker build, and CI/CD pipeline. Using a workspace:* dependency would couple apps to the monorepo and break independent builds.
Instead, each app owns a committed copy of tokens.css. Updates are applied on the app team's own schedule via veni update-tokens.
packages/design-tokens/dist/css/tokens.css ← source of truth (committed)
│
├── veni update-tokens
│
├── shell/ui/src/themes/tokens.css ← shell copy
├── templates/veni-service/ui/src/themes/tokens.css ← template copy
└── apps/<name>/ui/src/themes/tokens.css ← per-app copy2. Package Structure
packages/design-tokens/
├── tokens/
│ ├── primitives/
│ │ ├── colors.json # Raw palette: blue, slate, red, green, amber
│ │ └── radius.json # --radius: 0.5rem
│ └── semantic/
│ ├── light.json # Light mode token roles
│ └── dark.json # Dark mode overrides
├── style-dictionary.config.mjs # Build script (Style Dictionary v3)
└── dist/css/tokens.css # ✅ Generated output — committed to repo3. Token Layers
Primitives
Raw named values. Never referenced directly in components.
// tokens/primitives/colors.json
{
"color": {
"blue": {
"600": { "value": "#2563eb", "type": "color" }
},
"slate": {
"900": { "value": "#0f172a", "type": "color" }
}
}
}Semantic Tokens
Named roles that map to primitives. These are the values components use.
// tokens/semantic/light.json
{
"semantic": {
"primary": { "value": "{color.blue.600}", "type": "color" },
"background": { "value": "{color.slate.50}", "type": "color" },
"foreground": { "value": "{color.slate.900}", "type": "color" },
"border": { "value": "{color.slate.200}", "type": "color" }
}
}// tokens/semantic/dark.json — overrides applied under .dark class
{
"semantic-dark": {
"primary": { "value": "{color.blue.500}", "type": "color" },
"background": { "value": "{color.slate.900}", "type": "color" }
}
}4. Generated Output
Style Dictionary resolves all references and builds a single tokens.css:
/* Primitives */
:root {
--radius: 0.5rem;
--font-size: 16px;
--font-weight-normal: 400;
--font-weight-medium: 500;
}
/* Semantic — Light */
:root {
--background: #f8fafc;
--foreground: #0f172a;
--primary: #2563eb;
--primary-foreground: #ffffff;
--border: #e2e8f0;
/* ... */
}
/* Semantic — Dark */
.dark {
--background: #0f172a;
--primary: #3b82f6;
/* ... */
}
/* Tailwind v4 @theme map */
@theme inline {
--color-background: var(--background);
--color-primary: var(--primary);
--color-border: var(--border);
--radius-sm: calc(var(--radius) - 4px);
--radius-lg: var(--radius);
/* ... */
}The @theme inline block tells Tailwind v4 which utility classes to generate (bg-primary, text-foreground, border-border, etc.).
5. Token Reference
| Token | Light | Dark | Usage |
|---|---|---|---|
--background | #f8fafc | #0f172a | Page background |
--foreground | #0f172a | #f8fafc | Default text |
--primary | #2563eb | #3b82f6 | Brand, buttons |
--primary-foreground | #ffffff | #ffffff | Text on primary |
--secondary | #f1f5f9 | #334155 | Secondary surfaces |
--muted | #f1f5f9 | #334155 | Muted backgrounds |
--muted-foreground | #64748b | #94a3b8 | Muted text |
--accent | #f1f5f9 | #334155 | Hover states |
--destructive | #dc2626 | #ef4444 | Errors, delete |
--border | #e2e8f0 | #334155 | Borders |
--input | transparent | #334155 | Input border |
--input-background | #f8fafc | #1e293b | Input fill |
--ring | #2563eb | #3b82f6 | Focus ring |
--card | #ffffff | #1e293b | Card surfaces |
--sidebar | #ffffff | #1e293b | Sidebar |
--sidebar-primary | #2563eb | #3b82f6 | Active nav item |
--success | #10b981 | #10b981 | Success |
--warning | #f59e0b | #f59e0b | Warning |
--info | #3b82f6 | #3b82f6 | Info |
--chart-1…5 | Blue scale | Blue scale | Charts |
--radius | 0.5rem | — | Border radius |
6. How Apps Consume Tokens
Each app has a committed tokens.css imported at the CSS entry point:
/* ui/src/themes/index.css */
@import 'tailwindcss';
@import './tokens.css'; /* ← vendored design tokens */
@import './themes.css'; /* ← optional per-app theme overrides */
@custom-variant dark (&:is(.dark *));No npm dependency. No runtime network request. The file is fully self-contained.
Using tokens in components
// ✅ Always use token-based Tailwind classes — they adapt to dark mode automatically
<div className="bg-background text-foreground">
<button className="bg-primary text-primary-foreground hover:bg-primary/90">
<p className="text-muted-foreground text-sm">
<div className="border border-border rounded-lg">
<input className="bg-input-background border-input focus:ring-ring">
// ❌ Never hardcode colors
<div className="bg-[#f8fafc]">
<button className="bg-blue-600">Dark mode is applied by adding .dark to <html> (handled by next-themes):
const { setTheme } = useTheme();
setTheme('dark'); // adds class="dark" to <html>7. CLI Commands
Update tokens across all apps
Rebuilds packages/design-tokens/dist/css/tokens.css and copies it into the shell, the template, and every apps/*/ui/src/themes/tokens.css that already has the file:
veni update-tokensOptions:
| Flag | Description |
|---|---|
--no-build | Skip rebuild, sync from existing dist/css/tokens.css |
Rebuild tokens only
cd packages/design-tokens && bun run build8. Adding a New Token
Step 1. Add the primitive value if needed (tokens/primitives/colors.json):
"purple": {
"500": { "value": "#8b5cf6", "type": "color" }
}Step 2. Add semantic entries in tokens/semantic/light.json and dark.json:
// light.json
"brand-accent": { "value": "{color.purple.500}", "type": "color" }
// dark.json
"brand-accent": { "value": "{color.purple.400}", "type": "color" }Step 3. Rebuild and sync:
veni update-tokensStep 4. Commit the updated tokens.css in each app repo. The new token is now available as --brand-accent (CSS var) and bg-brand-accent / text-brand-accent (Tailwind class).
9. Scaffolding New Apps
When veni create app <name> runs, the CLI automatically copies packages/design-tokens/dist/css/tokens.css into apps/<name>/ui/src/themes/tokens.css. The new app is ready to use tokens immediately with no extra setup.
10. Per-App Theme Overrides
Apps can override specific tokens without modifying tokens.css. The template ships with themes/themes.css:
/* Applied via className on root element */
.theme-blue {
--primary: var(--color-blue-700);
--sidebar-primary: var(--color-blue-600);
}
.theme-green {
--primary: var(--color-lime-600);
--sidebar-primary: var(--color-lime-600);
}These override semantic tokens at runtime — tokens.css is not modified.