DuckVizBeta
Guides

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

PresetPrimaryDescription
terracottaWarm orange-brownDefault DuckViz brand
oceanBlueCool blues
forestGreenDeep greens
slateGrayNeutral slate
lavenderPurpleSoft lavender
emberOrange-redWarm embers
midnightDark blueDeep midnight
copperCopperWarm copper
rosePinkSoft rose
sageOlive greenEarthy 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:

ValueBehavior
"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

GroupExamplesDescription
--app-background-*primary, secondary, tertiaryPage backgrounds
--app-surface-*primary, secondary, hover, activeCard and panel surfaces
--app-text-*primary, secondary, subtle, invertedText colors
--app-border-*primary, secondary, focusBorder colors
--app-primary-*default, hover, light, textBrand primary variants
--app-chart-*1 through 10Chart color palette
--app-radius-*sm, md, lg, xlBorder 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.