Skip to main content
ninna-ui

React Router Setup

Step-by-step guide to set up Ninna UI in a React Router v7 project with Tailwind CSS v4. Full-stack React framework with optional SSR.

Prerequisites

  • Node.js 18+
  • React Router v7
  • Tailwind CSS v4

Create Project

Skip this step if you already have a React Router project.

npx create-react-router@latest my-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

React Router uses Vite under the hood, so use the @tailwindcss/vite plugin.

pnpm add
pnpm add -D tailwindcss @tailwindcss/vite

Configure Vite

Add the Tailwind CSS plugin to your Vite config alongside the React Router plugin.

import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss(), reactRouter()],
});

CSS Setup

Replace the contents of your global CSS file.

In app/index.css (or equivalent global CSS):

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

Then add data-theme to the <html> element in your root layout:

// app/root.tsx
export default function App() {
return (
<html lang="en" data-theme="default">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="min-h-screen bg-base-50 text-base-content antialiased">
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

The theme preset automatically includes all utility classes used by Ninna UI components — no @source directive needed.

Your First Component

Verify the setup by rendering a Ninna UI component.

import { Button, Heading, Text } from "@ninna-ui/primitives";
export default function Home() {
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>
);
}

This site (ninna-ui.dev) is itself built with React Router v7 + Ninna UI!

SSR Notes

React Router v7 supports both SPA and SSR modes.

By default, React Router v7 runs in SPA mode (ssr: false in react-router.config.ts). Ninna UI works in both SPA and SSR modes without any additional configuration.

If you enable SSR (ssr: true), Ninna UI components hydrate correctly because they use standard React patterns — no global state, no window-only APIs at import time.

// react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
ssr: true, // Enable SSR — Ninna UI works with both modes
} satisfies Config;

Theme Presets

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

/* 1. Change the CSS import in index.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/root.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.