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:
: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:
.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
| Token | Purpose |
|---|---|
| --haze-primary | Brand color for buttons, links, active states, chart accents |
| --haze-primary-lighter | Tinted backgrounds for badges, tips, and highlights |
| --haze-primary-darker | Hover states, gradient endpoints |
| --haze-bg | Page background color |
| --haze-surface | Card and panel background |
| --haze-surface-hover | Hover state for surface elements |
| --haze-text-primary | Main body text |
| --haze-text-secondary | Descriptions, labels, muted text |
| --haze-divider | Border color for separators and table rows |
| --haze-sidebar-bg | Sidebar background (dark charcoal in both modes) |
| --haze-header-bg | Header 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:
| Token | Usage |
|---|---|
| --shadow-card | Default card elevation (subtle dual-layer shadow) |
| --shadow-card-hover | Elevated hover state (deeper shadow) |
| --shadow-dropdown | Dropdown menus and popovers |
| --shadow-dialog | Modal 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):
// 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:
/* 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:
Accent Color Presets
The theme customizer offers 6 accent color presets. Each preset maps to a Tailwind/Nuxt UI color name:
| Preset | Color | Hex |
|---|---|---|
| Teal (default) | teal | #14b8a6 |
| Blue | blue | #3b82f6 |
| Purple | purple | #8b5cf6 |
| Orange | orange | #f97316 |
| Rose | rose | #f43f5e |
| Emerald | emerald | #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.
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:
| Density | Scale | Use Case |
|---|---|---|
| Compact | 0.85 | Dense data views, fitting more content on screen |
| Default | 1.0 | Standard spacing for most use cases |
| Spacious | 1.15 | Relaxed 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:
// 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.