Skip to main content
Source of truth is DESIGN.md at the repo root. This page renders it visually. Keep them in sync when tokens change.

Philosophy

GAIA’s UI is dark-first, flat, and single-accent. Every decision flows from five constraints:
  • Dark-first. Primary experience is dark mode (#111111 background). Light mode is supported via CSS variables but is secondary.
  • Flat depth. Depth comes from layered backgrounds (zinc-800zinc-900) only. Never borders, rings, or outlines.
  • Single accent. One primary action color: #00bbff. Everything else is zinc-scale neutrals.
  • Borderless cards. Data cards use background-only separation, no border-, ring-, or outline- in the card tree.
  • Subtle motion. Animations serve entrance, exit, and state changes only. Never decorative. Keep durations ≤ 300ms.

Colors

Brand Tokens

Primary

Selection

Layout rule: Use --color-primary for CTAs, user chat bubbles, selection highlights, and links. Use --color-primary-bg / --color-secondary-bg for the main app canvas and sidebar. Never use these on dark card surfaces, use zinc directly there.

Semantic Variables (Shadcn / Radix)

Use on layout surfaces and standard components. Switch automatically between light and dark.

Zinc Scale, Dark Card Surfaces

Use zinc directly on dark card surfaces, not the CSS variables above.

Surfaces

Text

Status Colors

Always use /10 opacity background paired with matching foreground text. Never solid.

Status

Priority

Never use solid color backgrounds for status badges, always /10 opacity background paired with matching text color.

Typography

Three font families. Never set font-family inline, use the Tailwind token class.

Specimens

Inter, font-sans
The quick brown fox jumps
GAIA proactively manages your email, calendar, tasks, and workflows, so you don’t have to.
Light 300Regular 400Medium 500Semibold 600Bold 700
PP Editorial New, font-serif
The future of personal AI
is already here.
Ultralight 200Ultralight ItalicRegular 400Regular Italic
Anonymous Pro, font-mono
const gaia = await agent.run(“Summarize my inbox”);

Heading scale

Defined globally via @layer base, use semantic HTML tags, styles apply automatically.

Text patterns

Inline code gets border-radius: 10px and padding: 4px globally. Use font-mono or .monospace class.

Spacing


Border Radius

Never use rounded-lg on card containers, that’s the Shadcn base radius, visually too small. Cards always use rounded-2xl.

Depth & Elevation

Depth primarily from background layering and blur, not shadow.

Dark Card System

All data cards, tool sections, and info panels use this contract. Two-tone zinc depth, no borders.

Template

Live preview

Recent Activity4 items
Deploy pipelineSuccess

2 minutes ago

Memory syncWarning

12 minutes ago

Email batchInfo

1 hour ago

Layer reference

Constraints: Never border-, ring-, outline- anywhere in the card tree. rounded-2xl on outer containers always. zinc-800 outer → zinc-900 inner is the entire separation mechanism. Status colors always use /10 opacity backgrounds.

Icons

All icons come from @icons (@theexperiencecompany/gaia-icons). Never raw SVGs.
Icons accept className, height, width, and size props.
Never use Unicode/text symbols in JSX: no , , , , ×, or similar. Always use icon components from @icons.

Animations

Available classes

Transitions

Default: transition-all duration-200. Use this everywhere unless a specific property needs targeting.

Easing

Framer Motion

Import from motion/react, not framer-motion. AnimatePresence is required for exit animations. Keep durations ≤ 300ms for micro-interactions, ≤ 500ms for entrances.

Toast / Notifications

Sileo, already mounted globally. Call the toast function directly. Never add <Toaster> or import from sonner / react-hot-toast. Toast style: dark fill (#262626), white title, white/75 description, top-right position. Action button colors apply automatically by type: error → red, warning → amber, success → green, info → blue.

Component Library

Style preset: new-york · Base color: zinc · CSS variables: on · Located at src/components/ui/

Styling Tools


Forms & Validation

Field pattern

Input states

Error state is driven by aria-invalid={!!error}, styling applies automatically via the Input component.

Loading & Empty States

Loading

Skeleton inherits: bg-accent animate-pulse rounded-md. Match the skeleton shape to the content it replaces.

Empty states

No shared component, build inline:

Interactive States


Dark / Light Mode

Class-based: .dark on <html>. Tailwind dark: modifier works everywhere.
  • Layout surfacesbg-background text-foreground (auto-switches via CSS vars)
  • Dark cardsbg-zinc-800 / bg-zinc-900 (always dark, no dark: needed)
  • Explicit overrides → only when CSS variables don’t cover it
  • Brand cyan (#00bbff) is the same in both modes

Responsiveness

Core layout does not use lg:, xl:, or 2xl: breakpoints.

Scrollbars

Global scrollbar is already styled (8px, pill-shaped, zinc-700 thumb). Use .no-scrollbar to suppress chrome on scroll areas where it would be distracting.

Rules

Do
  • rounded-2xl on all outer card containers
  • zinc-800 outer → zinc-900 inner for card depth
  • /10 opacity backgrounds for all status colors
  • Import icons from @icons
  • cn() for all conditional class merging
  • transition-all duration-200 as the default transition
  • AnimatePresence for exit animations
  • Import from motion/react
  • import { toast } from "@/lib/toast"
Don’t
  • border-, ring-, or outline- anywhere in a card tree
  • rounded-lg on card containers
  • Solid backgrounds for status badges
  • Unicode symbols in JSX (, , ), use icons
  • Add <Toaster>, already mounted globally
  • Import from sonner or react-hot-toast
  • CSS variables on dark card surfaces, use zinc directly
  • Set font-family inline, use Tailwind token classes
  • Import from framer-motion, use motion/react