Next.js and Tailwind CSS are the most popular stack for modern React applications. Here's how to properly integrate design tokens into your Next.js project for a maintainable, scalable design system.
Project Structure for Design Tokens
A well-organized structure makes your design system easy to maintain:
your-nextjs-app/
├── src/
│ ├── app/
│ │ ├── globals.css # CSS variables defined here
│ │ └── layout.tsx # Import globals.css
│ ├── styles/
│ │ └── theme.css # Optional: separate theme file
│ └── lib/
│ └── theme.ts # TypeScript theme constants
├── tailwind.config.js # Tailwind configuration
└── package.jsonWhere to Put globals.css
In Next.js App Router, global styles go in src/app/globals.css and are imported in layout.tsx:
/* src/app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
/* Colors */
--color-background: #ffffff;
--color-foreground: #0f172a;
--color-primary: #3b82f6;
--color-primary-foreground: #ffffff;
--color-secondary: #64748b;
--color-secondary-foreground: #ffffff;
--color-muted: #f1f5f9;
--color-muted-foreground: #64748b;
--color-accent: #f59e0b;
--color-accent-foreground: #ffffff;
--color-border: #e2e8f0;
/* Typography */
--font-family: 'Inter', sans-serif;
--font-family-heading: 'Inter', sans-serif;
/* Spacing - use Tailwind's scale */
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
--spacing-6: 1.5rem;
--spacing-8: 2rem;
/* Border Radius */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
:root.dark {
--color-background: #0f172a;
--color-foreground: #f8fafc;
--color-primary: #60a5fa;
--color-muted: #1e293b;
--color-muted-foreground: #94a3b8;
--color-border: #334155;
}tailwind.config.js Setup
Extend Tailwind to use your CSS variables. This lets you use Tailwind classes that reference your tokens:
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: [
'./src/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
background: 'var(--color-background)',
foreground: 'var(--color-foreground)',
primary: {
DEFAULT: 'var(--color-primary)',
foreground: 'var(--color-primary-foreground)',
},
secondary: {
DEFAULT: 'var(--color-secondary)',
foreground: 'var(--color-secondary-foreground)',
},
muted: {
DEFAULT: 'var(--color-muted)',
foreground: 'var(--color-muted-foreground)',
},
accent: {
DEFAULT: 'var(--color-accent)',
foreground: 'var(--color-accent-foreground)',
},
border: 'var(--color-border)',
},
borderRadius: {
sm: 'var(--radius-sm)',
md: 'var(--radius-md)',
lg: 'var(--radius-lg)',
},
fontFamily: {
sans: ['var(--font-family)', 'sans-serif'],
heading: ['var(--font-family-heading)', 'sans-serif'],
},
boxShadow: {
sm: 'var(--shadow-sm)',
md: 'var(--shadow-md)',
lg: 'var(--shadow-lg)',
},
},
},
plugins: [],
}App Router vs Pages Router
The setup differs slightly between Next.js routing paradigms:
App Router (Recommended)
// src/app/layout.tsx
import './globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Pages Router
// src/pages/_app.tsx
import '../styles/globals.css';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}TypeScript Integration
Create a TypeScript file for type-safe access to your theme values when needed in JavaScript:
// src/lib/theme.ts
export const theme = {
colors: {
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)',
background: 'var(--color-background)',
foreground: 'var(--color-foreground)',
muted: 'var(--color-muted)',
accent: 'var(--color-accent)',
border: 'var(--color-border)',
},
radius: {
sm: 'var(--radius-sm)',
md: 'var(--radius-md)',
lg: 'var(--radius-lg)',
},
// Add more as needed
} as const;
export type ThemeColors = keyof typeof theme.colors;Use it in components that need programmatic access:
import { theme } from '@/lib/theme';
// In a component
<div style={{ borderColor: theme.colors.border }}>
Programmatic styling
</div>shadcn/ui Integration
shadcn/ui uses the exact same CSS variable pattern. If you're using shadcn, your globals.css replaces theirs:
/* shadcn/ui expects these variable names */
:root {
--background: 0 0% 100%; /* HSL format */
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
/* ... */
}
/* Or use hex with the naming convention PickCSS uses */
:root {
--color-background: #ffffff;
--color-foreground: #0f172a;
--color-primary: #3b82f6;
/* ... */
}PickCSS exports both formats—choose the one that matches your project.
Using PickCSS Output in Next.js
When you export from PickCSS, you get files ready to drop into your Next.js project:
pickcss-export/
├── theme.css # → src/app/globals.css (replace/merge)
├── globals.css # → shadcn/ui compatible version
├── tailwind.config.js # → merge with your tailwind.config.js
├── theme.ts # → src/lib/theme.ts
└── tokens.json # → for Figma/Storybook integrationStep-by-Step Integration
- Copy CSS variables from
theme.cssinto yourglobals.css - Merge Tailwind config - copy the
extendsection into your existing config - Optional: Add
theme.tsfor TypeScript access - Restart dev server to pick up Tailwind changes
Best Practices
Use CSS Variables in Tailwind Classes
{/* Good - uses your design tokens */}
<button className="bg-primary text-primary-foreground rounded-md">
Click me
</button>
{/* Also good - explicit variable reference */}
<button className="bg-[var(--color-primary)] rounded-[var(--radius-md)]">
Click me
</button>
{/* Avoid - hardcoded values */}
<button className="bg-blue-500 rounded-lg">
Click me
</button>Keep Variables in One Place
Define all variables in globals.css. Don't scatter them across component CSS files.
Use Semantic Names
--color-primary is better than --blue-500 because it describes purpose, not appearance.
Getting Started
Skip the manual setup. PickCSS generates all these files automatically based on your design preferences.
Create your Next.js design system in 5 minutes.