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
| Layer | Z-Index Concept | Visual Weight |
|---|---|---|
| Canvas | 0 (base) | Lowest — the background everything sits on |
| Sidebar | 1 | Low — sits alongside main content |
| Surface | 1 | Low — sits on top of canvas |
| Elevated | 2+ | 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>
);
}Sidebar — Navigation Areas
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 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-canvas | Establishes a consistent foundation |
Use bg-surface for content containers | Creates visual separation from canvas |
Reserve bg-elevated for overlays | Ensures modals and popups stand out |
| Use the pairing pattern | Guarantees contrast in both modes |
| Let the system handle dark mode | Avoid manual color switching |
Don'ts
| Don't ❌ | Why |
|---|---|
| Skip layers (canvas → elevated) | Creates jarring visual jumps |
| Use surface for modals | Won't stand out enough from content |
| Manually specify colors for dark mode | Defeats the purpose of the system |
| Nest surfaces too deeply | Creates 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>
);
}