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 content paths → 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 @apply everywhere → 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 .