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">
<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>
</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 af-stack">
<h2 className="af-card__title">Counter</h2>
<div className="af-card__body">
<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”Dark Mode
Section titled “Dark Mode”Dark mode follows system preference by default. To override the system preference, set dark mode on the root element using one of these methods:
data-themeattribute: Standard data attribute.darkclass: CSS class
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
// Using data-theme attribute
<html lang="en" data-theme="dark">
<body>{children}</body>
</html>
)
}
// Or using .dark class
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" class="dark">
<body>{children}</body>
</html>
)
}
// Or using .dark class
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="dark">
<body>{children}</body>
</html>
)
}Custom Theme
Section titled “Custom Theme”Override tokens in your CSS:
/* app/globals.css */
@import "@airframeui/core/core.css";
:root {
--af-base-primary: #0066cc;
--af-hover-mix: 12%;
}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-cols-12">
{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:
- Node version is 20+ (check
.nvmrc) - All dependencies are installed
- TypeScript is configured correctly