How to Customize Kaneo Themes and UI: Complete Tailwind and Zustand Guide
Customize Kaneo themes by modifying the Zustand-based user-preferences store in 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 |
| ThemeToggleDropdown | Sidebar dropdown for Light/Dark/System selection | apps/web/src/components/theme-toggle-dropdown.tsx |
| ThemeToggle | Simple switch for public project pages | 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 |
All components read from and write to the same Zustand hook:
// 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.
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.
// 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
// 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
// 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
// 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 |
Zustand store with theme state and setTheme action |
apps/web/src/components/providers/theme-provider/index.tsx |
Applies theme class to document.documentElement |
apps/web/src/components/theme-toggle-dropdown.tsx |
Primary UI for theme selection in sidebar |
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 |
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.tswiththemeandsetThemeexports - 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.tsxsynchronizes the store value to the DOM via CSS classes - Tailwind customization happens in
tailwind.config.cjsusing thedarkMode: "class"strategy - Persistence is built into the store via Zustand's storage middleware—no manual
localStoragecode 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. 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →