Custom Themes
Customize colors and appearance with theme presets and custom tokens.
DuckViz uses a CSS custom property-based theme system. You can switch between 10 built-in presets, build a custom theme from seed colors, or toggle light/dark mode — all at runtime.
Using built-in presets
Wrap your app in <DuckvizThemeProvider> and pass a preset name:
import { DuckvizThemeProvider } from "@duckviz/ui";
<DuckvizThemeProvider preset="ocean" mode="dark">
{children}
</DuckvizThemeProvider>Available presets
| Preset | Primary | Description |
|---|---|---|
terracotta | Warm orange-brown | Default DuckViz brand |
ocean | Blue | Cool blues |
forest | Green | Deep greens |
slate | Gray | Neutral slate |
lavender | Purple | Soft lavender |
ember | Orange-red | Warm embers |
midnight | Dark blue | Deep midnight |
copper | Copper | Warm copper |
rose | Pink | Soft rose |
sage | Olive green | Earthy sage |
Switching themes at runtime
Use React state to switch themes dynamically:
"use client";
import { useState } from "react";
import { DuckvizThemeProvider, ALL_PRESETS, type DuckvizThemePreset } from "@duckviz/ui";
function App({ children }) {
const [preset, setPreset] = useState<DuckvizThemePreset>(ALL_PRESETS[0]!);
const [mode, setMode] = useState<"light" | "dark">("dark");
return (
<DuckvizThemeProvider preset={preset} mode={mode}>
{/* Theme picker */}
<select onChange={(e) => {
const p = ALL_PRESETS.find((p) => p.name === e.target.value);
if (p) setPreset(p);
}}>
{ALL_PRESETS.map((p) => (
<option key={p.name} value={p.name}>{p.label}</option>
))}
</select>
<button onClick={() => setMode(mode === "dark" ? "light" : "dark")}>
Toggle {mode === "dark" ? "Light" : "Dark"}
</button>
{children}
</DuckvizThemeProvider>
);
}Building a custom theme
Use buildThemeTokens() to generate a full theme from eight seed colors. The signature is (id, name, seeds) — id is a unique slug, name is human-readable, and seeds is a ThemeSeeds object:
import { buildThemeTokens, DuckvizThemeProvider } from "@duckviz/ui";
const customTheme = buildThemeTokens("my-brand", "My Brand", {
primary: "#2563eb", // Brand / accent (buttons, links, active indicators)
background: "#ffffff", // Main page background
surface: "#f8fafc", // Card / panel background
text: "#0f172a", // Default body text
border: "#e2e8f0", // Default border / divider
success: "#16a34a", // Success state
danger: "#dc2626", // Error / destructive state
warning: "#d97706", // Warning state
});
<DuckvizThemeProvider preset={customTheme} mode="light">
{children}
</DuckvizThemeProvider>buildThemeTokens generates a full preset with both light and dark token tables — each a complete set of CSS custom properties (--app-background-*, --app-surface-*, --app-text-*, --app-primary-*, --app-chart-*, etc.) derived from your seeds. The mode prop on the provider chooses which table is applied.
Light/dark mode
The mode prop controls the color scheme:
| Value | Behavior |
|---|---|
"light" | Light backgrounds, dark text |
"dark" | Dark backgrounds, light text |
"auto" | Follows the user's system preference (prefers-color-scheme) |
Reading the current mode
Use the useColorScheme() hook to read the resolved mode:
import { useColorScheme } from "@duckviz/ui";
function MyComponent() {
const { mode, resolvedMode } = useColorScheme();
// mode: "light" | "dark" | "auto"
// resolvedMode: "light" | "dark" (resolved value when mode is "auto")
return <p>Current theme: {resolvedMode}</p>;
}CSS custom properties reference
The theme system sets --app-* CSS custom properties. You can use these in your own stylesheets:
.my-card {
background: var(--app-surface-primary);
color: var(--app-text-primary);
border: 1px solid var(--app-border-primary);
border-radius: var(--app-radius-md);
}
.my-card:hover {
background: var(--app-surface-hover);
}Key token groups
| Group | Examples | Description |
|---|---|---|
--app-background-* | primary, secondary, tertiary | Page backgrounds |
--app-surface-* | primary, secondary, hover, active | Card and panel surfaces |
--app-text-* | primary, secondary, subtle, inverted | Text colors |
--app-border-* | primary, secondary, focus | Border colors |
--app-primary-* | default, hover, light, text | Brand primary variants |
--app-chart-* | 1 through 10 | Chart color palette |
--app-radius-* | sm, md, lg, xl | Border radii |
Theming DuckViz components
All DuckViz package components (Explorer, Dashboard, WidgetRenderer, Button, etc.) automatically read the CSS custom properties set by DuckvizThemeProvider. No additional configuration is needed — changing the theme affects all components.