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/orsrc/components/ui/components/airy-blocks/orsrc/components/airy-blocks/styles/airy-theme/orsrc/styles/airy-theme/components/lev-assets/orsrc/components/lev-assets/airy-rules/
If a protected file appears to need changes:
- Stop and diagnose the underlying need.
- Prefer an app-owned wrapper, shared component,
className, variant, CSS variable, descendant selector, ordata-slotoverride. - If the change belongs in Airy itself, propose an upstream Airy registry change instead of editing the consuming app's installed copy.
- 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/, orsrc/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-slotattributes 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, andborder. - Never use raw hex or RGB values such as
#ff0000orrgb(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/60orborder-border/50, unless the user explicitly asks for that treatment. - Respect the dual theme structure:
- App:
:rootand.dark. - Marketing:
.theme-marketingand.theme-marketing.dark.
- App:
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, andFormMessage. - 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-onlyfor visual hiding, notdisplay: none. - Keep error messages associated with their controls.
Naming Conventions
- Component names:
PascalCase. - Props:
camelCase. - Variants:
kebab-caseor semantic words such asdefault,secondary, andghost.
Feedback And Errors
- Use
sonnerfor toasts when a top-level action succeeds or fails. - Use
FormMessagefor inline form errors. - Explain errors and empty states plainly.
- For product wording, read
copy.mdbefore 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