Design Tokens¶
Design tokens are the building blocks of Pulp's theme system. They store colors, dimensions, typography, and motion values as structured data — not code. This guide covers how tokens work, how to create themes, and how to integrate with external design tools.
How Tokens Work¶
A Theme is three flat maps:
Theme
├── colors { "background": #0f0f1a, "accent": #58a6ff, ... }
├── dimensions { "spacing_sm": 8, "corner_radius_md": 8, ... }
└── strings { "font_family": "Inter", ... }
Every view in the tree can have its own theme. When a widget resolves a token, it walks up the parent chain until it finds a match:
This means you can override tokens for a specific section without affecting the rest of the UI.
Token Categories¶
Colors¶
| Token | Purpose | Dark Default | Light Default |
|---|---|---|---|
background |
Window/root background | #0f0f1a |
#f5f5f5 |
surface |
Panel/card background | #1a1a2e |
#ffffff |
surface_variant |
Alternate surface | #1e1e36 |
#f0f0f0 |
on_surface |
Text on surface | #e0e0e0 |
#1a1a1a |
accent |
Primary interactive color | #58a6ff |
#0066cc |
primary |
Brand/primary color | #58a6ff |
#0066cc |
secondary |
Secondary accent | #8b5cf6 |
#6633cc |
tertiary |
Tertiary accent | #ec4899 |
#cc3399 |
success |
Success/positive | #22c55e |
#16a34a |
warning |
Warning/caution | #f59e0b |
#d97706 |
error |
Error/destructive | #ef4444 |
#dc2626 |
Dimensions¶
| Token | Purpose | Default |
|---|---|---|
spacing_xs |
Extra-small spacing | 4 |
spacing_sm |
Small spacing | 8 |
spacing_md |
Medium spacing | 12 |
spacing_lg |
Large spacing | 16 |
spacing_xl |
Extra-large spacing | 24 |
corner_radius_sm |
Small border radius | 4 |
corner_radius_md |
Medium border radius | 8 |
corner_radius_lg |
Large border radius | 12 |
Typography¶
| Token | Purpose | Default |
|---|---|---|
font_family |
Primary font | "Inter" |
font_size_xs |
Extra-small text | 12 |
font_size_sm |
Small text | 14 |
font_size_md |
Medium text | 16 |
font_size_lg |
Large text | 18 |
font_weight_normal |
Normal weight | 400 |
font_weight_bold |
Bold weight | 700 |
Motion¶
| Token | Purpose | Default |
|---|---|---|
motion.duration.fast |
Quick transitions | 80 ms |
motion.duration.normal |
Standard transitions | 150 ms |
motion.duration.slow |
Slow/dramatic transitions | 300 ms |
motion.duration.meter_decay |
Meter falloff speed | 300 ms |
motion.duration.peak_hold |
Peak indicator hold time | 1500 ms |
motion.easing.interaction |
User interaction easing | ease_out_cubic |
motion.easing.enter |
Element enter easing | ease_out_quad |
motion.easing.exit |
Element exit easing | ease_in_quad |
Creating a theme.json¶
A theme JSON file defines token overrides. Only include tokens you want to change — unspecified tokens fall through to the parent or built-in default.
{
"colors": {
"background": "#0d1117",
"surface": "#161b22",
"surface_variant": "#1c2128",
"on_surface": "#c9d1d9",
"accent": "#58a6ff",
"primary": "#58a6ff",
"secondary": "#bc8cff",
"success": "#3fb950",
"warning": "#d29922",
"error": "#f85149"
},
"dimensions": {
"spacing_sm": 8,
"spacing_md": 12,
"corner_radius_sm": 6,
"corner_radius_md": 10
},
"strings": {
"font_family": "JetBrains Mono"
}
}
Using Tokens in JS¶
Built-in Themes¶
setTheme("dark"); // Default dark theme
setTheme("light"); // Light theme
setTheme("pro_audio"); // Pro Audio (DAW-style) theme
Reading Theme Data¶
const themeJson = getThemeJson();
const theme = JSON.parse(themeJson);
// theme.colors.accent → "#58a6ff"
Applying Partial Overrides¶
// Override just the accent color without replacing the entire theme
applyTokenDiff(JSON.stringify({
colors: {
accent: "#ff6b6b",
primary: "#ff6b6b"
}
}));
Motion Tokens¶
// Read motion timing from the theme
const duration = getMotionToken("motion.duration.normal"); // 150
// Use theme-consistent animation timing
animate("knob", "opacity", 1.0, duration, "ease_out_cubic");
// Override a motion token
setMotionToken("motion.duration.fast", 50);
Using Tokens in C++¶
// Set up a theme
Theme theme = Theme::dark();
// Override specific tokens
theme.colors["accent"] = Color::hex(0xff6b6b);
theme.dimensions["spacing_md"] = 16.0f;
// Apply to a view
root->set_theme(theme);
// Resolve a token (walks up parent chain)
Color bg = view.resolve_color("background", Color::hex(0x000000));
Loading from JSON¶
std::string json = read_file("ui/theme.json");
Theme custom = Theme::from_json(json);
root->set_theme(custom);
Serializing to JSON¶
Inheritance and Cascading¶
Tokens cascade through the view tree. Override tokens at any level:
Root (dark theme)
��── Header Panel (override: accent → red)
│ ├── Title Label → resolves accent as red
│ └── Subtitle → resolves accent as red
├── Main Panel (no overrides)
│ ├── Knob → resolves accent as blue (from root)
│ └── Fader → resolves accent as blue (from root)
└── Footer (override: surface → darker)
└── Label → resolves surface as darker, accent as blue
In JS:
// Override tokens for a specific panel
setBackground("header", getThemeColor("error")); // Use error red
// Children of "header" inherit the panel's visual context
In C++:
Theme header_theme;
header_theme.colors["accent"] = Color::hex(0xff4444);
header_panel->set_theme(header_theme);
// All children of header_panel resolve "accent" as red
Exporting to CSS Custom Properties¶
For web builds (WASM), tokens map to CSS custom properties:
:root {
--pulp-background: #0f0f1a;
--pulp-surface: #1a1a2e;
--pulp-accent: #58a6ff;
--pulp-spacing-sm: 8px;
--pulp-corner-radius-md: 8px;
--pulp-font-family: 'Inter', sans-serif;
}
Generate CSS from a theme:
Exporting to WGSL¶
For custom shader widgets, tokens are available as uniforms:
struct ThemeUniforms {
accent: vec4<f32>,
background: vec4<f32>,
surface: vec4<f32>,
on_surface: vec4<f32>,
}
@group(0) @binding(0) var<uniform> theme: ThemeUniforms;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
return mix(theme.background, theme.accent, uv.x);
}
Figma Integration¶
Export Pulp tokens to Figma-compatible format for design handoff:
The exported JSON uses Figma's token format:
{
"color": {
"background": { "value": "#0f0f1a", "type": "color" },
"accent": { "value": "#58a6ff", "type": "color" }
},
"spacing": {
"sm": { "value": "8", "type": "spacing" },
"md": { "value": "12", "type": "spacing" }
}
}
Import into Figma using the Tokens Studio plugin. Changes in Figma can be exported back to theme.json to keep design and code in sync.
Best Practices¶
-
Use tokens, not hardcoded values. Write
getThemeColor("accent")instead of"#58a6ff". This ensures your UI respects theme switching. -
Override at the narrowest scope. Don't replace the entire theme when you only need a different accent color for one panel.
-
Keep motion tokens consistent. All animations in the same category (interactions, enters, exits) should use the same duration and easing.
-
Name custom tokens with namespaces. If you add plugin-specific tokens, prefix them:
mysynth.oscillator_color,mysynth.filter_glow. -
Test with all three built-in themes. A UI that only looks good in dark mode is incomplete.