Tailwind + Next.js cheatsheet.
Setup (already in create-next-app)
npx create-next-app@latest myapp --tailwind
Or manually:
npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.ts:
import type { Config } from "tailwindcss";
export default {
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
darkMode: "class",
theme: {
extend: {
colors: {
brand: { 500: "#3b82f6" },
},
fontFamily: {
sans: ["var(--font-sans)"],
},
},
},
plugins: [],
} satisfies Config;
app/globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
Import in root layout:
import "./globals.css";
cn() helper
npm i clsx tailwind-merge
// lib/cn.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
<button className={cn("px-4 py-2", isActive && "bg-blue-500", className)} />
twMerge resolves conflicts (later wins): cn("p-4", "p-8") → "p-8".
Dark mode
// dark variant uses class strategy
<html className="dark">
.bg-white { background: white; }
.dark .bg-white { background: #111; } /* auto-generated */
Tailwind classes:
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
Toggling dark mode
"use client";
import { useEffect, useState } from "react";
function ThemeToggle() {
const [dark, setDark] = useState(false);
useEffect(() => {
document.documentElement.classList.toggle("dark", dark);
localStorage.setItem("theme", dark ? "dark" : "light");
}, [dark]);
return <button onClick={() => setDark(!dark)}>{dark ? "☀" : "🌙"}</button>;
}
Or use next-themes:
npm i next-themes
// app/providers.tsx
"use client";
import { ThemeProvider } from "next-themes";
export function Providers({ children }) {
return <ThemeProvider attribute="class">{children}</ThemeProvider>;
}
// app/layout.tsx
<html suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
import { useTheme } from "next-themes";
const { theme, setTheme } = useTheme();
Font variables in Tailwind
// app/layout.tsx
import { Inter, JetBrains_Mono } from "next/font/google";
const sans = Inter({ subsets: ["latin"], variable: "--font-sans" });
const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-mono" });
<html className={`${sans.variable} ${mono.variable}`}>
// tailwind.config.ts
theme: {
extend: {
fontFamily: {
sans: ["var(--font-sans)"],
mono: ["var(--font-mono)"],
},
},
},
Component variants (cva)
npm i class-variance-authority
import { cva, type VariantProps } from "class-variance-authority";
const button = cva(
"rounded font-medium",
{
variants: {
variant: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
ghost: "bg-transparent hover:bg-gray-100",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4",
lg: "h-12 px-6 text-lg",
},
},
defaultVariants: { variant: "primary", size: "md" },
}
);
type Props = React.ComponentProps<"button"> & VariantProps<typeof button>;
export function Button({ variant, size, className, ...rest }: Props) {
return <button className={cn(button({ variant, size }), className)} {...rest} />;
}
<Button variant="ghost" size="sm">Click</Button>
shadcn/ui
npx shadcn@latest init
npx shadcn@latest add button card dialog
Copies components into your repo (you own the code). Built on Radix UI + Tailwind + cva.
import { Button } from "@/components/ui/button";
<Button variant="outline">Click</Button>
prettier-plugin-tailwindcss
npm i -D prettier prettier-plugin-tailwindcss
// .prettierrc
{
"plugins": ["prettier-plugin-tailwindcss"]
}
Auto-sorts Tailwind classes in correct order.
@apply (sparingly)
@layer components {
.btn {
@apply inline-flex items-center px-4 py-2 rounded;
}
}
Use only for repeated patterns; not as a replacement for components.
Arbitrary values
<div className="w-[420px] grid-cols-[200px_1fr_auto] text-[#ff0]" />
Typography plugin
npm i -D @tailwindcss/typography
plugins: [require("@tailwindcss/typography")],
<article className="prose dark:prose-invert max-w-none">
{markdown}
</article>
Beautiful prose styling for generated/markdown content.
Common mistakes
- Missing
contentpaths → unused styles. classes not generated → no styles. - Hydration mismatch when toggling dark mode without
suppressHydrationWarning. - Inline styles for theme — Tailwind is better at it.
- Composing classes by string concat without
twMerge— duplicate Tailwind. - Using
@applyeverywhere → defeats the whole utility-first approach.
Read this next
If you want my Tailwind + shadcn setup, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .