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-appcd my-appInstall 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/layoutYou 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/viteConfigure 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.tsxexport 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.tsimport 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.