Theming

Learn how the design system works and how to customize colors, dark mode, density, and direction.

Design System Overview

Haze Dashboard uses a Minimals-inspired design system built on OKLCH color tokens, solid card surfaces with layered shadow elevation, and a dark charcoal sidebar. The system provides a cohesive visual language across 80+ pages while remaining easy to customize.

All design decisions flow from CSS custom properties defined in app/assets/css/main.css. Changing a single token updates every component that references it — buttons, cards, charts, badges, and sidebar accents all respond automatically.

CSS Custom Properties

The design tokens are defined in two blocks: :root for light mode and .dark for dark mode. Here is the full light mode token set:

css
:root {
  --haze-primary: oklch(0.58 0.17 170);
  --haze-primary-lighter: oklch(0.92 0.04 170);
  --haze-primary-darker: oklch(0.42 0.14 170);
  --haze-bg: #F9FAFB;
  --haze-surface: #FFFFFF;
  --haze-surface-hover: #F4F6F8;
  --haze-text-primary: #1C252E;
  --haze-text-secondary: #637381;
  --haze-text-disabled: #919EAB;
  --haze-divider: rgba(145, 158, 171, 0.2);
  --haze-sidebar-bg: #1C252E;
  --haze-sidebar-text: #919EAB;
  --haze-sidebar-text-active: #FFFFFF;
  --haze-sidebar-active-bg: rgba(0, 167, 111, 0.08);
  --haze-header-bg: rgba(255, 255, 255, 0.8);
}

Dark Mode Tokens

When dark mode is active, the .dark class on the <html> element overrides every token:

css
.dark {
  --haze-primary: oklch(0.65 0.17 170);
  --haze-primary-lighter: oklch(0.25 0.06 170);
  --haze-primary-darker: oklch(0.50 0.14 170);
  --haze-bg: #141A21;
  --haze-surface: #1C252E;
  --haze-surface-hover: #252F3A;
  --haze-text-primary: #FFFFFF;
  --haze-text-secondary: #919EAB;
  --haze-text-disabled: #637381;
  --haze-divider: rgba(145, 158, 171, 0.16);
  --haze-sidebar-bg: #1C252E;
  --haze-sidebar-text: #919EAB;
  --haze-sidebar-text-active: #FFFFFF;
  --haze-sidebar-active-bg: rgba(0, 167, 111, 0.16);
  --haze-header-bg: rgba(20, 26, 33, 0.8);
}

Semantic Token Reference

TokenPurpose
--haze-primaryBrand color for buttons, links, active states, chart accents
--haze-primary-lighterTinted backgrounds for badges, tips, and highlights
--haze-primary-darkerHover states, gradient endpoints
--haze-bgPage background color
--haze-surfaceCard and panel background
--haze-surface-hoverHover state for surface elements
--haze-text-primaryMain body text
--haze-text-secondaryDescriptions, labels, muted text
--haze-dividerBorder color for separators and table rows
--haze-sidebar-bgSidebar background (dark charcoal in both modes)
--haze-header-bgHeader background with backdrop blur transparency

Shadow Elevation System

Cards and surfaces use a layered shadow system defined as Tailwind theme tokens. Shadows transition smoothly on hover to create a sense of depth:

TokenUsage
--shadow-cardDefault card elevation (subtle dual-layer shadow)
--shadow-card-hoverElevated hover state (deeper shadow)
--shadow-dropdownDropdown menus and popovers
--shadow-dialogModal dialogs (dramatic offset shadow)

Changing the Primary Color

The primary color is controlled by two systems working together:

1. Nuxt UI colors — Set in app/app.config.ts. This controls all Nuxt UI components (buttons, inputs, badges):

typescript
// app/app.config.ts
export default defineAppConfig({
  ui: {
    colors: {
      primary: 'teal',    // Change to 'blue', 'purple', etc.
      secondary: 'slate',
      success: 'emerald',
      warning: 'amber',
      error: 'red',
      info: 'sky',
    },
  },
})

2. Custom CSS tokens — Set in app/assets/css/main.css. This controls custom components (sidebar accents, gradient buttons, chart colors). To switch from teal to blue, change the hue from 170 to 250:

css
/* Change hue from 170 (teal) to 250 (blue) */
:root {
  --haze-primary: oklch(0.58 0.17 250);
  --haze-primary-lighter: oklch(0.92 0.04 250);
  --haze-primary-darker: oklch(0.42 0.14 250);
}

.dark {
  --haze-primary: oklch(0.65 0.17 250);
  --haze-primary-lighter: oklch(0.25 0.06 250);
  --haze-primary-darker: oklch(0.50 0.14 250);
}

Runtime Color Switching

The useThemeSettings composable lets users switch accent colors at runtime without a page reload. It updates the Nuxt UI appConfig color palette and persists the choice to localStorage:

typescript

Accent Color Presets

The theme customizer offers 6 accent color presets. Each preset maps to a Tailwind/Nuxt UI color name:

PresetColorHex
Teal (default)teal #14b8a6
Blueblue #3b82f6
Purplepurple #8b5cf6
Orangeorange #f97316
Roserose #f43f5e
Emeraldemerald #10b981

To add a new preset, add an entry to the accentPresets object in app/composables/useThemeSettings.ts. The color name must match a Tailwind/Nuxt UI color palette name.

Dark Mode

Dark mode is managed by Nuxt UI's built-in color mode module (powered by @nuxtjs/color-mode). It supports three states: light, dark, and system (follows OS preference). The preference persists to localStorage.

vue


The toggle is available in the header and the Appearance settings page. When dark mode is active, the .dark class is added to <html>, and all --haze-* tokens switch to their dark values.

Density

Density controls global spacing via a CSS custom property --haze-density-scale. Three levels are available:

DensityScaleUse Case
Compact0.85Dense data views, fitting more content on screen
Default1.0Standard spacing for most use cases
Spacious1.15Relaxed layout with more breathing room

RTL Support

The entire dashboard supports right-to-left text direction for Arabic, Hebrew, Persian, and other RTL languages. The toggle is in the Appearance settings. When enabled, the composable sets dir="rtl" on the <html> element.

Tailwind's logical properties handle most directional flipping automatically: ms-* / me-* for margins, ps-* / pe-* for padding, text-start / text-end for text alignment. For edge cases (icon rotation, absolute positioning), use Tailwind's ltr: and rtl: variants.

Nuxt UI Component Overrides

Nuxt UI components (buttons, inputs, modals, tables) are customized globally in app/app.config.ts via the slot system. This is where cards get the .card class, modals get the glass overlay, and inputs get rounded corners:

typescript
// app/app.config.ts
export default defineAppConfig({
  ui: {
    colors: {
      primary: 'teal',
      secondary: 'slate',
      success: 'emerald',
      warning: 'amber',
      error: 'red',
      info: 'sky',
    },
    card: {
      slots: {
        root: 'card card-hover',          // Custom shadow elevation
        header: 'border-b border-divider',
      },
    },
    button: {
      defaultVariants: { color: 'primary' },
    },
    input: {
      slots: {
        root: 'rounded-[var(--radius-input)]',
      },
    },
    modal: {
      slots: {
        content: 'glass-overlay rounded-[var(--radius-card-lg)]',
        overlay: 'bg-black/50',
      },
    },
  },
})

Tip

All theme settings (accent color, density, RTL) persist to localStorage via @vueuse/core's useLocalStorage. They survive page refreshes and browser restarts automatically.

Next Steps

See the Charts guide to learn about data visualization, or browse the Components reference.