Tailwind CSS Tips for Claude Code: A Practical Guide to Stable UI
Practical Tailwind CSS tips for Claude Code: tokens, responsive UI, dark mode, components, safelists, and visual checks.
Tailwind CSS is powerful because it lets you build a UI with small utility classes like p-4, grid, text-sm, and rounded-lg. Claude Code makes that even faster because it can inspect your repository, edit files, run checks, and iterate on a feature. The risk is that fast UI work can also create long className strings, inconsistent colors, mobile-only layout bugs, and dark mode states nobody reviewed.
This guide is a practical beginner-friendly workflow for using Claude Code with Tailwind CSS. It covers design tokens, responsive utilities, component extraction, avoiding class soup, dark mode, forms, buttons, cards, safelists, content scanning, and visual checks. The examples are written for React and Tailwind CSS, but the review process works for Astro, Next.js, Remix, Vite, or any stack where Tailwind classes live in source files.
The guidance follows the current official docs for Tailwind theme variables, responsive design, dark mode, detecting classes in source files, and adding custom styles. For typed React examples, see the React TypeScript guide. For Claude Code basics, use the Claude Code documentation. For visual regression checks, the relevant Playwright page is Visual comparisons.
If your prompts are still vague, read 5 tips for better prompts first. If you are improving a full mobile app flow, connect this article with the PWA implementation guide.
Start with an Audit, Not a Rewrite
Claude Code can build features and fix UI bugs, but it cannot infer every brand rule or conversion goal unless you tell it. Before asking it to “make the page look better,” ask it to audit the Tailwind surface area.
Inspect this repository's Tailwind CSS usage. Do not edit files yet.
Report the following:
- Existing design tokens for color, spacing, radius, shadow, and typography
- React components with very long className values
- Layouts that may break at 375px, 768px, or 1440px
- Light/dark mode coverage gaps
- Repeated form, button, card, and badge styles
- Dynamic Tailwind classes that may not be detected in production CSS
- Existing Playwright, Storybook, or browser-based visual checks
This first pass prevents a common failure: Claude Code polishes one section while accidentally weakening a CTA, form, ad slot, or code block elsewhere. On ClaudeCodeLab, the most painful Tailwind regressions usually came from changing the header or card spacing without checking article-end CTAs on mobile.
Put Design Tokens in One Place
Design tokens are named values for low-level design decisions: colors, spacing, shadows, border radius, fonts, and breakpoints. Tailwind CSS v4 uses CSS-first @theme variables, which create utilities such as bg-brand-600 or rounded-card. Older projects may still use tailwind.config.ts; the principle is the same, but the current docs prefer theme variables.
/* src/styles/app.css */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--font-sans: Inter, system-ui, sans-serif;
--color-brand-50: #eef6ff;
--color-brand-100: #d9ebff;
--color-brand-600: #2563eb;
--color-brand-700: #1d4ed8;
--color-ink: #111827;
--color-muted: #6b7280;
--color-surface: #ffffff;
--color-surface-soft: #f8fafc;
--color-danger: #dc2626;
--radius-card: 0.75rem;
--shadow-card: 0 16px 40px rgb(15 23 42 / 0.08);
--breakpoint-3xl: 112rem;
}
When you prompt Claude Code, say: “Use existing brand tokens. Add new @theme variables only when the design needs a reusable value.” That is much safer than asking for “a nicer blue,” which often produces a mix of blue, sky, and indigo utilities across the same product.
Use Responsive Utilities Intentionally
Tailwind responsive utilities are mobile-first: the base class applies to small screens, then sm:, md:, lg:, and larger variants add changes for wider screens. Do not start with a desktop grid and patch mobile after it breaks.
Improve the product grid with Tailwind CSS.
Requirements:
- 1 column at 375px, 2 columns at 640px, 3 columns at 1024px
- Product images stay square
- Card heights are consistent
- The CTA button stays aligned at the bottom of each card
- Keep the existing Product props type
- Check mobile and desktop screenshots after editing
type Product = {
id: string;
name: string;
price: number;
imageUrl: string;
badge?: string;
};
export function ProductGrid({ products }: { products: Product[] }) {
return (
<section className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</section>
);
}
function ProductCard({ product }: { product: Product }) {
return (
<article className="flex h-full flex-col overflow-hidden rounded-card border border-slate-200 bg-surface shadow-card dark:border-slate-800 dark:bg-slate-950">
<div className="relative aspect-square overflow-hidden bg-surface-soft">
<img
src={product.imageUrl}
alt={product.name}
className="h-full w-full object-cover transition duration-200 hover:scale-105"
/>
{product.badge ? (
<span className="absolute left-3 top-3 rounded-full bg-brand-600 px-3 py-1 text-xs font-semibold text-white">
{product.badge}
</span>
) : null}
</div>
<div className="flex flex-1 flex-col p-4">
<h3 className="line-clamp-2 text-base font-semibold text-ink dark:text-white">
{product.name}
</h3>
<p className="mt-2 text-sm text-muted dark:text-slate-400">
${product.price.toLocaleString("en-US")}
</p>
<button className="mt-auto rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-600 focus:ring-offset-2 dark:focus:ring-offset-slate-950">
View details
</button>
</div>
</article>
);
}
The important utilities here are aspect-square for image stability, flex h-full flex-col for equal card behavior, and mt-auto for button alignment.
Extract Components Before Class Soup Spreads
Class soup is the point where a className becomes so long that nobody can tell which classes are base styling, which are states, and which are one-off overrides. Tailwind works best when utility classes stay close to the markup, but repeated buttons and cards deserve a small component.
import type { ButtonHTMLAttributes, ReactNode } from "react";
type ButtonVariant = "primary" | "secondary" | "danger";
const buttonVariants: Record<ButtonVariant, string> = {
primary: "bg-brand-600 text-white hover:bg-brand-700 focus:ring-brand-600",
secondary:
"border border-slate-300 bg-white text-slate-900 hover:bg-slate-50 focus:ring-slate-400 dark:border-slate-700 dark:bg-slate-900 dark:text-white",
danger: "bg-danger text-white hover:bg-red-700 focus:ring-danger",
};
function cn(...classes: Array<string | false | null | undefined>) {
return classes.filter(Boolean).join(" ");
}
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: ButtonVariant;
loading?: boolean;
children: ReactNode;
};
export function Button({
variant = "primary",
loading = false,
disabled,
className,
children,
...props
}: ButtonProps) {
return (
<button
className={cn(
"inline-flex min-h-10 items-center justify-center rounded-lg px-4 py-2 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:focus:ring-offset-slate-950",
buttonVariants[variant],
className,
)}
disabled={disabled || loading}
{...props}
>
{loading ? "Working..." : children}
</button>
);
}
Ask Claude Code to keep classes as complete strings. Avoid bg-${color}-600, because Tailwind may not see that class during scanning.
Treat Forms, Buttons, and Cards as Revenue UI
Forms and CTAs are not decoration. They are where users sign up, ask for help, download templates, or buy a product. Claude Code should review success, error, disabled, focus, hover, light mode, and dark mode states.
"use client";
import type { FormEvent } from "react";
type LeadFormProps = {
onSubmit: (values: { email: string; message: string }) => void;
error?: string;
};
export function LeadForm({ onSubmit, error }: LeadFormProps) {
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
onSubmit({
email: String(formData.get("email") ?? ""),
message: String(formData.get("message") ?? ""),
});
}
return (
<form
onSubmit={handleSubmit}
className="space-y-4 rounded-card border border-slate-200 bg-white p-5 shadow-card dark:border-slate-800 dark:bg-slate-950"
>
<label className="block text-sm font-medium text-slate-900 dark:text-white">
Email
<input
name="email"
type="email"
required
aria-describedby={error ? "lead-form-error" : undefined}
className="mt-1 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-brand-600 focus:ring-2 focus:ring-brand-600/20 dark:border-slate-700 dark:bg-slate-900 dark:text-white"
placeholder="you@example.com"
/>
</label>
<label className="block text-sm font-medium text-slate-900 dark:text-white">
What do you want to improve?
<textarea
name="message"
rows={4}
className="mt-1 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-brand-600 focus:ring-2 focus:ring-brand-600/20 dark:border-slate-700 dark:bg-slate-900 dark:text-white"
placeholder="Tailwind UI cleanup, Claude Code rollout, review workflow..."
/>
</label>
{error ? (
<p id="lead-form-error" className="text-sm font-medium text-danger">
{error}
</p>
) : null}
<button className="w-full rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-600 focus:ring-offset-2 dark:focus:ring-offset-slate-950">
Request consultation
</button>
</form>
);
}
For accessibility review, connect this workflow with the Claude Code accessibility guide.
Add Dark Mode Early
Dark mode is not just dark:bg-slate-900. Text, borders, shadows, form controls, focus rings, status colors, and image overlays all need review. Decide whether your project follows the operating system setting or a manual .dark class.
"use client";
import { useEffect, useState } from "react";
type Theme = "light" | "dark";
export function ThemeToggle() {
const [theme, setTheme] = useState<Theme>("light");
useEffect(() => {
document.documentElement.classList.toggle("dark", theme === "dark");
document.documentElement.style.colorScheme = theme;
}, [theme]);
return (
<button
type="button"
onClick={() => setTheme((current) => (current === "dark" ? "light" : "dark"))}
className="rounded-lg border border-slate-300 px-3 py-2 text-sm font-semibold text-slate-900 transition hover:bg-slate-50 dark:border-slate-700 dark:text-white dark:hover:bg-slate-900"
>
{theme === "dark" ? "Light mode" : "Dark mode"}
</button>
);
}
Ask Claude Code to verify the full screen in both themes, not just the component it edited.
Prevent Safelist and Content Scanning Bugs
Tailwind generates CSS from the classes it detects in source files. If a class is assembled dynamically, production CSS may miss it. Prefer static maps.
type Status = "success" | "warning" | "danger";
const statusClasses: Record<Status, string> = {
success: "bg-emerald-50 text-emerald-700 ring-emerald-600/20",
warning: "bg-amber-50 text-amber-800 ring-amber-600/20",
danger: "bg-red-50 text-red-700 ring-red-600/20",
};
export function StatusBadge({ status, label }: { status: Status; label: string }) {
return (
<span
className={`inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold ring-1 ring-inset ${statusClasses[status]}`}
>
{label}
</span>
);
}
If classes live in an external package or CMS-rendered source, use @source or @source inline() sparingly.
@import "tailwindcss";
@source "../node_modules/@acme/ui-kit";
@source inline("bg-emerald-50");
@source inline("text-emerald-700");
@source inline("bg-amber-50");
@source inline("text-amber-800");
Verify the Visual Result
Types and builds do not catch visual regressions. Use a browser pass or Playwright screenshots at the widths that matter.
import { expect, test } from "@playwright/test";
const viewports = [
{ name: "mobile", size: { width: 375, height: 812 } },
{ name: "tablet", size: { width: 768, height: 1024 } },
{ name: "desktop", size: { width: 1440, height: 960 } },
];
for (const viewport of viewports) {
test(`pricing page visual check - ${viewport.name}`, async ({ page }) => {
await page.setViewportSize(viewport.size);
await page.goto("/pricing");
await expect(page.getByRole("main")).toHaveScreenshot(
`pricing-${viewport.name}.png`,
{ maxDiffPixelRatio: 0.01 },
);
});
}
Ask Claude Code to list changed files, risks, removed assumptions, viewport checks, dark mode checks, and manual fallback steps when Playwright is not available.
Practical Use Cases
| Use case | What to ask Claude Code | Tailwind focus |
|---|---|---|
| Landing page | Review hero, CTA, pricing, and testimonials together | Spacing, heading scale, CTA visibility |
| SaaS dashboard | Organize tables, filters, sidebars, and empty states | Density, overflow, sticky regions |
| Lead form | Cover input, error, success, loading, and disabled states | Focus rings, labels, mobile tap targets |
| Content site | Check article body, code blocks, ads, and CTAs together | Reading width, code scroll, link hierarchy |
Extract components when the same button or card appears in three or more places. Keep one-off layout details local.
Common Pitfalls
- Building dynamic class names like
bg-${color}-600. - Reviewing only desktop and forgetting 375px mobile width.
- Adding dark backgrounds but forgetting text, borders, and focus rings.
- Hiding every repeated style behind
@applyand losing local readability. - Creating several near-identical blues, grays, shadows, and radii.
- Shipping a form after checking only the happy path.
- Skipping screenshots and missing a CTA or ad overlap.
Monetization CTA
Tailwind improvements should support revenue paths. For a content site, check the article-end CTA. For a template product, check the pricing card. For a team service, check the consultation form. ClaudeCodeLab offers a free Claude Code cheatsheet, practical products and templates, and training or consultation for teams that want a repeatable Claude Code workflow.
Summary
Use Claude Code to audit first, then implement. Define tokens, write mobile-first layouts, extract repeated components, keep classes static enough for Tailwind scanning, add dark mode early, and verify with screenshots. In my tests on ClaudeCodeLab pages, the biggest gains came from the audit prompt and the 375px screenshot pass. They caught CTA spacing, form focus, and dark mode contrast issues before the change became another cleanup task.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Safety Ladder: Expand Access Without Losing Control
A beginner-friendly ladder for moving Claude Code from read-only to limited edits, proof commands, and deploy checks.
Claude Code Small PR Proof Pack: Make Tiny Changes Reviewable
A practical proof pack for Claude Code PRs: diff, checks, public URL, CTA path, and rollback note.
Claude Code Review Gate Before Commit: Diff, Tests, Public URL, and CTA Checks
A commit-time review gate for Claude Code work: diff scope, build, public URL, revenue CTA links, missing tests, and unrelated files.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.