Theming & Colors
Learn how to use Vayu UI's semantic color system with the pairing pattern for consistent, accessible designs.
Overview
Welcome to the Vayu UI color system! This guide will teach you how to think about colors in a semantic, meaningful way. By the end, you'll understand why we never say "use blue" — we say "use brand".
The Golden Rule: The Pairing Pattern
Before we dive into individual colors, memorize this pattern:
bg-[name] + text-[name]-contentEvery background color has a paired text color that guarantees proper contrast. Always use them together.
// ✅ Do: Use paired colors
<div className="bg-surface text-surface-content">
This text will always be readable
</div>
// ❌ Don't: Mix arbitrary colors
<div className="bg-surface text-blue-500">
Contrast is not guaranteed
</div>This pattern ensures:
- WCAG AA compliance in both light and dark modes
- Consistent contrast across your entire app
- Automatic dark mode support without extra work
The Layer Cake Model
Think of your UI as a stack of layers, like a cake. Each layer has a purpose, and they stack from bottom to top:
┌─────────────────────────────────────┐ ← Elevated (Modals, Tooltips)
│ ┌───────────────────────────────┐ │
│ │ Surface (Cards) │ │ ← Surface (Containers)
│ │ ┌─────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ Content Here │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
│ │ ← Canvas (App Background)
└─────────────────────────────────────┘Why This Matters
Each layer is slightly different from the one below it, creating visual separation. In light mode, layers get lighter as they go up. In dark mode, they get darker. This creates natural depth without you having to think about it.
Base Layers
These four layers form the foundation of every screen in your app.
| Variable | Usage Context | Paired Text | Tailwind Classes |
|---|---|---|---|
| Canvas | App background, viewport, body | canvas-content | bg-canvas text-canvas-content |
| Surface | Cards, sections, inputs, tables, panels | surface-content | bg-surface text-surface-content |
| Sidebar | Navigation sidebar, drawer, rail nav | sidebar-content | bg-sidebar text-sidebar-content |
| Elevated | Modals, popovers, dropdowns, tooltips | elevated-content | bg-elevated text-elevated-content |
Canvas
The canvas is your app's foundation. It's the bottom-most layer.
Surface
Surfaces are containers that sit on top of the canvas. Cards, panels, and form elements use this layer.
Sidebar
Sidebars are for navigation. They're visually distinct from the main content area.
Elevated
Elevated surfaces appear above everything else. Think modals, tooltips, and dropdowns.
Semantic Colors
Semantic colors convey meaning. Use these when you need to communicate state or intent.
| Variable | Meaning | Paired Text | Use Cases |
|---|---|---|---|
| Brand | Primary identity | brand-content | CTAs, active states, links |
| Success | Positive, completed | success-content | Confirmations, checkmarks |
| Warning | Caution, pending | warning-content | Alerts, unsaved changes |
| Destructive | Error, dangerous | destructive-content | Delete buttons, errors |
| Info | Informational | info-content | Help text, notifications |
| Muted | De-emphasized | muted-content | Disabled states, placeholders |
Brand (Primary)
Your brand color is for primary actions and key interactive elements.
Success
For positive outcomes, confirmations, and completed states.
✓ Your changes have been saved successfully.
Warning
For cautionary messages and pending states.
⚠ You have unsaved changes.
Destructive
For errors, dangerous actions, and critical warnings.
✕ Something went wrong. Please try again.
Info
For informational messages and help content.
ℹ Tip: You can drag and drop files to upload them.
Muted
For de-emphasized content, disabled states, and placeholders.
This content is less important.
Structural Elements
These colors define borders and focus states.
| Variable | Usage | Tailwind Class |
|---|---|---|
| Border | Container borders, dividers | border-border |
| Field | Form field borders (inputs, selects) | border-field |
| Focus | Focus rings, keyboard navigation | ring-focus, focus:ring-focus |
// Card with border
<div className="bg-surface text-surface-content border border-border rounded-surface p-4">
Card content
</div>
// Input with field border
<input className="border border-field rounded-control px-3 py-2 focus:ring-2 focus:ring-focus" />
// Focus ring on button
<button className="focus:ring-2 focus:ring-focus rounded-control px-4 py-2">
Click Me
</button>Dark Mode
Dark mode is automatically handled when you use the pairing pattern. Simply add the .dark class to a parent element (typically <html> or <body>), and all colors will adapt.
How It Works
// In your root layout
<html className="dark">
<body className="bg-canvas text-canvas-content">{/* All children use dark mode colors */}</body>
</html>Toggle Pattern
'use client';
import { useState, useEffect } from 'react';
export function ThemeToggle() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
document.documentElement.classList.toggle('dark', isDark);
}, [isDark]);
return <button onClick={() => setIsDark(!isDark)}>{isDark ? '☀️ Light' : '🌙 Dark'}</button>;
}CSS Custom Variant
The dark mode is configured using Tailwind's custom variant:
@custom-variant dark (&:where(.dark, .dark *));This means dark: classes apply when the element (or any parent) has the .dark class.
Quick Reference
Complete Color Pairing Table
| Background | Text Color | Example |
|---|---|---|
bg-canvas | text-canvas-content | App shell |
bg-surface | text-surface-content | Cards, forms |
bg-sidebar | text-sidebar-content | Navigation |
bg-elevated | text-elevated-content | Modals, tooltips |
bg-brand | text-brand-content | Primary actions |
bg-success | text-success-content | Success states |
bg-warning | text-warning-content | Warning states |
bg-destructive | text-destructive-content | Error states |
bg-info | text-info-content | Info states |
bg-muted | text-muted-content | Disabled states |
Do's and Don'ts
| Do ✅ | Don't ❌ |
|---|---|
Use semantic names: bg-brand | Use literal names: bg-indigo-500 |
Pair colors: bg-surface text-surface-content | Mix arbitrarily: bg-surface text-blue-500 |
Use muted for secondary content | Use gray or random gray shades |
| Let the system handle dark mode | Manually specify dark mode colors |