Skip to content

# Theming

Learn how the color system works and how to customize the look and feel of Zenith Dashboard.

## OKLCh Color Tokens

All colors are defined as CSS custom properties using the OKLCh color space. OKLCh provides perceptually uniform lightness, making it easier to create consistent color palettes. The tokens are defined in `src/app/globals.css`.

    :root {
      --primary: oklch(0.205 0 0);
      --primary-foreground: oklch(0.985 0.002 230);
      --background: oklch(0.985 0.002 230);
      --foreground: oklch(0.155 0.015 230);
      --card: oklch(1 0 0);
      --muted: oklch(0.96 0.005 230);
      --muted-foreground: oklch(0.556 0.015 230);
      --border: oklch(0.922 0.005 230);
      /* ... more tokens */
    }

The OKLCh format is `oklch(lightness chroma hue)` where lightness is 0–1, chroma is 0–0.4, and hue is 0–360 degrees. Tailwind CSS v4 maps these tokens through the `@theme inline` block at the top of globals.css.

## Changing the Primary Color

To change the primary color, update the `--primary` token in both the `:root` (light) and `.dark` blocks. For example, to switch from teal to blue:

    /* Light mode */
    :root {
      --primary: oklch(0.55 0.175 250);          /* hue 160 → 250 (blue) */
      --primary-foreground: oklch(0.985 0.002 230);
      --ring: oklch(0.55 0.175 250);
    }

    /* Dark mode */
    .dark {
      --primary: oklch(0.65 0.19 250);
      --primary-foreground: oklch(0.09 0.015 170);
      --ring: oklch(0.65 0.19 250);
    }

The change propagates everywhere `bg-primary`, `text-primary`, or `ring-primary` is used — buttons, links, active states, charts, and sidebar accents all update automatically.

## Semantic Color Tokens

The design system uses semantic names rather than raw color values. Each token has a foreground counterpart for text that sits on top of it:

Token | Purpose
--- | ---
--primary | Brand color, buttons, active states
--secondary | Secondary actions, subtle backgrounds
--muted | Muted backgrounds, code blocks
--accent | Hover states, highlights
--destructive | Error states, delete actions
--success | Success indicators, positive trends
--warning | Warning badges, caution states
--chart-1 to --chart-5 | Chart color palette (5 colors)
--sidebar-* | Sidebar-specific tokens (dark sidebar in light mode)

## Dark Mode

Dark mode is implemented by toggling a `.dark` class on the `<html>` element. The `ThemeProvider` component in `src/components/theme-provider.tsx` manages the state and persists the preference to localStorage. The Tailwind dark variant is configured in globals.css:

    @custom-variant dark (&:is(.dark *));

This allows you to use `dark:bg-slate-900` classes in your components, and the `.dark` block in globals.css overrides all semantic tokens for dark mode.

## Using the Theme Provider

The `useTheme` hook provides access to the current theme and a setter:

    "use client";

    import { useTheme } from "@dashboardpack/core/providers/theme-provider";
    import type { Metadata } from "next";

    export const metadata: Metadata = {
      title: "Theming",
      description:
        "Customise OKLCh color tokens, density, layout modes, dark mode, and RTL.",
    };

    export function MyComponent() {
      const { theme, setTheme } = useTheme();

      return (
        <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
          Current theme: {theme}
        </button>
      );
    }

Supported values are `"light"`, `"dark"`, and `"system"`. The system option follows the user's OS preference and reacts to changes in real time.

## Theme Customizer

Zenith Dashboard includes a live Theme Customizer accessible via the palette icon in the header. It opens as a slide-out drawer panel where you can preview every change in real time without refreshing the page. The Customizer controls the following settings:

- Color Presets — 6 built-in palettes: Default (Teal), Ocean, Sunset, Forest, Berry, and Slate. Each preset swaps the full set of OKLCh tokens at runtime, instantly recoloring every component, chart, and sidebar accent across the entire dashboard.
- Density — Three levels: Compact, Default, and Comfortable. Adjusts spacing, padding, and font sizes globally via CSS custom properties, letting you fit more data on screen or give content room to breathe.
- Layout — Choose between the default Sidebar layout (fixed left navigation) or a Horizontal Top-Nav layout where the main navigation runs across the top of the page.
- Container — Fluid (full-width, edge-to-edge content) or Boxed (max-width centered container with side margins for a more traditional feel).
- Direction — Toggle between LTR (left-to-right) and RTL (right-to-left) text direction for multilingual support.
- Reset — A single button that restores all settings to their defaults (Default Teal preset, Default density, Sidebar layout, Fluid container, LTR direction).

All preferences are persisted to `localStorage`, so they survive page refreshes and browser restarts. The next time the user visits the dashboard, their chosen color preset, density, layout, container, and direction settings are restored automatically.

## RTL Support

The entire dashboard supports right-to-left text direction for languages such as Arabic, Hebrew, Persian, and Urdu. The toggle is available in the Theme Customizer under the Direction setting. When RTL is enabled, the sidebar moves to the right, text aligns to the right, and all directional spacing flips accordingly.

The implementation relies on the following techniques:

- CSS Logical Properties — Instead of physical `ml-*`/ `mr-*` and `pl-*`/ `pr-*`, all spacing uses logical equivalents: `ms-*`/ `me-*`, `ps-*`/ `pe-*`, `text-start`/ `text-end`, `border-s`/ `border-e`, and `rounded-s`/ `rounded-e`. These automatically flip based on the document direction.
- Tailwind CSS v4 ltr:/rtl: Variants — For cases where logical properties alone are not sufficient (e.g., icon rotation, absolute positioning, transform origins), Tailwind's `ltr:` and `rtl:` variants apply direction-specific styles conditionally.
- dir attribute on HTML element — The Theme Customizer sets `dir="rtl"` on the `<html>` element, which browsers use natively to reverse text flow, scroll direction, and table column order.
- Inline Script Prevents Flash — A small inline script in the `<head>` reads the saved direction preference from localStorage and applies the `dir` attribute before the page renders. This prevents a flash of incorrect direction (FOID) on page load, similar to how the theme script prevents a flash of incorrect theme.

    <!-- Example: classes that work in both LTR and RTL -->
    <div className="ms-4 me-2 ps-3 pe-3 text-start border-s-2">
      Content flows correctly in both directions
    </div>

    <!-- Example: direction-specific overrides -->
    <ChevronRight className="size-4 ltr:rotate-0 rtl:rotate-180" />

## Next Steps

See Components for a full list of available UI primitives, or learn how to add new pages to the dashboard.