AI Rules
The authoritative rules ship with @airframeui/core as @airframeui/core/rules (AIRFRAME_RULES.md). This page is the docs summary agents and humans should follow when generating markup. What Airframe is, for retrieval: Airframe for AI agents.
Product names
Section titled “Product names”Use these names in agent output, comments, and docs. Package ids and hosts are not the product names.
- Airframe — the system. Not “AirframeUI”.
- IntelliSense for Airframe — the editor plugin (
airframe-intellisense). VS Code and Cursor are hosts. Do not call it “the VS Code extension”. - Airframe MCP —
@airframeui/mcp. - Airframe → Figma —
/figma(product). How-to: Figma plugin. Preferred:npx af theme export→ Airframe plugin → Update Figma (native Variables). Theme Studio /af theme export --no-figmafour-file pack is the fallback. - Write Visual StudioCode, never “Visual Studio” or “Visual Studio Code”.
Construction model
Section titled “Construction model”Airframe’s UI language has four levels. Do not flatten them into one “component” layer.
- Structure — arrange and compose UI. Primitives (
af-stack,af-inline,af-grid) and shells (af-app,af-cover,af-body) - Patterns — individual HTML/CSS UI (
af-btn,af-card,af-dialog) - Blocks — section guides. Start from Blocks or
/llms-blocks.txt, then edit - Blueprints — complete interfaces. Start from Blueprints, then change them
AI should understand this hierarchy, not only class names. If a block or blueprint is close, start there. Then adapt it. Do not treat gallery markup as the only valid structure.
A block is a section guide. A blueprint is a screen guide. Do not call a pattern a component.
Design rationale
Section titled “Design rationale”Why Airframe looks the way it does. Use this when a token or pattern does not spell out the case.
- Structure over decoration — name the UI (
af-stack,af-card,af-btn). Hierarchy and spacing do the work. Do not add shadows, gradients, or one-off radii to finish a screen. - One language — override
--af-*for brand. Do not fork a second look beside Airframe. - Composition over customisation — prefer an existing structure class, pattern, block, or blueprint over a new class, token, or custom CSS.
- Progressive density — default spacing is generous. Tighten with
af-density-compactor a smalleraf-gap-*. Do not invent spacing, radius, or type scales.
Composition
Section titled “Composition”Do not flatten the four levels into one “component” layer. When generating UI, pick the highest close match, then edit:
- Blueprint — complete screen is close → Blueprints
- Block — section is close → Blocks or
/llms-blocks.txt - Pattern — catalog HTML/CSS (
af-btn,af-card,af-input). Look up. Do not inventaf-*names - Structure — primitives (
af-stack,af-inline,af-grid) and shells (af-app,af-cover,af-body). Not Tailwindflex flex-col gap-4
Prefer
Section titled “Prefer”- Semantic HTML (
<button>,<a>,<label>, real headings) - Catalog patterns over composing the same UI from utilities
--af-*tokens over hex,rgb(), or rawfont-family- Pattern defaults (cards already stack; form controls are already full width)
- One token source: alias existing
--color-*into--af-*once for semantics patterns consume - BYO Tokens: product-owned namespace via
theme.byoTokens.prefix— export to Figma, optionally bridge into--af-*. See BYO Tokens - Kit wrappers (
<my-button>) in product templates when they exist — they renderaf-*
Do not
Section titled “Do not”- Invent
af-*classes or--af-*token names - Recreate a catalog pattern with utilities (
div+ padding + border instead ofaf-btn/af-card) - Hard-code colours, fonts, or radii when a token exists
- Introduce a second palette, radius scale, or layout grammar
- Wrap every grouping in
af-card - Ship a parallel look (
.ui-buttonthat is not anaf-btn) - Dump the catalog into the prompt — look up on demand
- Copy gallery markup unchanged when the product needs a different layout
- Nest interactive elements or put flow-only tags inside
<button>(see Valid HTML)
Valid HTML
Section titled “Valid HTML”Markup must be valid and semantic. Prefer native elements. Do not fake controls with div + role.
Full inventory: catalog htmlSemantics.rules (id, category, severity, selector, message). MCP validate_markup loads that list and returns ruleId on each issue. Nest selectors (button a) are enforced as descendants; other selectors document the check.
Pattern look-alikes and hard limits also live on catalog entries:
useCases—{ stance: recommended|discouraged, when, useInstead?, prefer?, rationale? }.useInsteadis another catalog id;preferis a utility or non-catalog class (e.g.af-sr-only)guidelines—{ level: must|must-not|should|should-not, text }; treatmust*as hard limitscriteria—{ id, statement, level, check? }wherecheckmay point atvalidate_markupor ESLint viaruleId
/* Nest selectors — must never match */
a a, a button,
button a, button button, button select, button input,
button div, label label, label div,
p div, span div, p p, form form, main mainAlso required: button[type] (submit for form primary, else button), real a[href] (not #), img[alt] + real src, form accessible names, field errors (af-field__error id + aria-describedby), data tables with caption + thead. One interactive per hit target.
For LLMs and coding agents
Section titled “For LLMs and coding agents”Prefer these entry points over scraping the whole site:
- Airframe for AI agents — entity description (vocabulary, composition, MCP, endpoints)
/llms.txt— curated index of documentation sets and package resources/llms-small.txt— abridged docs (best default context)/llms-blocks.txt— copy-ready page sections/blueprints— complete screens/_llms-txt/ai-rules.txt— this page + AI engineering notes/_llms-txt/getting-started.txt— install, usage, tokens, structure
After npm install @airframeui/core, load in-project resources:
// JSON (safe to import in Node / bundlers)
import catalog from '@airframeui/core/catalog' with { type: 'json' }; // pattern metadata
import classes from '@airframeui/core/classes' with { type: 'json' }; // class reference
import examples from '@airframeui/core/examples' with { type: 'json' }; // code examples
import { catalog as tokenCatalog } from '@airframeui/tokens'; // --af-* catalog (or @airframeui/tokens/catalog.json)
// Markdown — resolve the package export and read as text (do not assume a JS default export)
// @airframeui/core/rules → AIRFRAME_RULES.md
// @airframeui/core/ai → AI_INTEGRATION.mdPattern catalog
Section titled “Pattern catalog”@airframeui/core/catalog is the machine-readable inventory of every CSS pattern (meta.patternCount), plus structure (primitives and shells), utility summaries, and htmlSemantics (HTML E/W rules + nest selectors). That includes src/patterns/*.css and foundations listed in catalog.enrichment.json → foundationFiles (divider, link, lists).
It is regenerated on @airframeui/core build from:
- Pattern CSS files (
packages/core/src/patterns/*.css) — base class, variants, parts, class list - Foundation CSS listed in
foundationFiles(divider.css,link.css,lists.css, …) - Hand overlays in
catalog.enrichment.json— descriptions, categories, a11y notes, aliases (app-shell→app), structure, utilities,htmlSemantics, and situational routing (useCases, leveledguidelines, optionalcriteria).
Agents should treat the catalog (or MCP tools that read it) as the source of truth for which patterns exist. Do not invent af-* classes that are not in the catalog or @airframeui/core/classes. get_pattern("app-shell") and get_example("divider") resolve via aliases. Compose valid semantic HTML — follow htmlSemantics (e.g. a a, button div, button[type], img[alt]). Prefer useCases / guidelines on a pattern over guessing look-alikes; treat must / must-not as hard limits; follow useInstead / prefer.
Token catalog
Section titled “Token catalog”@airframeui/tokens/catalog.json (also import { catalog } from '@airframeui/tokens') lists every --af-* token with a description and default colours per mode. It is regenerated on the tokens build from CSS plus token-meta.json. Prefer unsuffixed names in components (--af-color-background, not --af-color-background--dark).
Airframe MCP server
Section titled “Airframe MCP server”Prefer @airframeui/mcp over dumping catalogs. Loop: search_patterns → get_pattern → get_example → compose af-* → validate_markup. Dense get_pattern includes useCases / guidelines / criteria when present.
Install -D @airframeui/mcp (same version as @airframeui/core) or run with npx -y @airframeui/mcp@<version>. Restart the airframeui server after saving. Full setup: Airframe MCP.
| Host | File |
|---|---|
| Cursor | .cursor/mcp.json (or Cursor Settings → MCP) |
| Claude Code | .mcp.json |
| Codex | .codex/config.toml |
| VS Code | .vscode/mcp.json |
Dev dependency (recommended):
{
"mcpServers": {
"airframeui": {
"command": "node",
"args": ["./node_modules/@airframeui/mcp/dist/server.js"]
}
}
}{
"mcpServers": {
"airframeui": {
"command": "node",
"args": ["./node_modules/@airframeui/mcp/dist/server.js"]
}
}
}[mcp_servers.airframeui]
command = "node"
args = ["./node_modules/@airframeui/mcp/dist/server.js"]{
"servers": {
"airframeui": {
"type": "stdio",
"command": "node",
"args": ["./node_modules/@airframeui/mcp/dist/server.js"]
}
}
}npx (no install) — pin the version so @latest cannot drift from core:
{
"mcpServers": {
"airframeui": {
"command": "npx",
"args": ["-y", "@airframeui/mcp@<version>"]
}
}
}{
"mcpServers": {
"airframeui": {
"command": "npx",
"args": ["-y", "@airframeui/mcp@<version>"]
}
}
}[mcp_servers.airframeui]
command = "npx"
args = ["-y", "@airframeui/mcp@<version>"]{
"servers": {
"airframeui": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@airframeui/mcp@<version>"]
}
}
}Bootstrap agent docs (af init --agents)
Section titled “Bootstrap agent docs (af init --agents)”Drop a short, version-pinned agent file into your project so coding agents know the Airframe workflow without scraping the whole docs site:
npm install -D @airframeui/build
npx af init --agents
# or target a specific tool:
npx af init --agents --agent cursor # .cursor/rules/airframe.mdc
npx af init --agents --agent claude # .claude/CLAUDE.md
# --force replaces a marked Airframe file only; it will not overwrite a product AGENTS.mdWhat it writes:
AIRFRAME.mdby default (--agent agents/codex) — does not replace a productAGENTS.md- Cursor / Claude paths as above, or
--agent-docs-path <file> - A reliable
package.json"af"script alias (skip with--no-script)
The file tells agents to: use @airframeui/core/catalog as the class source of truth (same as IntelliSense / ESLint / MCP), follow the design rationale (structure over decoration, one language, compose existing Airframe, progressive density), pick a close blueprint or block before composing patterns and layouts, wrap an existing UI kit rather than duplicating a look, run search_patterns → get_pattern → get_example → validate_markup before inventing classes, and avoid Tailwind/Bootstrap leftovers, hardcoded colours, and recreated patterns. Re-run after upgrading @airframeui/core to refresh the pinned version block.
vs Airframe MCP
Section titled “vs Airframe MCP”They are complementary, not two versions of the same thing. Both use @airframeui/core/catalog and @airframeui/core/classes — the same source of truth as IntelliSense and the ESLint plugin.
Agent bootstrap (af init --agents) | Airframe MCP (@airframeui/mcp) | |
|---|---|---|
| What it is | A static instruction file in the repo | A running stdio server the host talks to |
| Package | @airframeui/build | @airframeui/mcp |
| What it does | Writes AIRFRAME.md / a Cursor rule / Claude file + an "af" npm script | Lets the agent look things up on demand |
| Source of truth | Points at the catalog; interpolates layout/pattern names from it | Reads the catalog and token files at runtime |
| Puts CSS on the page | No | No |
MCP is the toolbox (search_patterns, get_pattern, validate_markup, …). Bootstrap is standing orders so the agent actually follows that loop. You can use MCP with no bootstrap. Bootstrap without MCP still helps (agents can import the catalog) but they lose live search and validate_markup. Connect MCP separately — this command does not write .cursor/mcp.json. See Airframe MCP.
After upgrading Airframe packages, the agent file is a snapshot. Check it, then refresh:
npx af doctor
npx af init --agentsaf doctor fails if AIRFRAME.md (or the Cursor/Claude file) is pinned to an older @airframeui/core than you have installed, or if @airframeui/mcp / @airframeui/build do not match core. af init --agents rewrites the marked block from the current catalog (no --force needed if the markers are still there). If the file exists without markers, the command refuses — it will not append into a product AGENTS.md. --force is refused on unmarked files.
Also documented under Build Tool → af init —agents.
Non-negotiable rules
Section titled “Non-negotiable rules”- Semantic HTML first —
<button type>,<a href>,<label>, real headings. Follow cataloghtmlSemantics(E/W). - Token-first theming — prefer
--af-color-*/ other--af-*tokens; do not invent unknown token names - One look — Use
af-*as the system, or wrap it in a kit. Do not ship a parallel palette or layout grammar (.ui-stackthat copiesaf-stack) - Wrappers sit on
af-*—<my-button>/<MyButton>is anaf-btnwith framework inputs, not a restyled cousin. Keep kit JS for behaviour CSS cannot do (showing toasts, focus trap, togglingaf-is-collapsed) - Readable DOM — avoid utility soup; prefer patterns + structure classes
- Do not invent — no unknown
af-*/--af-*names, no parallel look, no recreating catalog patterns with utilities af-prefix — only use known Airframe classes (see@airframeui/core/classes)- Minimal classes — components include sensible defaults; don’t restate them. Filled
af-btnis already primary — do not addaf-is-primaryexcept withaf-is-ghost - Modifiers — variants/state use
af-is-*(e.g.af-btn af-is-outline,af-btn af-is-ghost) - Responsive — breakpoint suffixes use
@in HTML:af-grid-2@md,af-stack@lg - Framework attrs —
classin HTML/Vue/Angular/Astro;classNamein React
If the project has a UI kit, product templates compose af-stack / af-inline / af-grid for layout and <my-*> for widgets. Do not use af-btn in product templates when <my-button> exists. Teams without a kit use af-* directly. Full contract: Use as much as you need.
Theme generation for agents
Section titled “Theme generation for agents”Runtime CSS variable overrides are the default. When mapping an existing design system into Airframe tokens:
- Theme Studio — upload Tokens Studio / DTCG / CSS / Figma Variables and export
theme.css - CLI —
npx af theme generate --from ./tokens.json -o theme.css(via@airframeui/build) - MCP —
generate_theme/lint_themeon@airframeui/mcp - Rules —
@airframeui/theme/rulesand@airframeui/theme/mappingafternpm install -D @airframeui/theme
See Figma plugin, Airframe → Figma, Theming, and Theme package.
AI predictable patterns
Section titled “AI predictable patterns”Canonical patterns (aligned with AIRFRAME_RULES.md):
Card (minimal — default stacking)
Section titled “Card (minimal — default stacking)”Cards already display: flex; flex-direction: column with a default gap (--af-space-4). Do not add redundant stack/gap.
<!-- ✅ Good: default card stacking -->
<div class="af-card">
<h2 class="af-card__title">Title</h2>
<div class="af-card__body">Content</div>
</div>
<!-- ❌ Avoid: redundant stack/gap on cards -->
<div class="af-card af-stack af-gap-md">
<h2 class="af-card__title">Title</h2>
<div class="af-card__body">Content</div>
</div>Simple grid (equal columns)
Section titled “Simple grid (equal columns)”af-grid-* includes grid display. Default gap is --af-space-3 (same as af-gap-md).
<!-- 2 equal columns -->
<div class="af-grid af-grid-2 af-gap-lg">
<div>Item 1</div>
<div>Item 2</div>
</div>
<!-- Responsive equal columns -->
<div class="af-grid af-grid-1 af-grid-2@md af-grid-3@lg af-gap-lg">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>12-column grid (asymmetric layouts)
Section titled “12-column grid (asymmetric layouts)”<div class="af-grid">
<div class="af-col-span-8@lg">Main content</div>
<div class="af-col-span-4@lg">Sidebar</div>
</div>Note: af-grid:has(> [class*='af-col-span']) switches to 12 columns. Default gap is --af-space-3 (af-gap-md). Override with af-gap-sm / af-gap-lg when needed.
Stack + inline (header pattern)
Section titled “Stack + inline (header pattern)”<div class="af-stack af-gap-lg">
<header class="af-inline af-justify-between">
<h1>Title</h1>
<div class="af-inline af-gap-sm">
<button type="button" class="af-btn">Action</button>
<button type="button" class="af-btn af-is-ghost">More</button>
</div>
</header>
</div>App shell + settings
Section titled “App shell + settings”<div class="af-app">
<aside class="af-app__sidebar">…</aside>
<main class="af-app__main">
<nav class="af-nav-pills" aria-label="Settings">
<a href="#profile" aria-current="page">Profile</a>
<a href="#security">Security</a>
</nav>
<label class="af-action-row">
<span class="af-action-row__copy">
<span class="af-action-row__title">Email alerts</span>
<span class="af-action-row__hint">When someone mentions you</span>
</span>
<span class="af-action-row__control">
<input type="checkbox" role="switch" />
</span>
</label>
</main>
</div>Do not use af-tabs for settings nav (af-nav-pills) or af-field-inline for action rows (af-action-row). Full-page waits use af-page-loader + af-spinner, not af-backdrop. Overlay af-drawer width is --af-drawer-width, not --af-sidebar-width.
Skip to content
Section titled “Skip to content”First focusable element in the body, before site nav. tabindex="-1" on the target so Safari moves keyboard focus. Do not also add af-sr-only-focusable. Do not put af-skip inside <nav>.
<a href="#main" class="af-skip">Skip to content</a>
<nav class="af-navbar">…</nav>
<main id="main" tabindex="-1">…</main>Semantic typography first
Section titled “Semantic typography first”<!-- ✅ Good: semantic element, no class needed -->
<h1>Title</h1>
<p>Body text</p>
<!-- ✅ Good: visual override when semantics differ -->
<h2 class="af-text-h1">Visually H1, semantically H2</h2>
<p class="af-text-caption">Caption</p>
<!-- ❌ Avoid: redundant class on semantic element -->
<h1 class="af-text-h1">Title</h1>There is no af-is-block, af-is-full, or af-btn-block.
Form controls (af-input, af-textarea, af-select, af-range) are already width: 100%. Do not add af-w-full.
Buttons are inline-flex. Stretch with af-w-full. Do not use af-block on af-btn.
<!-- Form control: already full-width -->
<input type="email" class="af-input" />
<!-- ❌ Avoid: redundant on form controls -->
<input type="email" class="af-input af-w-full" />
<!-- Button: shrink-to-content; compose af-w-full when needed -->
<button type="submit" class="af-btn af-w-full">Continue</button>
<!-- ❌ Avoid: invented modifier -->
<button type="submit" class="af-btn af-is-block">Continue</button>Related tooling
Section titled “Related tooling”- Airframe for AI agents — what this is: vocabulary, MCP, catalogs, endpoints
- Figma plugin — plugin how-to (
af theme export→ Update Figma) - Airframe → Figma — product overview, Theme Studio, and four-file pack
- Use as much as you need — Airframe as the system, or wrap a kit. Agent layering rules
- IntelliSense for Airframe — tokens and classes
- Packages — npm packages and IntelliSense for Airframe
- ESLint plugin — leftover Tailwind/Bootstrap classes, unknown
af-*names, redundant defaults, structure classes - Why Airframe — why the structural contract exists
- Airframe MCP — Cursor / Claude: search patterns, validate markup, look up tokens
- Blocks — section guides.
/llms-blocks.txt - Blueprints — screen guides