Gaia UI uses CSS variables and a design token system for flexible, consistent theming. Every component supports both light and dark modes out of the box.
CSS Variables
All colors in Gaia UI are defined using CSS variables that automatically adapt to light and dark modes.
Color System
The color system uses OKLCH color space for perceptually uniform colors:
:root {
--background: oklch(1 0 0); /* Pure white */
--foreground: oklch(0.145 0 0); /* Near black */
--muted: oklch(0.97 0 0); /* Light gray */
--muted-foreground: oklch(0.556 0 0); /* Medium gray */
--primary: oklch(0.205 0 0); /* Dark gray */
--primary-foreground: oklch(0.985 0 0); /* Off-white */
}
.dark {
--background: oklch(0.145 0 0); /* Dark background */
--foreground: oklch(0.985 0 0); /* Light text */
--muted: oklch(0.269 0 0); /* Dark gray */
--muted-foreground: oklch(0.708 0 0); /* Light gray */
--primary: oklch(0.922 0 0); /* Light gray */
--primary-foreground: oklch(0.205 0 0); /* Dark text */
}
Available Color Tokens
All color tokens automatically switch between light and dark mode. Use Tailwind classes like bg-background and text-foreground to leverage these tokens.
Backgrounds and Surfaces
background — Primary background color
foreground — Primary text color
card — Card background
card-foreground — Card text color
popover — Popover background
popover-foreground — Popover text color
Interactive Elements
primary — Primary action color
primary-foreground — Text on primary color
secondary — Secondary elements
secondary-foreground — Text on secondary
muted — Muted backgrounds
muted-foreground — Muted text
accent — Accent highlights
accent-foreground — Text on accent
Semantic Colors
destructive — Error and destructive actions
destructive-foreground — Text on destructive
Borders and Inputs
border — Border color
input — Input border color
ring — Focus ring color
Using Color Tokens
// ✅ Good - uses theme-aware colors
<div className="bg-background text-foreground">
<p className="text-muted-foreground">Secondary text</p>
<button className="bg-primary text-primary-foreground">
Primary Action
</button>
</div>
// ❌ Bad - hardcoded colors won't adapt to theme
<div className="bg-white text-black">
<p className="text-gray-500">Secondary text</p>
</div>
Dark Mode
Dark mode is automatically handled by the .dark class on a parent element (typically <html> or <body>).
Implementation
Gaia UI uses a custom variant for dark mode:
@custom-variant dark (&:is(.dark *));
This allows you to use the dark: prefix in Tailwind classes:
<div className="bg-zinc-100 dark:bg-zinc-800">
<span className="text-zinc-900 dark:text-white">
Text that adapts to theme
</span>
</div>
Dark Mode Patterns
From the Composer component:
<div className={cn(
// Light mode: light gray background
"bg-zinc-100",
// Dark mode: darker gray background
"dark:bg-zinc-800"
)}>
<textarea className={cn(
"text-zinc-900 dark:text-white",
"placeholder:text-zinc-400 dark:placeholder:text-zinc-500"
)} />
</div>
Always test your components in both light and dark modes. What looks good in light mode might have contrast issues in dark mode.
Border Radius
Border radius values use a consistent scale:
:root {
--radius: 0.625rem; /* Base radius: 10px */
--radius-sm: calc(var(--radius) - 4px); /* 6px */
--radius-md: calc(var(--radius) - 2px); /* 8px */
--radius-lg: var(--radius); /* 10px */
--radius-xl: calc(var(--radius) + 4px); /* 14px */
}
Access these via Tailwind classes:
<div className="rounded-sm"> {/* 6px */}
<div className="rounded-md"> {/* 8px */}
<div className="rounded-lg"> {/* 10px */}
<div className="rounded-xl"> {/* 14px */}
Customizing Colors
You can customize the color palette by overriding CSS variables in your globals.css:
:root {
/* Override primary color with blue */
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.985 0 0);
}
.dark {
/* Override primary color in dark mode */
--primary: oklch(0.696 0.17 162.48);
--primary-foreground: oklch(0.145 0 0);
}
Example: Custom Brand Colors
/* In your globals.css */
:root {
/* Custom brand colors */
--primary: oklch(0.55 0.22 250); /* Brand blue */
--accent: oklch(0.75 0.15 160); /* Brand green */
--destructive: oklch(0.577 0.245 27); /* Error red */
}
Use OKLCH Color Picker to find perceptually uniform colors. OKLCH provides better color consistency across light and dark modes.
Component-Specific Theming
Some components use additional CSS variables for fine-grained control.
Example: HoloCard
The HoloCard component uses custom properties for sizing:
.holo-card {
--holo-width: 320px;
--holo-height: 446px;
width: var(--holo-width);
height: var(--holo-height);
}
And adjusts effects for light mode:
.light .holo-card {
box-shadow:
-3px -3px 3px 0 rgba(38, 230, 247, 0.2),
3px 3px 3px 0 rgba(247, 89, 228, 0.2),
0 0 6px 2px rgba(255, 231, 89, 0.2);
}
Opacity Patterns
Use opacity to create subtle variations without adding new color tokens:
// Subtle backgrounds
<div className="bg-muted/50"> {/* 50% opacity */}
<div className="bg-primary/10"> {/* 10% opacity */}
// Transparent borders
<div className="border border-white/10"> {/* In dark mode */}
Common Opacity Values
/10 (10%) — Very subtle tints
/20 (20%) — Subtle backgrounds
/50 (50%) — Medium transparency
/70 (70%) — Slightly transparent
/90 (90%) — Nearly opaque
Selection Styles
Customize text selection colors:
::selection {
background-color: black;
color: white;
}
html.dark ::selection {
background-color: white;
color: black;
}
Chart Colors
For data visualization, Gaia UI provides chart color tokens:
:root {
--chart-1: oklch(0.646 0.222 41.116); /* Orange */
--chart-2: oklch(0.6 0.118 184.704); /* Cyan */
--chart-3: oklch(0.398 0.07 227.392); /* Blue */
--chart-4: oklch(0.828 0.189 84.429); /* Yellow */
--chart-5: oklch(0.769 0.188 70.08); /* Green */
}
Use with Tailwind:
<div className="bg-chart-1">Orange</div>
<div className="bg-chart-2">Cyan</div>
Responsive Theming
Combine theming with responsive classes:
<div className={cn(
"bg-white dark:bg-zinc-900",
"md:bg-zinc-50 md:dark:bg-zinc-800", // Different on larger screens
"p-4 md:p-6" // More padding on larger screens
)}>
Testing Your Theme
Checklist
Browser DevTools
Most browsers allow you to:
- Toggle dark mode in DevTools
- Check contrast ratios
- Simulate color blindness
- View CSS variable values
Chrome DevTools has a “Rendering” panel where you can emulate dark mode and test prefers-reduced-motion.
Best Practices
Do:
- Use CSS variables for all colors
- Test in both light and dark modes
- Use semantic color names (primary, destructive, muted)
- Leverage opacity for subtle variations
- Use OKLCH for better color consistency
Don’t:
- Hardcode color values
- Use only hex colors
- Skip dark mode testing
- Use colors that fail contrast checks
- Mix color systems (stay consistent with OKLCH)
Examples
Themed Card
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
export function ThemedCard() {
return (
<Card className="bg-card text-card-foreground">
<CardHeader>
<CardTitle>Themed Card</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
This card automatically adapts to your theme.
</p>
</CardContent>
</Card>
);
}
Custom Accent Color
// Override accent for specific component
<div style={{
'--accent': 'oklch(0.6 0.2 320)',
'--accent-foreground': 'oklch(0.98 0 0)'
}}>
<Button variant="ghost" className="text-accent hover:bg-accent hover:text-accent-foreground">
Custom Accent
</Button>
</div>