Styling React with Tailwind CSS

Tailwind CSS is a utility-first framework: instead of writing CSS in separate files, you compose small single-purpose classes directly in your JSX className . flex , p-4 , text-lg , hover:bg-blue-600 — each does one thing. You build whole interfaces without leaving your component, with built-in responsive design, dark mode, and a consistent design scale. This whole site is styled with Tailwind. By the end you'll read and write utility classes fluently and compose them conditionally in React.

Learn Styling React with Tailwind CSS in our free React course — a beginner-friendly interactive lesson with runnable examples, a practice exercise and a…

Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

1️⃣ Utility-First Basics

Each class maps to a tiny slice of CSS. Layout ( flex , grid ), spacing ( p-4 = padding, gap-2 ), color ( bg-blue-500 , text-white ), and so on. Here's a card built entirely from utilities:

Read it like a sentence: a flex row, items centered, gap 4, padding 6, extra-large rounded corners, white background, medium shadow. The demo models how variant classes switch on and off with state:

2️⃣ Variants: hover, focus, dark

Prefix a class with a variant to apply it conditionally. This is Tailwind's superpower over inline styles — you can express interaction states and themes right in the markup.

3️⃣ Conditional Classes in React

In React you constantly toggle styles based on state — active tab, error field, selected item. The clean pattern is a helper like clsx (many projects wrap it as cn() , which also merges conflicting Tailwind classes):

Build your own tiny clsx to see exactly how falsy values get dropped. Fill in the blanks:

4️⃣ Responsive Design

Tailwind is mobile-first: unprefixed classes apply at all sizes, and a prefix like md: applies from that breakpoint up . So you style the small screen first, then layer overrides for bigger screens.

The demo models how the "largest matching breakpoint wins" so you can see how many columns render at each screen width:

5️⃣ Don't Repeat Classes — Extract a Component

If you find the same long class string in five places, that's a signal: extract a component, not a CSS class. The React way to "reuse styles" is to reuse the component that owns them.

These lines of a responsive card grid are scrambled. Put them in the correct order:

Apply display: flex from the md breakpoint (≥768px) upward. Below that, the element isn't flex. Tailwind is mobile-first, so prefixes mean "this size and bigger."

2) Why is hover:bg-blue-600 impossible with inline style= ?

Inline styles can't express pseudo-states like :hover , media queries, or dark mode. Variants compile to real CSS rules, which is exactly what inline styles lack.

3) You're repeating the same 8 classes in 5 components. What's the fix?

Extract a component (e.g. ) that owns those classes. In React you reuse the component, not a CSS class. @apply is a last resort.

📋 Quick Reference

A status badge picks its color classes from its status. Finish badgeClasses so each status returns the right utility string — the engine behind every colored pill in a dashboard.

Practice quiz

What is Tailwind CSS's core approach?

  • A component library of pre-built widgets
  • A CSS-in-JS runtime
  • Utility-first: compose small single-purpose classes directly in className
  • A preprocessor like Sass

Answer: Utility-first: compose small single-purpose classes directly in className. Tailwind is utility-first. You build interfaces by snapping together small classes (flex, p-4, text-lg) right in your JSX.

What does the prefix in hover:bg-blue-600 do?

  • Applies bg-blue-600 only on hover
  • Applies bg-blue-600 always
  • Disables the background on hover
  • Applies it only in dark mode

Answer: Applies bg-blue-600 only on hover. hover: is a variant modifier. It gates the utility so bg-blue-600 applies only when the element is hovered.

In Tailwind's mobile-first model, what does md:flex mean?

  • Apply flex only on screens smaller than md
  • Apply flex only at exactly 768px
  • Apply flex on hover
  • Apply flex from the md breakpoint (>=768px) and up

Answer: Apply flex from the md breakpoint (>=768px) and up. Tailwind is mobile-first: a prefix like md: means 'this size and larger.' Unprefixed classes apply at all sizes.

How do variant stacks like md:hover:underline read?

  • Underline always, ignoring md and hover
  • On hover, at the md breakpoint and above
  • Only on the md breakpoint, never on hover
  • Underline on focus only

Answer: On hover, at the md breakpoint and above. Read right-to-left: the utility is underline, gated by hover, gated by md. Variants stack to express responsive, stateful styling.

Why won't a class built dynamically like work?

  • The scanner only generates CSS for class names it can see as complete static strings
  • Tailwind doesn't support blue
  • Template literals are banned in JSX
  • It works fine

Answer: The scanner only generates CSS for class names it can see as complete static strings. Tailwind scans source for literal class names and generates only those. A runtime-built string is invisible to the scanner, so use full static strings or a lookup map.

How does Tailwind keep the CSS bundle small despite thousands of utilities?

  • It compresses CSS with gzip only
  • It ships every utility and lets the browser cache them
  • It scans your files and generates CSS only for the classes you actually use
  • It inlines all styles

Answer: It scans your files and generates CSS only for the classes you actually use. Unused utilities never reach the bundle. Tailwind generates CSS for exactly the class names it finds in your source.

You're repeating the same 8 classes across 5 components. What's the idiomatic React fix?

  • Copy and paste them everywhere
  • Extract a component (e.g. a <Button>) that owns those classes
  • Always reach for @apply first
  • Switch to inline styles

Answer: Extract a component (e.g. a <Button>) that owns those classes. In React you reuse the component that owns the styles, not a CSS class. @apply is a last resort for truly repeated patterns.

Why is hover:bg-blue-600 impossible with an inline style= attribute?

  • Inline styles are slower
  • React blocks inline hover
  • It actually works with inline styles
  • Inline styles can't express pseudo-states, media queries, or dark mode

Answer: Inline styles can't express pseudo-states, media queries, or dark mode. Variants compile to real CSS rules. Inline styles apply to one element and can't represent :hover, breakpoints, or dark mode.

What does a helper like clsx (often wrapped as cn) do for class names?

  • Validates class names against the Tailwind config
  • Composes class strings cleanly, dropping falsy values, and cn additionally merges conflicting utilities
  • Minifies the final CSS
  • Converts classes to inline styles

Answer: Composes class strings cleanly, dropping falsy values, and cn additionally merges conflicting utilities. clsx joins truthy class names and skips falsy ones. Many projects' cn() also uses tailwind-merge to resolve conflicting utilities predictably.

Your dark: variant does nothing. What's the likely cause?

  • dark: was removed from Tailwind
  • You need to use inline styles
  • Dark mode isn't enabled (no dark class on a parent, or the media strategy isn't set)
  • The class name is misspelled as darkmode:

Answer: Dark mode isn't enabled (no dark class on a parent, or the media strategy isn't set). The dark: variant needs dark mode active — either a dark class on a parent element (class strategy) or the media strategy configured.