Skip to content

Next.js Setup Guide

Airframe works seamlessly with Next.js in both App Router and Pages Router.

npm install @airframeui/core
# or
pnpm add @airframeui/core

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>
)
}

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} />
}

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/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>

)
}
// 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>

)
}
// 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/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>
)
}
// 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>
)
}

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-theme attribute: Standard data attribute
  • .dark class: 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>
)
}

Override tokens in your CSS:

/* app/globals.css */
@import "@airframeui/core/core.css";

:root {
  --af-base-primary: #0066cc;
  --af-hover-mix: 12%;
}

TypeScript support is included. No additional types needed.

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>
)
}

Make sure you’re importing the CSS in the correct file:

  • App Router: app/globals.css imported in app/layout.tsx
  • Pages Router: styles/globals.css imported in pages/_app.tsx

Check that:

  1. CSS is imported correctly
  2. Classes use className (not class) in JSX
  3. No conflicting CSS is overriding styles

If you see build errors, ensure:

  • Node version is 20+ (check .nvmrc)
  • All dependencies are installed
  • TypeScript is configured correctly
  • Explore Patterns - See all available patterns
  • Learn about Tokens - Customize your theme
  • Read Layouts - Layout patterns and recipes