VayuUI
Design System

Theming & Colors

Learn how to use Vayu UI's semantic color system with the pairing pattern for consistent, accessible designs.

Theming & Colors

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]-content

Every 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.

VariableUsage ContextPaired TextTailwind Classes
CanvasApp background, viewport, bodycanvas-contentbg-canvas text-canvas-content
SurfaceCards, sections, inputs, tables, panelssurface-contentbg-surface text-surface-content
SidebarNavigation sidebar, drawer, rail navsidebar-contentbg-sidebar text-sidebar-content
ElevatedModals, popovers, dropdowns, tooltipselevated-contentbg-elevated text-elevated-content

Canvas

The canvas is your app's foundation. It's the bottom-most layer.

Canvas is a soft off-white (#fafafa)

Easier on the eyes than pure white

Canvas is a rich near-black (#09090b)

Not pure black for eye comfort

// App shell
<body className="bg-canvas text-canvas-content">
  <App />
</body>

// Page container
<main className="bg-canvas min-h-screen">
  {children}
</main>

Surface

Surfaces are containers that sit on top of the canvas. Cards, panels, and form elements use this layer.

Surface is crisp white (#fff)

Creates clear separation from canvas

Surface is elevated (#18181b)

Lighter than canvas for depth

// Card
<div className="bg-surface text-surface-content rounded-surface shadow-surface p-6">
  <h3>Card Title</h3>
  <p>Card content goes here</p>
</div>

// Form input
<input
  className="bg-surface text-surface-content border border-field rounded-control px-3 py-2"
  placeholder="Enter text..."
/>

Sidebars are for navigation. They're visually distinct from the main content area.

Sidebar is neutral gray (#f4f4f5)

Subtle difference from canvas

Sidebar is deeper black (#0a0a0a)

Darker than canvas for contrast

// Navigation sidebar
<nav className="bg-sidebar text-sidebar-content w-64 min-h-screen p-4">
  <a href="/dashboard">Dashboard</a>
  <a href="/settings">Settings</a>
</nav>

// Drawer
<aside className="bg-sidebar text-sidebar-content fixed inset-y-0 left-0 w-80">
  {drawerContent}
</aside>

Elevated

Elevated surfaces appear above everything else. Think modals, tooltips, and dropdowns.

Elevated is white (#fff)

Same as surface, but with stronger shadow

Elevated is lightest layer (#27272a)

Stands out from all other layers

// Modal dialog
<div className="bg-elevated text-elevated-content rounded-overlay shadow-elevated p-6">
  <h2>Modal Title</h2>
  <p>Modal content</p>
</div>

// Dropdown menu
<ul className="bg-elevated text-elevated-content rounded-overlay shadow-elevated">
  <li>Option 1</li>
  <li>Option 2</li>
</ul>

// Tooltip
<div className="bg-elevated text-elevated-content rounded-control shadow-elevated px-2 py-1 text-sm">
  Tooltip text
</div>

Semantic Colors

Semantic colors convey meaning. Use these when you need to communicate state or intent.

VariableMeaningPaired TextUse Cases
BrandPrimary identitybrand-contentCTAs, active states, links
SuccessPositive, completedsuccess-contentConfirmations, checkmarks
WarningCaution, pendingwarning-contentAlerts, unsaved changes
DestructiveError, dangerousdestructive-contentDelete buttons, errors
InfoInformationalinfo-contentHelp text, notifications
MutedDe-emphasizedmuted-contentDisabled states, placeholders

Brand (Primary)

Your brand color is for primary actions and key interactive elements.

LinkActive

Aa

Indigo (#6366f1)

Confident, professional

Aa

Lighter Indigo (#818cf8)

Brighter for dark backgrounds

// Primary button
<button className="bg-brand text-brand-content rounded-control px-4 py-2">
  Get Started
</button>

// Active navigation
<a className="bg-brand text-brand-content rounded-control px-3 py-1">
  Dashboard
</a>

// Selected tab
<button className="border-b-2 border-brand text-brand">
  Active Tab
</button>

Success

For positive outcomes, confirmations, and completed states.

✓ Your changes have been saved successfully.

// Success alert
<div className="bg-success text-success-content rounded-surface p-4">
  Operation completed successfully.
</div>

// Success badge
<span className="bg-success text-success-content rounded-full px-2 py-0.5 text-sm">
  Complete
</span>

// Checkmark icon
<CheckCircle className="text-success" />

Warning

For cautionary messages and pending states.

⚠ You have unsaved changes.

// Warning alert
<div className="bg-warning text-warning-content rounded-surface p-4">
  Please review before continuing.
</div>

// Pending badge
<span className="bg-warning text-warning-content rounded-full px-2 py-0.5 text-sm">
  Pending
</span>

Destructive

For errors, dangerous actions, and critical warnings.

✕ Something went wrong. Please try again.

// Delete button
<button className="bg-destructive text-destructive-content rounded-control px-4 py-2">
  Delete Account
</button>

// Error message
<p className="text-destructive">This field is required.</p>

// Error alert
<div className="bg-destructive text-destructive-content rounded-surface p-4">
  Failed to save changes.
</div>

Info

For informational messages and help content.

ℹ Tip: You can drag and drop files to upload them.

// Info alert
<div className="bg-info text-info-content rounded-surface p-4">
  New feature: Dark mode is now available.
</div>

// Help text
<p className="text-info text-sm">
  Password must be at least 8 characters.
</p>

Muted

For de-emphasized content, disabled states, and placeholders.

This content is less important.

// Disabled button
<button className="bg-muted text-muted-content rounded-control px-4 py-2" disabled>
  Cannot Click
</button>

// Placeholder text
<input
  className="bg-surface text-surface-content placeholder:text-muted-content"
  placeholder="Enter your name..."
/>

// Secondary label
<span className="text-muted-content text-sm">
  Last updated 2 hours ago
</span>

Structural Elements

These colors define borders and focus states.

VariableUsageTailwind Class
BorderContainer borders, dividersborder-border
FieldForm field borders (inputs, selects)border-field
FocusFocus rings, keyboard navigationring-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

BackgroundText ColorExample
bg-canvastext-canvas-contentApp shell
bg-surfacetext-surface-contentCards, forms
bg-sidebartext-sidebar-contentNavigation
bg-elevatedtext-elevated-contentModals, tooltips
bg-brandtext-brand-contentPrimary actions
bg-successtext-success-contentSuccess states
bg-warningtext-warning-contentWarning states
bg-destructivetext-destructive-contentError states
bg-infotext-info-contentInfo states
bg-mutedtext-muted-contentDisabled states

Do's and Don'ts

Do ✅Don't ❌
Use semantic names: bg-brandUse literal names: bg-indigo-500
Pair colors: bg-surface text-surface-contentMix arbitrarily: bg-surface text-blue-500
Use muted for secondary contentUse gray or random gray shades
Let the system handle dark modeManually specify dark mode colors

On this page