# How to Customize Kaneo Themes and UI: Complete Tailwind and Zustand Guide

> Easily customize Kaneo themes and UI. Learn to modify the Zustand store and extend Tailwind CSS v4 tokens for a personalized look. Get started now!

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: how-to-guide
- Published: 2026-08-05

---

**Customize Kaneo themes by modifying the Zustand-based **user-preferences store** in [`apps/web/src/store/user-preferences.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/store/user-preferences.ts) and extending Tailwind CSS v4 tokens in `tailwind.config.cjs`.**

Kaneo provides a flexible theming system built on **Tailwind CSS v4** with dark-mode class strategy and a **Zustand** state store for persistence. Whether you want to switch between light and dark modes, add custom color palettes, or create entirely new theme variants, the customization hooks are centralized in predictable locations.

## Understanding Kaneo's Theme Architecture

Kaneo separates theme logic into two layers: the **state layer** (Zustand store) and the **presentation layer** (Tailwind classes applied via ThemeProvider). The store persists to `localStorage`, so user preferences survive reloads.

### Core Components That Handle Theming

| Component | Purpose | Source Path |
|-----------|---------|-------------|
| **ThemeProvider** | Injects theme class onto `<html>` element for Tailwind dark mode | [`apps/web/src/components/providers/theme-provider/index.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/providers/theme-provider/index.tsx) |
| **ThemeToggleDropdown** | Sidebar dropdown for Light/Dark/System selection | [`apps/web/src/components/theme-toggle-dropdown.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/theme-toggle-dropdown.tsx) |
| **ThemeToggle** | Simple switch for public project pages | [`apps/web/src/components/public-project/theme-toggle.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/public-project/theme-toggle.tsx) |
| **Preferences Settings** | Theme controls in user settings dashboard | [`apps/web/src/routes/_layout/_authenticated/dashboard/settings/account/preferences.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/routes/_layout/_authenticated/dashboard/settings/account/preferences.tsx) |

All components read from and write to the same Zustand hook:

```tsx
// Core pattern used throughout Kaneo
import { useUserPreferencesStore } from "@/store/user-preferences";

const { theme, setTheme } = useUserPreferencesStore();

```

## How to Switch Themes Programmatically

The simplest customization is programmatic theme switching from any component. This pattern mirrors the implementation in [`apps/web/src/components/theme-toggle-dropdown.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/theme-toggle-dropdown.tsx).

```tsx
import { useUserPreferencesStore } from "@/store/user-preferences";

export function MyThemeSwitcher() {
  const { theme, setTheme } = useUserPreferencesStore();

  const toggle = () => {
    setTheme(theme === "dark" ? "light" : "dark");
  };

  return (
    <button onClick={toggle} className="btn">
      Switch to {theme === "dark" ? "Light" : "Dark"} mode
    </button>
  );
}

```

The `setTheme` function accepts three built-in values: `"light"`, `"dark"`, or `"system"`. When `"system"` is selected, Kaneo defers to the OS preference without adding any class to the document root.

## How to Customize Tailwind Colors in Kaneo

Kaneo uses **Tailwind CSS v4** with class-based dark mode. To override or extend the color palette, modify `tailwind.config.cjs` at the repository root.

```js
// tailwind.config.cjs
module.exports = {
  darkMode: "class",
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#f5faff",
          100: "#e0f4ff",
          200: "#b3e0ff",
          500: "#0077cc", // primary brand colour
        },
      },
    },
  },
  // ...remaining config
};

```

These tokens become available immediately to all UI components in `apps/web/src/components/ui/`. For component-specific overrides, copy any file from `apps/web/src/components/ui/` and modify its class composition.

## How to Add a Custom Theme Mode to Kaneo

For advanced customization, you can extend the theme system beyond the three built-in modes. This requires three coordinated changes:

### Step 1: Extend the Store Type Definition

```tsx
// apps/web/src/store/user-preferences.ts
import { create } from "zustand";

type Theme = "light" | "dark" | "system" | "high-contrast";

export const useUserPreferencesStore = create<{
  theme: Theme;
  setTheme: (t: Theme) => void;
}>((set) => ({
  theme: "system",
  setTheme: (t) => set({ theme: t }),
}));

```

### Step 2: Update the ThemeProvider

```tsx
// apps/web/src/components/providers/theme-provider/index.tsx
import { useUserPreferencesStore } from "@/store/user-preferences";

export function ThemeProvider({ children }: { children: ReactNode }) {
  const { theme } = useUserPreferencesStore();

  useEffect(() => {
    const root = document.documentElement;
    // Remove all known theme classes before applying new one
    root.classList.remove("light", "dark", "high-contrast");
    
    if (theme === "system") {
      return; // OS handles it; no class needed
    }
    root.classList.add(theme);
  }, [theme]);

  return <>{children}</>;
}

```

### Step 3: Add the Option to UI Controls

```tsx
// apps/web/src/components/theme-toggle-dropdown.tsx
// Inside your Select component:
<SelectItem value="high-contrast">High Contrast</SelectItem>

```

Then define corresponding CSS variables or utility classes for `.high-contrast` in your global styles or Tailwind configuration.

## Key Files for Kaneo Theme Customization

| File | Role |
|------|------|
| [`apps/web/src/store/user-preferences.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/store/user-preferences.ts) | Zustand store with `theme` state and `setTheme` action |
| [`apps/web/src/components/providers/theme-provider/index.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/providers/theme-provider/index.tsx) | Applies theme class to `document.documentElement` |
| [`apps/web/src/components/theme-toggle-dropdown.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/theme-toggle-dropdown.tsx) | Primary UI for theme selection in sidebar |
| [`apps/web/src/components/public-project/theme-toggle.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/public-project/theme-toggle.tsx) | Theme toggle for public-facing project pages |
| [`apps/web/src/routes/_layout/_authenticated/dashboard/settings/account/preferences.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/routes/_layout/_authenticated/dashboard/settings/account/preferences.tsx) | Settings page theme controls |
| `tailwind.config.cjs` | Tailwind CSS configuration with dark mode and color tokens |

## Summary

- **Kaneo themes** are controlled through a Zustand store in [`apps/web/src/store/user-preferences.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/store/user-preferences.ts) with `theme` and `setTheme` exports
- **Mode options** are `"light"`, `"dark"`, or `"system"` by default; extendable by modifying the TypeScript union type
- **ThemeProvider** in [`apps/web/src/components/providers/theme-provider/index.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/providers/theme-provider/index.tsx) synchronizes the store value to the DOM via CSS classes
- **Tailwind customization** happens in `tailwind.config.cjs` using the `darkMode: "class"` strategy
- **Persistence** is built into the store via Zustand's storage middleware—no manual `localStorage` code required

## Frequently Asked Questions

### How do I change the default theme in Kaneo?

Modify the initial state in [`apps/web/src/store/user-preferences.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/store/user-preferences.ts). Change `theme: "system"` to `theme: "light"` or `theme: "dark"` in the Zustand store's `create()` call. The new default applies to first-time visitors without existing preferences in `localStorage`.

### Can I use CSS variables instead of Tailwind classes for theming?

Yes. Define your variables in the `.dark` class or custom theme classes, then reference them in `tailwind.config.cjs` using the `theme.extend.colors` with `var()` syntax. Kaneo's `ThemeProvider` architecture supports any CSS-in-JS or CSS variable approach as long as the theme class is applied to `document.documentElement`.

### Where is the theme preference stored?

Kaneo persists themes to `localStorage` through Zustand's persistence middleware. The storage key and serialization logic are co-located with the store definition in [`apps/web/src/store/user-preferences.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/store/user-preferences.ts). You can inspect or manually clear this in browser DevTools under Application → Local Storage.

### How do I add theme support to a new component?

Import `useUserPreferencesStore` from `@/store/user-preferences` and destructure `theme`. Use the value to conditionally apply classes, or rely on Tailwind's `dark:` prefix since `ThemeProvider` applies the `.dark` class to the HTML element. For color values not covered by Tailwind, compute styles from the `theme` state directly.