Angular Setup Guide
Airframe is CSS-only. No Angular-specific package needed. Install the core CSS, import it, and use Airframe classes directly in your templates.
Works with Angular 17+ standalone components and NgModule applications.
Quick Start
Section titled “Quick Start”1. Install
Section titled “1. Install”npm install @airframeui/core2. Import CSS
Section titled “2. Import CSS”Using angular.json (recommended)
Add to angular.json under projects.<app>.architect.build.options:
"styles": [
"node_modules/@airframeui/core/core.css",
"src/styles.scss"
]Using styles.scss
Alternatively, import directly in src/styles.scss:
@import "@airframeui/core/core.css";3. Use in templates
Section titled “3. Use in templates”@Component({
selector: 'app-root',
standalone: true,
template: ` <div class="af-container">
<div class="af-stack af-gap-lg">
<h1>
Welcome
</h1>
<button class="af-btn">
Get Started
</button>
</div>
</div>`
})
export class AppComponent {}That’s it. No wrappers, no modules to import, no extra dependencies.
Standalone Component Examples
Section titled “Standalone Component Examples”Dashboard Layout
Section titled “Dashboard Layout”@Component({
selector: 'app-dashboard',
standalone: true,
template: ` <div class="af-container">
<div class="af-stack af-gap-lg">
<header class="af-inline af-justify-between">
<h1>
Dashboard
</h1>
<button class="af-btn">
New Item
</button>
</header>
<div class="af-grid af-grid-3@lg af-gap-lg">
<div class="af-card">
<h3 class="af-card__title">
Revenue
</h3>
<p class="af-text-h2">
$84,200
</p>
</div>
<div class="af-card">
<h3 class="af-card__title">
Users
</h3>
<p class="af-text-h2">
12,340
</p>
</div>
<div class="af-card">
<h3 class="af-card__title">
Growth
</h3>
<p class="af-text-h2">
+18%
</p>
</div>
</div>
<div class="af-grid">
<div class="af-col-span-8@lg">
<div class="af-card">
<h2 class="af-card__title">
Main Content
</h2>
<p class="af-card__body">
Content goes here
</p>
</div>
</div>
<div class="af-col-span-4@lg">
<div class="af-card">
<h2 class="af-card__title">
Sidebar
</h2>
<p class="af-card__body">
Sidebar content
</p>
</div>
</div>
</div>
</div>
</div>`
})
export class DashboardComponent {}Form with Reactive Forms
Section titled “Form with Reactive Forms”import { Component } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-contact',
standalone: true,
imports: [ReactiveFormsModule],
template: ` <div class="af-container-sm">
<form class="af-stack af-gap-lg" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="af-field">
<label for="name" class="af-field__label">
Name
</label>
<input type="text" id="name" class="af-input" formControlName="name" />
</div>
<div class="af-field">
<label for="email" class="af-field__label">
Email
</label>
<input type="email" id="email" class="af-input" formControlName="email" />
<p class="af-field__help">
We'll never share your email.
</p>
</div>
<div class="af-field">
<label for="message" class="af-field__label">
Message
</label>
<textarea id="message" class="af-textarea" rows="5" formControlName="message">
</textarea>
</div>
<div class="af-inline af-gap-md">
<button type="submit" class="af-btn" [disabled]="form.invalid">
Send
</button>
<button type="button" class="af-btn af-is-secondary" (click)="form.reset()">
Clear
</button>
</div>
</form>
</div>`
})
export class ContactComponent {
form = inject(FormBuilder).group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
message: ['', Validators.required],
});
onSubmit(): void {
console.log(this.form.value);
}
}Navigation with Router
Section titled “Navigation with Router”import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-layout',
standalone: true,
imports: [RouterLink, RouterLinkActive, RouterOutlet],
template: ` <nav class="af-navbar">
<div class="af-container">
<a routerLink="/" class="af-navbar-brand">
My App
</a>
<ul class="af-navbar-nav">
<li>
<a routerLink="/dashboard" routerLinkActive="af-is-active">
Dashboard
</a>
</li>
<li>
<a routerLink="/settings" routerLinkActive="af-is-active">
Settings
</a>
</li>
</ul>
</div>
</nav>
<main class="af-container af-section-lg">
<router-outlet />
</main>`
})
export class LayoutComponent {}Data Table
Section titled “Data Table”import { Component, input } from '@angular/core';
@Component({
selector: 'app-user-table',
standalone: true,
template: ` <div class="af-card">
<table class="af-table">
<thead>
<tr>
<th>
Name
</th>
<th>
Email
</th>
<th>
Role
</th>
<th>
Actions
</th>
</tr>
</thead>
<tbody>
@for (user of users(); track user.id) {
<tr>
<td>
{{ user.name }}
</td>
<td>
{{ user.email }}
</td>
<td>
<span class="af-badge">
{{ user.role }}
</span>
</td>
<td>
<div class="af-inline af-gap-sm">
<button class="af-btn af-is-sm">
Edit
</button>
<button class="af-btn af-is-sm af-is-secondary">
Delete
</button>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>`
})
export class UserTableComponent {
users = input<{ id: number; name: string; email: string; role: string }[]>([]);
}Theming
Section titled “Theming”Custom tokens
Section titled “Custom tokens”Override tokens in your styles.scss. No rebuild needed.
/* src/styles.scss */
@import "@airframeui/core/core.css";
:root {
--af-base-primary: #0066cc;
--af-font-body: 'Inter', sans-serif;
--af-radius-md: 0.5rem;
}Dark mode
Section titled “Dark mode”Dark mode follows system preference by default. Override per-element or globally:
<!-- Global override -->
<html lang="en" data-theme="dark">
<!-- Scoped override -->
<div class="af-card" data-theme="dark">
Dark card on a light page
</div>Available themes: light, dark, high-contrast-light, high-contrast-dark
Dynamic theme switching
Section titled “Dynamic theme switching”@Component({
selector: 'app-theme-toggle',
standalone: true,
template: ` <button class="af-btn af-is-sm" (click)="toggleTheme()">
Toggle {{ isDark ? 'Light' : 'Dark' }}
</button>`
})
export class ThemeToggleComponent {
isDark = false;
toggleTheme(): void {
this.isDark = !this.isDark;
document.documentElement.setAttribute(
'data-theme',
this.isDark ? 'dark' : 'light'
);
}
}View Encapsulation
Section titled “View Encapsulation”Airframe classes work with Angular’s default view encapsulation. If you need to style Airframe elements from a parent component, use :host or set encapsulation to None:
@Component({
selector: 'app-custom',
standalone: true,
encapsulation: ViewEncapsulation.None,
styles: `
.my-custom-card {
--af-card-padding: var(--af-space-6);
--af-card-radius: var(--af-radius-lg);
}
`,
template: ` <div class="af-card my-custom-card">
<h2 class="af-card__title">
Custom Card
</h2>
</div>`
})
export class CustomComponent {}SSR (Angular Universal)
Section titled “SSR (Angular Universal)”Airframe CSS works with Angular SSR out of the box. No special configuration needed. The CSS is loaded via angular.json styles array, which works in both client and server builds.
Troubleshooting
Section titled “Troubleshooting”CSS not loading
Section titled “CSS not loading”- Check that
@airframeui/core/core.cssis in thestylesarray inangular.json - Or check that
src/styles.scssimports it and is itself in thestylesarray
Styles not applying
Section titled “Styles not applying”- Use
classnotclassName(Angular usesclass) - Check that no conflicting CSS is overriding Airframe styles
- If view encapsulation is blocking styles, override tokens via CSS variables instead of targeting Airframe class internals
Build errors
Section titled “Build errors”- Angular 17+ required
- Node 20+ required by the Angular CLI toolchain (Airframe CSS itself has no Node runtime requirement)
- Run
npm installto ensure@airframeui/coreis installed
Next Steps
Section titled “Next Steps”- Patterns - See all available patterns
- Tokens - Customise your theme
- Layouts - Structural primitives
- Accessibility - Built-in accessibility