Skip to main content
ninna-ui

Next.js Setup

Step-by-step guide to set up Ninna UI in a Next.js 15 project with the App Router and Tailwind CSS v4. Best choice for server-rendered applications.

Prerequisites

  • Node.js 18+
  • Next.js 15 with App Router
  • Tailwind CSS v4

Create Project

Skip this step if you already have a Next.js project.

npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app

Install Ninna UI

Install the component packages you need. All packages auto-install @ninna-ui/core.

pnpm add @ninna-ui/primitives @ninna-ui/feedback @ninna-ui/forms @ninna-ui/layout

You can also install individual packages later — e.g. @ninna-ui/overlays, @ninna-ui/navigation, @ninna-ui/data-display.

Install Tailwind CSS

Next.js uses the @tailwindcss/postcss plugin for Tailwind integration.

pnpm add
pnpm add -D tailwindcss @tailwindcss/postcss

Configure PostCSS

Create or update your PostCSS config at the project root.

// postcss.config.mjs
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};

CSS Setup

Replace the contents of your global CSS file.

In app/globals.css:

@import "tailwindcss";
@import "@ninna-ui/core/theme/presets/default.css";
@variant dark (&:is(.dark *));

Then add data-theme to your root layout:

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" data-theme="default">
<body>
<div className="min-h-screen bg-base-50 text-base-content antialiased">
{children}
</div>
</body>
</html>
);
}

The theme preset automatically includes all utility classes used by Ninna UI components — no @source directive needed. Note: keep Tailwind classes on a wrapper <div>, not on <body>, to avoid SSR hydration mismatches.

Your First Component

Verify the setup by rendering a Ninna UI component.

"use client";
import { Button, Heading, Text } from "@ninna-ui/primitives";
export default function Page() {
return (
<div className="p-8">
<Heading as="h1" size="3xl">Hello Ninna UI</Heading>
<Text className="text-base-content/70 mt-2">It works!</Text>
<Button color="primary" className="mt-4">Click me</Button>
</div>
);
}

"use client" Directive

Important: Ninna UI components use React hooks internally.

All Ninna UI components are client components. Any file that imports and renders Ninna UI components must include "use client" at the top.

You can still use Server Components for data fetching and pass data down to client component wrappers that use Ninna UI.

Hydration tip: Avoid putting Tailwind utility classes directly on the <body> tag in app/layout.tsx. Use a wrapper <div> instead — this prevents a hydration mismatch caused by @tailwindcss/postcss adding internal attributes during SSR.

// app/components/my-form.tsx
"use client";
import { Input, Field } from "@ninna-ui/forms";
import { Button } from "@ninna-ui/primitives";
export function MyForm({ defaultValues }: { defaultValues: Record<string, string> }) {
return (
<form>
<Field label="Name">
<Input defaultValue={defaultValues.name} />
</Field>
<Button type="submit" color="primary" className="mt-4">Submit</Button>
</form>
);
}

Theme Presets

Switch themes by changing the CSS import and the data-theme attribute.

/* 1. Change the CSS import in globals.css */
/* Default — Electric purple + magenta */
@import "@ninna-ui/core/theme/presets/default.css";
/* Ocean — Blue + cyan */
@import "@ninna-ui/core/theme/presets/ocean.css";
/* Sunset — Orange + rose */
@import "@ninna-ui/core/theme/presets/sunset.css";
/* Forest — Green + amber */
@import "@ninna-ui/core/theme/presets/forest.css";
/* Minimal — Monochrome */
@import "@ninna-ui/core/theme/presets/minimal.css";
/* 2. Update data-theme in app/layout.tsx */
/* <html lang="en" data-theme="ocean"> */

Dark mode works automatically via prefers-color-scheme or by adding the .dark class to <html>. The data-theme attribute is required for theme variables to activate.