Design System Rules

Rules and implementation patterns for Airy consuming apps.

Airy Design System Rules

These rules apply to consuming applications using the Airy design system. They cover registry-managed files, app-owned extension points, component usage, forms, accessibility, and implementation patterns.

For copy, labels, button text, errors, empty states, and product wording, also read copy.md.

App Policy Wins

Airy rules provide defaults for consuming apps. If an app has stricter or more specific guidance in AGENTS.md, local Cursor rules, or established code patterns, follow the app's guidance first.

Do not introduce a second framework, form stack, styling system, routing pattern, or validation library into a consuming app without an explicit migration plan.

For visual design language, token roles, presentation guidance, and iconography, use the Airy guidelines if they are installed: ../airy-guidelines/design.md and ../airy-guidelines/visual-design.md.

If those files are missing, install them with:

npx shadcn@latest add @airy/airy-guidelines

Protected Airy Installs

Do not edit Airy-installed directories or files in consuming apps.

Protected consuming-app paths depend on the app's install layout. Common protected paths include:

  • components/ui/ or src/components/ui/
  • components/airy-blocks/ or src/components/airy-blocks/
  • styles/airy-theme/ or src/styles/airy-theme/
  • components/lev-assets/ or src/components/lev-assets/
  • airy-rules/

If a protected file appears to need changes:

  1. Stop and diagnose the underlying need.
  2. Prefer an app-owned wrapper, shared component, className, variant, CSS variable, descendant selector, or data-slot override.
  3. If the change belongs in Airy itself, propose an upstream Airy registry change instead of editing the consuming app's installed copy.
  4. Do not patch the consuming app's installed copy as a local customization.

These files are installed from the Airy registry. Local edits can be overwritten on updates and can break upgrade paths.

Where Custom Work Goes

Use app-owned code for application-specific changes:

  • Create wrappers or composed components outside protected Airy install folders.
  • Prefer app-specific folders such as components/<feature>/, src/components/<feature>/, app/_components/, or src/app/_components/.
  • Use Airy primitives and blocks through composition rather than editing their source.
  • Document any app-specific customizations near the wrapper or feature code.

If a change truly belongs in a protected Airy component or theme file, propose an upstream Airy registry change. Do not patch the consuming app's installed copy as a local customization.

Component Usage

  • Use the app's configured Airy import aliases, commonly @/components/ui/* and @/components/airy-blocks/*.
  • Do not recreate primitives such as Button, Card, Dialog, Sidebar, Input, or Form.
  • Prefer component variants and size props before adding custom classes.
  • Add custom classes only for layout, spacing, or a clearly app-owned wrapper.
  • Use descendant selectors or data-slot attributes from an app-owned wrapper when a child primitive needs styling.

Example:

import { Button } from '@/components/ui/button'
;<Button variant="default" size="lg">
  Continue
</Button>

Do not modify components/ui/button.tsx or other installed primitive files to change one app's behavior.

Select Composition

When using Select, wrap menu options in SelectGroup inside SelectContent. This follows the shadcn composition and preserves the menu gutter.

SelectGroup provides optional grouping semantics. It does not change the single-value behavior of Select.

<SelectContent>
  <SelectGroup>
    <SelectItem value="one">One</SelectItem>
  </SelectGroup>
</SelectContent>

Component Classification

Airy has two installed component families:

  • Primitives (components/ui/) - shadcn components installed from the registry. These are protected low-level building blocks.
  • Blocks (components/airy-blocks/) - composed Airy components built on primitives. These are also protected registry-managed assets.

If it is not an installed primitive or block, treat it as app-owned code and keep it outside protected Airy folders.

Brand Assets

Use installed Lev assets from @/components/lev-assets/* for Lev brand marks. Do not recreate, approximate, or edit the logo assets without explicit confirmation.

Install if needed:

bunx shadcn@latest add @airy/lev-logo -y -o

Import:

import { LevLogo } from '@/components/lev-assets/lev-logo'

Variants:

  • icon - mark only.
  • logo - wordmark only.
  • lockup - wordmark and icon.

LevLogo uses currentColor, so it inherits text color automatically.

Theming

  • Use semantic tokens such as background, foreground, primary, muted, card, and border.
  • Never use raw hex or RGB values such as #ff0000 or rgb(255,0,0).
  • Never use raw Tailwind colors such as bg-orange-500; use semantic tokens.
  • Do not apply alpha modifiers to semantic color variables, such as text-foreground/60 or border-border/50, unless the user explicitly asks for that treatment.
  • Respect the dual theme structure:
    • App: :root and .dark.
    • Marketing: .theme-marketing and .theme-marketing.dark.

The installed Airy theme directory, commonly styles/airy-theme/ or src/styles/airy-theme/, is protected. Ask before modifying any file in that directory.

Surface Treatments

Use .surface-glass for translucent, blurred surfaces such as floating panels, popovers, command palettes, and similar elevated UI.

Glass owns background, backdrop-filter, border, and box-shadow. Pair it with Tailwind utilities for radius, padding, sizing, layout, and typography.

<div className="surface-glass rounded-2xl p-6">...</div>
<NavigationMenuViewport className="surface-glass" />

Per-instance overrides use CSS variable hooks:

  • --glass-tint - background color.
  • --glass-blur - backdrop blur radius.
  • --glass-saturation - backdrop saturation boost.

Avoid glass-on-glass nesting. For full guidance, see ../airy-guidelines/visual-design.md.

Forms

  • Use the app's established form stack.
  • If the app does not have an established form stack, prefer Airy Form components with react-hook-form.
  • Do not introduce a second form stack into a consuming app without an explicit migration plan.
  • When using Airy Form components, use Form, FormField, FormItem, FormLabel, and FormMessage.
  • Validate with the app's established schema library, or use Zod when no schema standard exists.
  • Keep labels, descriptions, and error messages accessible.

Before generating form code, inspect the consuming app's existing forms and follow that pattern.

Accessibility

  • Every input must have a label.
  • All interactive elements must be keyboard accessible.
  • Preserve focus rings and aria-* attributes.
  • Use sr-only for visual hiding, not display: none.
  • Keep error messages associated with their controls.

Naming Conventions

  • Component names: PascalCase.
  • Props: camelCase.
  • Variants: kebab-case or semantic words such as default, secondary, and ghost.

Feedback And Errors

  • Use sonner for toasts when a top-level action succeeds or fails.
  • Use FormMessage for inline form errors.
  • Explain errors and empty states plainly.
  • For product wording, read copy.md before writing or revising UI text.

Consuming App File Structure

components/ or src/components/
├── ui/           # Protected Airy primitives, do not modify without confirmation
├── airy-blocks/  # Protected Airy blocks, do not modify without confirmation
├── lev-assets/   # Protected Lev assets, do not modify without confirmation
└── ...           # App-owned components and wrappers

styles/airy-theme/ or src/styles/airy-theme/
└── ...           # Protected Airy theme files, do not modify without confirmation

airy-rules/
└── ...           # Protected Airy rule docs, do not modify without confirmation