Next.js Setup Guide
Airframe works seamlessly with Next.js in both App Router and Pages Router.
Quick Start
Section titled “Quick Start”1. Install
Section titled “1. Install”npm install @airframeui/core
# or
pnpm add @airframeui/core2. Import CSS
Section titled “2. Import CSS”App Router (app directory)
Section titled “App Router (app directory)”Create or update app/globals.css:
@import "@airframeui/core/core.css";Then import it in app/layout.tsx:
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}Pages Router (pages directory)
Section titled “Pages Router (pages directory)”Create or update styles/globals.css:
@import "@airframeui/core/core.css";Then import it in pages/_app.tsx:
import '../styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}3. Use Patterns
Section titled “3. Use Patterns”Now you can use Airframe classes anywhere:
// app/page.tsx (App Router) or pages/index.tsx (Pages Router)
export default function Home() {
return (
<div className="af-container">
<div className="af-stack af-gap-lg">
<h1 className="af-title">Welcome</h1>
<button className="af-btn">Get Started</button>
</div>
</div>
)
}App Router Examples
Section titled “App Router Examples”Basic Page
Section titled “Basic Page”// app/dashboard/page.tsx
export default function Dashboard() {
return (
<div className="af-container">
<div className="af-stack af-gap-lg">
<header className="af-inline af-justify-between">
<h1 className="af-title">Dashboard</h1>
<button className="af-btn">New Item</button>
</header>
<div className="af-grid">
<div className="af-col-span-8@lg">
<div className="af-card">
<h2 className="af-card__title">Main Content</h2>
<p className="af-card__body">Content goes here</p>
</div>
</div>
<div className="af-col-span-4@lg">
<div className="af-card">
<h2 className="af-card__title">Sidebar</h2>
<p className="af-card__body">Sidebar content</p>
</div>
</div>
</div>
</div>
</div>
)
}Server Component with Forms
Section titled “Server Component with Forms”// app/contact/page.tsx
export default function Contact() {
return (
<div className="af-container-sm">
<form className="af-stack">
<div className="af-field">
<label htmlFor="name" className="af-field__label">
Name
</label>
<input type="text" id="name" name="name" className="af-input" required />
</div>
<div className="af-field">
<label htmlFor="email" className="af-field__label">
Email
</label>
<input type="email" id="email" name="email" className="af-input" required />
</div>
<div className="af-field">
<label htmlFor="message" className="af-field__label">
Message
</label>
<textarea id="message" name="message" className="af-textarea" rows={5} required />
</div>
<button type="submit" className="af-btn">
Send Message
</button>
</form>
</div>
)
}Client Component
Section titled “Client Component”// app/components/Counter.tsx
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<div className="af-card">
<h2 className="af-card__title">Counter</h2>
<div className="af-card__body af-stack">
<p className="af-title">{count}</p>
<div className="af-inline af-gap-sm">
<button className="af-btn" onClick={() => setCount(count - 1)}>
Decrease
</button>
<button className="af-btn" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
</div>
</div>
)
}Pages Router Examples
Section titled “Pages Router Examples”Basic Page
Section titled “Basic Page”// pages/index.tsx
export default function Home() {
return (
<div className="af-container">
<div className="af-stack af-gap-lg">
<h1 className="af-title">Welcome</h1>
<button className="af-btn">Get Started</button>
</div>
</div>
)
}Page with Layout
Section titled “Page with Layout”// pages/about.tsx
import Layout from '../components/Layout'
export default function About() {
return (
<Layout>
<div className="af-container">
<div className="af-stack af-gap-lg">
<h1 className="af-title">About Us</h1>
<div className="af-card">
<p className="af-card__body">Content goes here</p>
</div>
</div>
</div>
</Layout>
)
}Theming
Section titled “Theming”Override --af-* in globals.css. Force light or dark with data-theme on <html>. JSX uses className, not class. Full workflow: Theming.
TypeScript Support
Section titled “TypeScript Support”TypeScript support is included. No additional types needed.
Server Components
Section titled “Server Components”Airframe is CSS-only, so it works perfectly with Next.js Server Components:
// app/products/page.tsx
export default async function Products() {
const products = await fetchProducts()
return (
<div className="af-container">
<div className="af-grid">
{products.map(product => (
<div key={product.id} className="af-col-span-6@md af-col-span-4@lg">
<div className="af-card">
<h2 className="af-card__title">{product.name}</h2>
<p className="af-card__body">{product.description}</p>
</div>
</div>
))}
</div>
</div>
)
}Troubleshooting
Section titled “Troubleshooting”CSS Not Loading
Section titled “CSS Not Loading”Make sure you’re importing the CSS in the correct file:
- App Router:
app/globals.cssimported inapp/layout.tsx - Pages Router:
styles/globals.cssimported inpages/_app.tsx
Styles Not Applying
Section titled “Styles Not Applying”Check that:
- CSS is imported correctly
- Classes use
className(notclass) in JSX - No conflicting CSS is overriding styles
Build Errors
Section titled “Build Errors”If you see build errors, ensure:
- Your Node version meets the minimum required by your Next.js version
- All dependencies are installed
- TypeScript is configured correctly
Next steps
Section titled “Next steps”- Patterns — buttons, cards, forms, and more
- Theming — CSS variable overrides
- Structure — structural primitives
- Use as much as you need — wrap
af-*if a kit exists