Skip to content

Custom Themes

@theme-kit/core

Theme Kit ships with neutral, preset, brand and accessibility themes — but the real power is defining your own. Every theme is a plain object of semantic tokens plus metadata.

1Define a theme

The smallest building block. A name, an optional family + mode, and nested tokens that become CSS variables.
ts
1import { defineTheme } from "@theme-kit/core";
2
3export const oceanLight = defineTheme({
4  name: "ocean-light",
5  meta: { family: "ocean", mode: "light", label: "Ocean Light" },
6  tokens: {
7    colors: {
8      background: "#f0f9ff",
9      foreground: "#0c1e2e",
10      primary: "#0284c7",
11      primaryForeground: "#ffffff",
12      secondary: "#e0f2fe",
13      secondaryForeground: "#0369a1",
14      muted: "#bae6fd",
15      mutedForeground: "#075985",
16      accent: "#0ea5e9",
17      accentForeground: "#ffffff",
18      destructive: "#dc2626",
19      destructiveForeground: "#ffffff",
20      success: "#16a34a",
21      successForeground: "#ffffff",
22      border: "#7dd3fc",
23      ring: "#0284c7",
24    },
25    radius: { lg: "0.5rem" },
26  },
27});
Mode + family |Give light and dark variants the same family so switching mode stays within your palette. See Core Concepts for how families work.
ts
1export const oceanDark = defineTheme({
2  name: "ocean-dark",
3  meta: { family: "ocean", mode: "dark", label: "Ocean Dark" },
4  tokens: {
5    colors: {
6      background: "#0c1e2e",
7      foreground: "#f0f9ff",
8      primary: "#38bdf8",
9      primaryForeground: "#0c1e2e",
10      secondary: "#0369a1",
11      secondaryForeground: "#e0f2fe",
12      muted: "#075985",
13      mutedForeground: "#bae6fd",
14      accent: "#0ea5e9",
15      accentForeground: "#0c1e2e",
16      destructive: "#f87171",
17      destructiveForeground: "#0c1e2e",
18      success: "#4ade80",
19      successForeground: "#0c1e2e",
20      border: "#0369a1",
21      ring: "#38bdf8",
22    },
23    radius: { lg: "0.5rem" },
24  },
25});

2Extend & compose

Inherit from a base theme and override only the tokens that change.
ts
1import { defineTheme, extendTheme } from "@theme-kit/core";
2import { oceanLight } from "./themes";
3
4const base = defineTheme({
5  name: "base",
6  meta: { family: "acme", mode: "light", label: "Acme Base" },
7  tokens: {
8    colors: {
9      background: "#fafafa",
10      foreground: "#18181b",
11      primary: "#6366f1",
12      primaryForeground: "#ffffff",
13      secondary: "#f4f4f5",
14      secondaryForeground: "#18181b",
15      muted: "#f4f4f5",
16      mutedForeground: "#71717a",
17      accent: "#f4f4f5",
18      accentForeground: "#18181b",
19      destructive: "#dc2626",
20      destructiveForeground: "#ffffff",
21      success: "#16a34a",
22      successForeground: "#ffffff",
23      border: "#e4e4e7",
24      ring: "#6366f1",
25    },
26    radius: { lg: "10px" },
27  },
28});
29
30export const acmeLight = extendTheme("acme-light", base, {
31  meta: { family: "acme", mode: "light", label: "Acme Light" },
32  colors: {
33    background: "#fafafa",
34    primary: "#4f46e5",
35  },
36});
37
38export const acmeDark = extendTheme("acme-dark", base, {
39  meta: { family: "acme", mode: "dark", label: "Acme Dark" },
40  colors: {
41    background: "#09090b",
42    foreground: "#fafafa",
43    primary: "#818cf8",
44    ring: "#818cf8",
45  },
46});

3Register with the runtime

Pass your themes to the runtime or provider in any framework. The same list works everywhere.
tsx
1import { createThemeRuntime } from "@theme-kit/core";
2import { ThemeProvider } from "@theme-kit/react";
3import { oceanLight, oceanDark } from "./themes";
4
5const themes = [oceanLight, oceanDark];
6
7createThemeRuntime({
8  themes,
9  defaultTheme: "ocean-light",
10  initialMode: "system",
11});
12
13export function App() {
14  return (
15    <ThemeProvider themes={themes} defaultTheme="ocean-light">
16      <YourApp />
17    </ThemeProvider>
18  );
19}
ts
1import { ThemeKit } from "@theme-kit/core/vanilla";
2import { oceanLight, oceanDark } from "./themes";
3
4const kit = new ThemeKit({ themes: [oceanLight, oceanDark] });
5kit.setFamily("ocean");
6kit.setMode("light");
7kit.update({ colors: { primary: "#06b6d4" } });
8kit.destroy();

4Generate from a seed

No token math needed — derive an entire light/dark pair from one color, then validate it.
ts
1import { generateTheme } from "@theme-kit/core";
2
3const { light, dark } = generateTheme({
4  seed: "#6366f1",
5  family: "indigo",
6});
7
8import { validateTheme } from "@theme-kit/core";
9const result = validateTheme(dark, { themes: [light, dark] });
10

5Presets & brand themes

Compose on top of the built-in palettes instead of starting from scratch.

Default presets

Nine curated families — oat, berry, mint, citrus, cocoa, plum, iris, sky, graphite — each with light + dark. Grab them via getPresetThemes() or restyle with getPresetThemes(overrides).

ts
1import { getPresetThemes } from "@theme-kit/core";
2
3const themes = getPresetThemes();
4
5const themes = getPresetThemes({
6  plum: {
7    light: { tokens: { colors: { primary: "#6d28d9" } } },
8  },
9});

Brand presets

Real-world brand palettes — Apple, GitHub, Vercel, Slack, Discord — via getBrandPresets(). Each is a ready-to-register theme pair.

ts
1import { getBrandPresets } from "@theme-kit/core";
2
3const themes = getBrandPresets();
4
5const brand = themes.filter((t) => t.meta?.family === "github");
6createThemeRuntime({ themes: brand, defaultTheme: "github-light" });

Accessibility profiles

High-contrast and large-text variants ship via getAccessibilityProfiles() and are tagged "accessibility" for filtering.

6Use your custom theme

The theme you defined above is a plain object — the same two objects register in every framework. Pick yours to see the exact integration with the custom ocean family.
Pick your framework
App.tsx
tsx
1import { ThemeProvider, useTheme } from "@theme-kit/react";
2import { oceanLight, oceanDark } from "./themes";
3
4const themes = [oceanLight, oceanDark];
5
6export function App() {
7  return (
8    <ThemeProvider themes={themes} defaultTheme="ocean-light">
9      <ThemeSwitcher />
10    </ThemeProvider>
11  );
12}
13
14function ThemeSwitcher() {
15  const { theme, setMode, setFamily } = useTheme();
16  return (
17    <div>
18      <span>{theme.name}</span>
19      <button onClick={() => setFamily("ocean")}>ocean</button>
20      <button onClick={() => setMode("light")}>Light</button>
21      <button onClick={() => setMode("dark")}>Dark</button>
22    </div>
23  );
24}
The oceanLight and oceanDark themes come from section 1. Register both, set ocean-light as the default, and mode toggling stays inside your family. See Framework Guides for a deeper walkthrough of each integration.

7What's next

Now that you have a custom theme, make it a first-class part of your product.
Custom Themes — Theme Kit