VayuUI
Design System

Layout & Structure

Learn how to properly stack layers and structure your application using Vayu UI's semantic layout system.

Layout & Structure

Building a well-structured UI is like architecture — you need a solid foundation before adding walls and decorations. This guide teaches you how to stack Vayu UI's layers correctly to create clear visual hierarchy.

The Layer Hierarchy

Vayu UI uses four primary layers, ordered from bottom to top:

┌──────────────────────────────────────────────────────┐
│                     Elevated                         │  ← Highest: Modals, Popovers, Tooltips
│  ┌────────────────────────────────────────────────┐  │
│  │                    Sidebar                     │  │  ← Navigation: Sidebars, Drawers
│  │  ┌──────────────────────────────────────────┐  │  │
│  │  │               Surface                    │  │  │  ← Content: Cards, Forms, Tables
│  │  │  ┌────────────────────────────────────┐  │  │  │
│  │  │  │                                    │  │  │  │
│  │  │  │           Canvas                   │  │  │  │  ← Foundation: App Background
│  │  │  │                                    │  │  │  │
│  │  │  └────────────────────────────────────┘  │  │  │
│  │  └──────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────┘

Visual Order

LayerZ-Index ConceptVisual Weight
Canvas0 (base)Lowest — the background everything sits on
Sidebar1Low — sits alongside main content
Surface1Low — sits on top of canvas
Elevated2+Highest — floats above everything

When to Use Each Layer

Canvas — The Foundation

Use for: App shell, page background, viewport

The canvas is your app's backdrop. It should always be the bottom-most layer.

This entire area represents the canvas layer.

Notice how it's slightly off-white, not pure white. This reduces eye strain.

// Root layout
export default function RootLayout({ children }) {
  return (
    <html>
      <body className="bg-canvas text-canvas-content">
        {children}
      </body>
    </html>
  );
}

// Page wrapper
export default function Page() {
  return (
    <main className="bg-canvas min-h-screen">
      <Content />
    </main>
  );
}

Surface — Content Containers

Use for: Cards, panels, forms, tables, list items, input fields

Surfaces sit on top of the canvas to group related content. Most of your UI components will use this layer.

Card Title

This card sits on the canvas.

Another Card

Multiple surfaces create visual separation.

// Card component
export function Card({ children }) {
  return (
    <div className="bg-surface text-surface-content rounded-surface shadow-surface p-6">
      {children}
    </div>
  );
}

// Form container
export function Form({ children }) {
  return (
    <form className="bg-surface text-surface-content rounded-surface p-6">
      {children}
    </form>
  );
}

// Table
export function Table({ children }) {
  return (
    <div className="bg-surface text-surface-content rounded-surface overflow-hidden">
      <table>{children}</table>
    </div>
  );
}

Use for: Side navigation, drawers, rail navigation, mobile menus

Sidebars are distinct from both canvas and surface. They create a dedicated space for navigation.

Main content area

// App layout with sidebar
export function AppLayout({ children }) {
  return (
    <div className="flex min-h-screen bg-canvas">
      {/* Sidebar */}
      <aside className="bg-sidebar text-sidebar-content w-64 p-4">
        <nav>
          <a href="/dashboard">Dashboard</a>
          <a href="/projects">Projects</a>
          <a href="/settings">Settings</a>
        </nav>
      </aside>

      {/* Main content */}
      <main className="flex-1 p-6">
        {children}
      </main>
    </div>
  );
}

// Mobile drawer
export function Drawer({ isOpen, onClose }) {
  return (
    <div className={`fixed inset-y-0 left-0 w-80 bg-sidebar text-sidebar-content transform ${isOpen ? 'translate-x-0' : '-translate-x-full'} transition-transform`}>
      <nav>{/* Nav items */}</nav>
    </div>
  );
}

Elevated — Floating Elements

Use for: Modals, dialogs, popovers, dropdowns, tooltips, notifications

Elevated elements float above everything else. They command attention and require user interaction.

Main content behind the modal

Modal Title

This modal floats above all other content.

// Modal component
export function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
      <div className="bg-elevated text-elevated-content rounded-overlay shadow-elevated p-6 max-w-md">
        {children}
      </div>
    </div>
  );
}

// Dropdown menu
export function Dropdown({ items }) {
  return (
    <ul className="bg-elevated text-elevated-content rounded-overlay shadow-elevated py-1">
      {items.map((item) => (
        <li key={item.id}>
          <button className="w-full px-4 py-2 hover:bg-surface text-left">
            {item.label}
          </button>
        </li>
      ))}
    </ul>
  );
}

// Tooltip
export function Tooltip({ content }) {
  return (
    <div className="bg-elevated text-elevated-content rounded-control shadow-elevated px-2 py-1 text-sm">
      {content}
    </div>
  );
}

Stacking Layers Correctly

The Basic Pattern

Every screen follows this pattern:

export function Screen() {
  return (
    // 1. Canvas — always at the root
    <div className="bg-canvas text-canvas-content min-h-screen">
      {/* 2. Sidebar — optional, for navigation */}
      <aside className="bg-sidebar text-sidebar-content">
        <nav>{/* ... */}</nav>
      </aside>

      {/* 3. Surface — content containers */}
      <main className="p-6">
        <div className="bg-surface text-surface-content rounded-surface p-6">{/* Content */}</div>
      </main>

      {/* 4. Elevated — only when needed */}
      <Modal>
        <div className="bg-elevated text-elevated-content">{/* Modal content */}</div>
      </Modal>
    </div>
  );
}

Common Layout Patterns

Dashboard Layout

export function DashboardLayout() {
  return (
    <div className="bg-canvas text-canvas-content min-h-screen flex">
      {/* Sidebar */}
      <aside className="bg-sidebar text-sidebar-content w-64 p-4">
        <Logo />
        <Navigation />
      </aside>

      {/* Main content */}
      <main className="flex-1 p-6 space-y-6">
        {/* Header card */}
        <header className="bg-surface text-surface-content rounded-surface p-4">
          <h1>Welcome back!</h1>
        </header>

        {/* Stats grid */}
        <div className="grid grid-cols-3 gap-4">
          {[1, 2, 3].map((i) => (
            <div key={i} className="bg-surface text-surface-content rounded-surface p-4">
              Stat Card {i}
            </div>
          ))}
        </div>

        {/* Content area */}
        <div className="bg-surface text-surface-content rounded-surface p-6">Main content</div>
      </main>
    </div>
  );
}

Settings Page

export function SettingsPage() {
  return (
    <div className="bg-canvas text-canvas-content min-h-screen p-6">
      <div className="max-w-2xl mx-auto space-y-6">
        {/* Page header */}
        <div>
          <h1 className="text-h1 font-primary">Settings</h1>
          <p className="text-muted-content">Manage your account preferences</p>
        </div>

        {/* Settings sections */}
        <section className="bg-surface text-surface-content rounded-surface p-6">
          <h2 className="text-h3 font-primary mb-4">Profile</h2>
          {/* Form fields */}
        </section>

        <section className="bg-surface text-surface-content rounded-surface p-6">
          <h2 className="text-h3 font-primary mb-4">Security</h2>
          {/* Form fields */}
        </section>

        <section className="bg-surface text-surface-content rounded-surface p-6">
          <h2 className="text-h3 font-primary mb-4">Notifications</h2>
          {/* Form fields */}
        </section>
      </div>
    </div>
  );
}

Dark Mode Strategy

Vayu UI uses a class-based dark mode strategy. Add .dark to a parent element, and all colors automatically adapt.

How It Works

// Toggle dark mode
<html className="dark">
  {/* Everything inside uses dark mode colors */}
</html>

// Or in light mode
<html>
  {/* Everything uses light mode colors */}
</html>

Visual Comparison

Canvas (#fafafa)

Soft off-white background

Surface (#fff)

Pure white, elevated from canvas

Sidebar (#f4f4f5)

Neutral gray for navigation

Canvas (#09090b)

Near-black, not pure black

Surface (#18181b)

Elevated from canvas

Sidebar (#0a0a0a)

Darker for depth

Implementing Dark Mode

'use client';
import { useState, useEffect } from 'react';

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    // Check system preference
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    const savedTheme = localStorage.getItem('theme');

    const initialTheme = savedTheme || (prefersDark ? 'dark' : 'light');
    setTheme(initialTheme as 'light' | 'dark');
  }, []);

  useEffect(() => {
    document.documentElement.classList.toggle('dark', theme === 'dark');
    localStorage.setItem('theme', theme);
  }, [theme]);

  return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
}

The CSS Behind It

The dark mode is defined in your CSS using :root.dark:

:root {
  --canvas: #fafafa;
  --canvas-content: #18181b;
  /* ... other light mode values */
}

:root.dark {
  --canvas: #09090b;
  --canvas-content: #fafafa;
  /* ... other dark mode values */
}

The Tailwind custom variant makes dark: classes work:

@custom-variant dark (&:where(.dark, .dark *));

Best Practices

Do's

Do ✅Why
Start every page with bg-canvasEstablishes a consistent foundation
Use bg-surface for content containersCreates visual separation from canvas
Reserve bg-elevated for overlaysEnsures modals and popups stand out
Use the pairing patternGuarantees contrast in both modes
Let the system handle dark modeAvoid manual color switching

Don'ts

Don't ❌Why
Skip layers (canvas → elevated)Creates jarring visual jumps
Use surface for modalsWon't stand out enough from content
Manually specify colors for dark modeDefeats the purpose of the system
Nest surfaces too deeplyCreates visual noise

Quick Reference

Layer Decision Tree

Is it the app background?
├── Yes → bg-canvas
└── No → Is it navigation?
    ├── Yes → bg-sidebar
    └── No → Does it float above everything?
        ├── Yes → bg-elevated
        └── No → bg-surface (default for content)

Complete Example

export function App() {
  return (
    <div className="bg-canvas text-canvas-content min-h-screen">
      {/* Sidebar */}
      <aside className="bg-sidebar text-sidebar-content fixed inset-y-0 left-0 w-64 p-4">
        <nav>{/* ... */}</nav>
      </aside>

      {/* Main content */}
      <main className="ml-64 p-6">
        {/* Cards */}
        <div className="bg-surface text-surface-content rounded-surface p-6 mb-6">Content card</div>

        {/* Form */}
        <form className="bg-surface text-surface-content rounded-surface p-6">
          <input className="bg-surface border border-field rounded-control" />
          <button className="bg-brand text-brand-content rounded-control">Submit</button>
        </form>
      </main>

      {/* Modal (when open) */}
      <div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center">
        <div className="bg-elevated text-elevated-content rounded-overlay p-6">Modal content</div>
      </div>
    </div>
  );
}

On this page