# Theming

> Customize colors, dark mode, and design tokens in kuzan

Package: Kuzan
Canonical: https://kuratchi.dev/docs/kuzan/theming
Markdown: https://kuratchi.dev/docs/kuzan/theming.md

## Theme model

`kuzan` is theme-driven via CSS custom properties. The runtime color scheme is controlled by:

- A class on `<html>` (`class="dark"` or omitted for light)
- An optional `data-theme="system"` attribute (follows the OS preference)
- An optional `<ThemeInit />` script that reads `localStorage['kui-theme']` and applies the saved choice on first paint

There is no framework-level config — the choice lives in your `src/routes/layout.koze`.

## Color schemes

### Dark mode

```html
<html lang="en" class="dark">
```

Dark background with light text. Optimized for low-light environments.

### Light mode

```html
<html lang="en">
```

White background with dark text. Default when no class is set.

### System preference

Follows the user's OS color scheme via `prefers-color-scheme`:

```html
<html lang="en" data-theme="system">
```

## Runtime theme switching

Use the `ThemeToggle` component to let users switch themes:

```html
<script>
  import ThemeToggle from '@kuratchi/kuzan/theme-toggle.koze';
</script>

<ThemeToggle />
```

The toggle:
- Adds/removes the `.dark` class on `<html>`
- Saves the preference to `localStorage` under `"kui-theme"`
- Syncs all toggle instances on the page

Pair it with `<ThemeInit />` in `<head>` so the saved theme applies before the page paints — see [Preventing FOUC](#preventing-fouc).

## Corner radius variants

`kuzan` reads radius from a `data-radius` attribute on `<html>`:

```html
<html lang="en" class="dark" data-radius="default">
```

Supported values:

- `data-radius="default"` — standard rounded corners (0.375rem - 0.75rem)
- `data-radius="none"` — square corners (0px radius)
- `data-radius="full"` — pill-shaped / fully rounded corners (9999px)

Omit the attribute for the default behavior.

## CSS variables

All theme values are exposed as CSS custom properties. Override them in your own stylesheet:

### Color tokens

```css
:root {
  /* Backgrounds */
  --kui-bg: #ffffff;
  --kui-bg-muted: #f9fafb;
  --kui-bg-elevated: #ffffff;

  /* Foreground */
  --kui-fg: #09090b;
  --kui-fg-muted: #71717a;

  /* Borders & focus */
  --kui-border: #e4e4e7;
  --kui-ring: #18181b;

  /* Primary brand color */
  --kui-primary: #584cd9;
  --kui-primary-fg: #ffffff;
  --kui-primary-weak: color-mix(in srgb, #584cd9 12%, transparent);
  --kui-primary-strong: #4338ca;

  /* Semantic colors */
  --kui-destructive: #ef4444;
  --kui-destructive-fg: #ffffff;
  --kui-success: #22c55e;
  --kui-success-fg: #ffffff;
  --kui-warning: #f59e0b;
  --kui-warning-fg: #ffffff;
}

.dark {
  --kui-bg: #09090b;
  --kui-bg-muted: #18181b;
  --kui-fg: #fafafa;
  --kui-fg-muted: #a1a1aa;
  --kui-border: #27272a;
  --kui-primary: #818cf8;
  /* ... */
}
```

### Spacing tokens

```css
:root {
  --kui-spacing-xs: 0.25rem;
  --kui-spacing-sm: 0.5rem;
  --kui-spacing-md: 1rem;
  --kui-spacing-lg: 1.5rem;
  --kui-spacing-xl: 2rem;
}
```

### Radius tokens

```css
:root {
  --kui-radius-sm: 0.375rem;
  --kui-radius-md: 0.5rem;
  --kui-radius-lg: 0.75rem;
}
```

### Shadow tokens

```css
:root {
  --kui-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
  --kui-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
  --kui-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06);
  --kui-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05);
}
```

### Timing tokens

```css
:root {
  --kui-duration-fast: 100ms;
  --kui-duration-base: 150ms;
}
```

## Customizing colors

Override CSS variables in `src/app.css` after the theme import:

```css
/* src/app.css */
@import '@kuratchi/kuzan/styles/theme.css';

:root {
  /* Change primary brand color */
  --kui-primary: #10b981;
  --kui-primary-fg: #ffffff;
  --kui-primary-strong: #059669;

  /* Adjust background */
  --kui-bg: #fafafa;
  --kui-bg-muted: #f4f4f5;
}

.dark {
  --kui-primary: #34d399;
  --kui-primary-fg: #09090b;
}
```

The framework auto-injects `src/app.css` into the app shell — no `<link>` tag needed.

## Using with Tailwind CSS

`kuzan` composes with Tailwind via the standard PostCSS / Tailwind v4 pipeline. Install:

```bash
npm install -D @tailwindcss/vite
```

Add to your `vite.config.ts`:

```ts
import tailwindcss from '@tailwindcss/vite';
import { koze } from '@kuratchi/koze/vite';

export default defineConfig({
  plugins: [
    koze(),
    tailwindcss(),
    cloudflare({ viteEnvironment: { name: 'ssr' } }),
  ],
});
```

Then import both from `src/app.css`:

```css
@import 'tailwindcss';
@import '@kuratchi/kuzan/styles/theme.css';
@source './routes/**/*.koze';
```

You can use Tailwind utility classes alongside `kuzan` components:

```html
<Card title="Dashboard" class="max-w-2xl mx-auto">
  <p class="text-sm text-gray-600">Custom Tailwind styling</p>
</Card>
```

## Font customization

The default font stack:

```css
:root {
  --kui-font-sans: 'Inter', 'Segoe UI', system-ui, -apple-system,
                   BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
```

Override it in `src/app.css`:

```css
:root {
  --kui-font-sans: 'Your Font', system-ui, sans-serif;
}

body {
  font-family: var(--kui-font-sans);
}
```

## Preventing FOUC

To prevent a flash of unstyled content when the page loads, use the `<ThemeInit />` component in your layout's `<head>`:

```html
<script>
  import ThemeInit from '@kuratchi/kuzan/theme-init.koze';
</script>

<!doctype html>
<html lang="en" class="dark">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>My App</title>
    <ThemeInit />
  </head>
  <body>
    <slot></slot>
  </body>
</html>
```

`<ThemeInit />` reads `localStorage['kui-theme']` and applies the saved class to `<html>` before the page paints.

## Next steps

- [Components](/docs/kuzan/components) — Browse the full component reference
- [Framework Styling](/docs/koze/styling) — Learn about CSS processing in Koze
