React Styling Guide: CSS, CSS Modules, and Tailwind CSS

Learn React Styling Guide: CSS, CSS Modules, and Tailwind CSS with practical examples, explanations, best practices, and real-world coding techniques.

Introduction to Styling in React

Styling is an essential part of building React applications. It determines how components look, how layouts adapt to different screen sizes, and how users interact with the interface. React does not require a particular styling system, so developers can choose from standard CSS, CSS Modules, inline styles, CSS-in-JS libraries, utility-first frameworks, and component libraries.

The best approach depends on the application's size, design requirements, team conventions, and build setup. For many projects, standard CSS or CSS Modules provide a straightforward starting point, while utility-first frameworks can speed up development when consistent design tokens and reusable patterns are important.

Important distinction

React handles component rendering, while CSS handles presentation. You can use CSS classes in JSX just as you would in HTML, but React uses the className attribute instead of class.

Using External CSS Files

External CSS is one of the simplest ways to style a React component. Create a CSS file, define the required selectors, and import the file into your JavaScript or TypeScript module.

For example, create a component named Welcome.jsx and a stylesheet named Welcome.css.

/* Welcome.css */
.welcome-card {
  padding: 24px;
  border-radius: 12px;
  background-color: #f1f5f9;
  color: #0f172a;
}

.welcome-title {
  font-size: 1.5rem;
  font-weight: 700;
}
import './Welcome.css';

function Welcome() {
  return (
    <section className="welcome-card">
      <h2 className="welcome-title">Welcome to React</h2>
      <p>Build reusable user interfaces.</p>
    </section>
  );
}

export default Welcome;

Importing the stylesheet makes its rules available to the application through the configured build system. Standard CSS selectors are generally global, so a class name used in multiple components can share the same styling rules.

The className Attribute

In JSX, use className to assign CSS classes to an element. This is the React attribute corresponding to the HTML class attribute.

function ProfileCard() {
  return (
    <article className="profile-card">
      <h2 className="profile-card__title">Developer Profile</h2>
      <p className="profile-card__description">
        Software engineer and React developer.
      </p>
    </article>
  );
}

React translates these JSX attributes into the corresponding DOM properties. Use meaningful class names that describe the element or its role instead of relying on vague names that become difficult to understand in larger projects.

Inline Styles in React

React supports inline styles through the style prop, which accepts a JavaScript object. CSS properties use camelCase names, and most numeric values are interpreted as pixel values when the property expects a length.

function Alert() {
  const alertStyle = {
    backgroundColor: '#dcfce7',
    color: '#166534',
    padding: '16px',
    borderRadius: '8px',
    fontWeight: 600
  };

  return (
    <div style={alertStyle}>
      Your changes have been saved.
    </div>
  );
}

Inline styles are useful when values depend directly on component data. However, they do not support CSS selectors, pseudo-classes such as :hover, or media queries. Use a stylesheet when the design needs complex states, responsive rules, animations, or shared visual patterns.

Dynamic and Conditional Styling

A component may need different styles depending on its state or props. You can build a class name conditionally using a template literal or a simple expression.

import { useState } from 'react';
import './StatusButton.css';

function StatusButton() {
  const [active, setActive] = useState(false);

  return (
    <button
      type="button"
      className={active ? 'status-button active' : 'status-button'}
      onClick={() => setActive(current => !current)}
      aria-pressed={active}
    >
      {active ? 'Active' : 'Inactive'}
    </button>
  );
}
/* StatusButton.css */
.status-button {
  padding: 10px 16px;
  border: 1px solid #94a3b8;
  border-radius: 8px;
  background: white;
  color: #334155;
  cursor: pointer;
}

.status-button.active {
  border-color: #15803d;
  background: #dcfce7;
  color: #166534;
}

This approach keeps the style definitions in CSS and uses React state to decide which class should be applied. For components with several conditional classes, a small helper or a class-name utility can make the expression easier to maintain.

Using CSS Modules

CSS Modules scope class names to the module that imports them. This helps avoid accidental conflicts when different components use similar class names. Many React build tools, including common Vite configurations, support CSS Modules through filenames ending in .module.css.

/* ProductCard.module.css */
.card {
  padding: 20px;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
}

.title {
  margin-bottom: 8px;
  font-size: 1.25rem;
  font-weight: 700;
}
import styles from './ProductCard.module.css';

function ProductCard() {
  return (
    <article className={styles.card}>
      <h2 className={styles.title}>Mechanical Keyboard</h2>
      <p>A compact keyboard for everyday development.</p>
    </article>
  );
}

The imported object maps readable property names to generated class names. You can also define several classes and combine them conditionally. CSS Modules reduce global naming conflicts, but they do not automatically provide a complete design system or prevent every possible style interaction.

Combining CSS Module Classes

CSS Modules work with conditional rendering and component state. You can apply a base class and add a modifier class only when a condition is true.

import styles from './Button.module.css';

function Button({ disabled }: { disabled: boolean }) {
  const className = [
    styles.button,
    disabled ? styles.disabled : ''
  ].filter(Boolean).join(' ');

  return (
    <button className={className} disabled={disabled}>
      Continue
    </button>
  );
}

The filter(Boolean) call removes empty class entries before joining the remaining class names. For more complicated conditions, libraries such as clsx can simplify class composition, but they are optional dependencies rather than React requirements.

Responsive Design

Responsive styling helps components adapt to mobile phones, tablets, laptops, and larger screens. CSS media queries let you change layouts according to viewport characteristics.

.product-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

@media (min-width: 640px) {
  .product-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .product-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

Mobile-first CSS starts with styles for smaller screens and progressively introduces wider layouts. CSS Grid, Flexbox, relative units, and responsive images can help create interfaces that adapt without duplicating components for each device size.

Styling with CSS Variables

CSS custom properties, also called CSS variables, help centralize colors, spacing, typography, and other design values. They are useful for maintaining consistency across an application and supporting themes.

:root {
  --color-primary: #2563eb;
  --color-surface: #ffffff;
  --color-text: #0f172a;
  --space-card: 1.25rem;
  --radius-card: 0.75rem;
}

.product-card {
  padding: var(--space-card);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  color: var(--color-text);
}

.primary-button {
  background: var(--color-primary);
  color: white;
}

Updating a design token can change the appearance of many components at once. For theme-specific variables, define custom properties on a theme container or a suitable document element and override them for the active theme.

Using Tailwind CSS with React

Tailwind CSS is a utility-first CSS framework that provides small, composable classes for layout, spacing, typography, colors, and responsive behavior. Instead of writing a separate class for every component style, you compose utilities directly in JSX.

function ProductCard() {
  return (
    <article className="rounded-xl border border-slate-200 p-5 shadow-sm">
      <h2 className="mb-2 text-xl font-bold text-slate-900">
        Developer Workspace
      </h2>
      <p className="text-sm leading-6 text-slate-600">
        A clean environment for building applications.
      </p>
      <button className="mt-4 rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2">
        Explore
      </button>
    </article>
  );
}

Responsive prefixes such as sm:, md:, and lg: let you change utilities at different viewport widths. Make sure Tailwind is configured correctly for the installed version and that its source detection includes the files containing your class names.

Tailwind is optional. Standard CSS, CSS Modules, and other styling approaches remain valid choices for React projects.

CSS-in-JS and Styling Libraries

CSS-in-JS libraries allow developers to associate styles with JavaScript components. Depending on the library, styles may support theme tokens, dynamic props, generated class names, and component-level styling APIs.

Component libraries can also provide prebuilt interface elements, design tokens, and theme configuration. They are useful when a project needs a consistent collection of buttons, dialogs, form controls, and other common patterns.

Evaluate library maintenance, compatibility with your React version, server-rendering requirements, bundle size, accessibility behavior, and styling conventions before choosing a dependency. Avoid adding a styling library solely for a small amount of straightforward CSS.

Styling Pseudo-Classes and Focus States

CSS pseudo-classes allow styles to respond to interaction or element state. Common examples include :hover, :focus-visible, :disabled, and :checked.

.action-button {
  background: #2563eb;
  color: white;
  border: 2px solid transparent;
  border-radius: 8px;
  padding: 10px 16px;
}

.action-button:hover:not(:disabled) {
  background: #1d4ed8;
}

.action-button:focus-visible {
  outline: 3px solid #93c5fd;
  outline-offset: 3px;
}

.action-button:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

Do not remove keyboard focus indicators without providing a clear alternative. Use semantic HTML elements and native states where possible so that visual behavior remains consistent with the element's actual interaction behavior.

Organizing Styles in a React Project

A consistent styling structure helps developers find and update styles as an application grows. One option is to keep a component and its CSS Module close together.

src/
  components/
    ProductCard/
      ProductCard.tsx
      ProductCard.module.css
    Navigation/
      Navigation.tsx
      Navigation.module.css
  styles/
    globals.css
    tokens.css
  App.tsx
  main.tsx

Global styles can contain resets, document-level typography, and shared CSS variables. Component styles can contain layout and presentation specific to a component. The exact folder structure is flexible; choose one that supports clear ownership and easy navigation.

Common React Styling Mistakes

  • Using class instead of className: Use className in JSX.
  • Using kebab-case in inline style objects: Use JavaScript property names such as backgroundColor.
  • Mixing global selectors unintentionally: Consider CSS Modules or a naming convention to reduce collisions.
  • Putting complex styles in inline objects: Use stylesheets for media queries, pseudo-classes, and reusable rules.
  • Ignoring small screens: Test layouts at different viewport widths rather than assuming desktop dimensions.
  • Removing focus outlines: Preserve visible keyboard focus for interactive elements.
  • Using overly specific selectors: Prefer simple selectors that are easier to override and maintain.
  • Adding unnecessary dependencies: Choose a styling library only when its capabilities justify the additional complexity.
  • Using dynamic utility names that tooling cannot detect: Keep class names statically discoverable or configure the build system appropriately.

React Styling Best Practices

  • Choose a consistent styling approach that fits the project's needs.
  • Use descriptive class names and keep component styles organized.
  • Use CSS Modules when local class-name scoping is beneficial.
  • Keep shared design values in CSS custom properties or a well-defined design system.
  • Build mobile-first responsive layouts with Grid, Flexbox, and media queries.
  • Use React state or props for styles that genuinely depend on application state.
  • Prefer semantic HTML and preserve accessible focus indicators.
  • Check color contrast, readable typography, and reduced-motion preferences.
  • Test the interface across browsers, viewport sizes, and interactive states.
  • Avoid premature optimization and unnecessary styling dependencies.

Related React Topics

Code Examples

import './Welcome.css';

function Welcome() {
  return (
    <section className="welcome-card">
      <h2 className="welcome-title">Welcome to React</h2>
      <p>Build reusable user interfaces.</p>
    </section>
  );
}

export default Welcome;
    
function Alert() {
  const alertStyle = {
    backgroundColor: '#dcfce7',
    color: '#166534',
    padding: '16px',
    borderRadius: '8px',
    fontWeight: 600
  };

  return <div style={alertStyle}>Your changes have been saved.</div>;
}
    
import { useState } from 'react';

function StatusButton() {
  const [active, setActive] = useState(false);

  return (
    <button
      type="button"
      className={active ? 'status-button active' : 'status-button'}
      onClick={() => setActive(current => !current)}
      aria-pressed={active}
    >
      {active ? 'Active' : 'Inactive'}
    </button>
  );
}
    
import styles from './ProductCard.module.css';

function ProductCard() {
  return (
    <article className={styles.card}>
      <h2 className={styles.title}>Mechanical Keyboard</h2>
      <p>A compact keyboard for everyday development.</p>
    </article>
  );
}
    
import styles from './Button.module.css';

type ButtonProps = { disabled: boolean };

function Button({ disabled }: ButtonProps) {
  const className = [
    styles.button,
    disabled ? styles.disabled : ''
  ].filter(Boolean).join(' ');

  return (
    <button className={className} disabled={disabled}>
      Continue
    </button>
  );
}
    
.product-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

@media (min-width: 640px) {
  .product-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .product-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
    
:root {
  --color-primary: #2563eb;
  --color-surface: #ffffff;
  --color-text: #0f172a;
  --space-card: 1.25rem;
  --radius-card: 0.75rem;
}

.product-card {
  padding: var(--space-card);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  color: var(--color-text);
}
    
function ActionButton() {
  return (
    <button className="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2">
      Explore
    </button>
  );
}
    

Pro Tip

Choose a styling strategy deliberately: use standard CSS for straightforward shared rules, CSS Modules for component-scoped styles, and utility-first classes when they fit your design workflow. Keep responsive behavior and keyboard focus states part of the design from the beginning.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.