Visual Design
Visual rules and standards for the Airy design system.
Visual Design Rules
This guide documents the visual system behind Airy.
Theme Architecture
Airy supports two themes with light and dark variants:
- App theme:
:rootand.dark - Marketing theme:
.theme-marketingand.theme-marketing.dark
All tokens currently use identical defaults so you can customize later without changing component code.
Color System
Use semantic tokens, not raw hex values:
--background,--foreground--primary,--secondary,--accent--muted,--destructive,--border--card,--popover,--ring
Charts and sidebar tokens exist for dashboards and admin surfaces.
Typography
Use the tokenized scale in airy.css:
--text-xsthrough--text-3xl--line-height-tight,--line-height-normal,--line-height-relaxed--font-weight-regular,--font-weight-medium,--font-weight-semibold,--font-weight-bold
Font families:
--font-sans:"IBM Plex Sans", ui-sans-serif, system-ui, sans-serif--font-mono:"IBM Plex Mono", ui-monospace, monospace--font-handwriting:"Caveat", cursive
Font roles:
- Body and interface text:
font-sans - Code and tabular text:
font-mono - Accent/handwritten treatments:
font-handwriting
Spacing
Use the spacing tokens:
--space-0through--space-24
Prefer layout utilities for spacing. Use tokens when defining new CSS rules.
Radius
Use --radius and derived tokens (--radius-md, --radius-lg, etc).
Brand
- Use the
lev-logoregistry component for all logo placements - Three variants:
icon,logo(wordmark),lockup(wordmark + icon) - The logo uses
currentColor— set text color on the parent to control it - Never recreate the logo as raw SVG or an image tag
Iconography
Airy uses the Lucide icon set. Browse the full library at lucide.dev.
Sizing and alignment
- Icons are drawn on a 24x24 grid but can be scaled up or down as needed
- Icon size should be roughly 2px larger than the accompanying text size
- Maintain 16-24px clear padding from surrounding UI elements
- Align icons optically to the text's x-height for visual balance
- Single-line text: center-align the icon vertically
- Multiline text: align the icon to the top edge of the text
- Never use emojis as icon substitutes
If you need an icon not in the Lucide standard set, contact Product Design before introducing a new one.
Code implementation
In React, import from lucide-react:
import { Mail, Search, Settings } from "lucide-react"
Size pairings (icon should be ~2px larger than the text):
text-xswithsize-3.5text-smwithsize-4text-basewithsize-5text-lgortext-xlwithsize-6
Use text-muted-foreground for secondary/decorative icons and
text-foreground for primary action icons.
Example:
<Button><Mail className="size-4" /> Send email</Button>
When lucide-react cannot be imported (static HTML, emails, sandboxed
environments): use inline SVGs from lucide.dev. Never use emojis or
image tags as substitutes.
Reserved icons
These icons are reserved to ensure consistency across Lev products. They represent core actions and concepts that should not be replaced, restyled, or repurposed. Always reference the reserved set first and only introduce new icons when a concept is not already represented.
Status and feedback:
Info— infoCircleAlert— warningTriangleAlert— errorCircleCheck— success
Universal system actions:
CircleHelp— helpSearch— searchShare2— shareSettings— settingsMoreHorizontal— moreRefreshCw— refresh
File manipulations:
Save— saveTrash2— deleteUpload— uploadDownload— downloadCopy— copy / duplicate
Editing:
Pencil— editLink— linkUndo2— undoRedo2— redo
Dark Mode
- Maintain contrast ratios for all text and controls.
- Avoid hard-coded colors in components.
- Verify
hover,focus, anddisabledstates in both themes.
Marketing Assets
Marketing materials — brochures, one-pagers, landing pages, pitch decks —
use the marketing dark theme (.theme-marketing.dark). All colors must come
from the semantic tokens defined in that theme context, not from primitives
or hardcoded values.
The marketing dark theme provides a dark background with green accent
palette. Use the same semantic token names as the app theme (--background,
--foreground, --card, --primary, --border, etc.) — they resolve to
the correct marketing values automatically.
When generating output that cannot reference CSS variables (PDFs, static
HTML, emails, sandboxed artifacts), call resolve_theme and use the returned
resolved color values and font stacks directly. Never extract colors from Figma
mockups or approximate values.
Resolve Theme Decision Rule
Use this rule before calling resolve_theme:
- Will CSS custom properties be available at render time?
- If yes, use semantic tokens/classes directly (do not resolve).
- If no or unknown, use
resolve_theme.
Common examples:
- Use
resolve_theme: PDFs, emails, static exports, sandboxed outputs. - Do not use
resolve_theme: normal web app components withairy.cssloaded, runtime light/dark switching, or token discovery tasks (useget_theme_tokensandget_guidelines).
For static deliverables, pair theme resolution with static building blocks:
list_componentswithtype=uifor primitives ortype=blockfor layout-level scaffoldsget_document_templatepreflight call to receive exactrequiredComponentCalls- execute each
requiredComponentCallsentry viaget_component_staticexactly as returned get_document_templateonly for optional print/document scaffoldingresolve_themefor final token value substitution when CSS variables are unavailable- follow machine-friendly
nextActionsfrom tool payloads when present - treat template
requiredComponentCallswithrequired=trueanddoNotHandWrite=trueas the default composition contract get_document_templatemay block until requiredcomponentProofsfromget_component_staticare provided
Anti-pattern: template-only output plus hand-written primitive component CSS when
those components already exist in get_component_static.
Canonical static flow example:
list_components({"type":"ui"})list_components({"type":"block"})get_document_template({"name":"marketing-one-pager","context":"print","theme":"marketing-dark"})(preflight)- execute each returned
requiredComponentCalls[*]entry withget_component_static - retry
get_document_templatewithcomponentProofs resolve_theme({"theme":"marketing-dark"})only when CSS variables are unavailable