Skip to main content

Overview

React offers multiple ways to style your components, each with its own advantages and use cases. This section covers the main styling approaches used in modern React applications.

Dynamic Styling

Apply styles conditionally based on component state

CSS Modules

Scoped CSS that prevents style conflicts

Styled Components

CSS-in-JS with dynamic prop-based styling

Tailwind CSS

Utility-first CSS framework for rapid development

Inline Styles

Inline styles are JavaScript objects applied directly to elements. They’re useful for highly dynamic styles.
import { useState } from 'react';

export default function AuthInputs() {
  const [enteredEmail, setEnteredEmail] = useState('');
  const [submitted, setSubmitted] = useState(false);

  const emailNotValid = submitted && !enteredEmail.includes('@');

  return (
    <input
      type="email"
      style={{
        backgroundColor: emailNotValid ? '#fed2d2' : '#d1d5db'
      }}
      onChange={(event) => setEnteredEmail(event.target.value)}
    />
  );
}
Inline styles use camelCase property names (e.g., backgroundColor instead of background-color) and values must be strings or numbers.

Pros and Cons

  • Direct access to component state and props
  • No CSS file needed
  • Styles are scoped to the component
  • Great for highly dynamic values
  • No pseudo-selectors (:hover, :focus, etc.)
  • No media queries
  • Can become verbose for complex styles
  • Inline styles have high specificity

Dynamic CSS Classes

Conditionally apply CSS classes for more maintainable styling.
import { useState } from 'react';

export default function AuthInputs() {
  const [enteredEmail, setEnteredEmail] = useState('');
  const [submitted, setSubmitted] = useState(false);

  const emailNotValid = submitted && !enteredEmail.includes('@');

  return (
    <div>
      <label className={`label ${emailNotValid ? 'invalid' : ''}`}>
        Email
      </label>
      <input
        type="email"
        className={emailNotValid ? 'invalid' : undefined}
        onChange={(event) => setEnteredEmail(event.target.value)}
      />
    </div>
  );
}
Use template literals to combine multiple class names dynamically. For more complex scenarios, consider the classnames or clsx libraries.

CSS Modules

CSS Modules automatically scope styles to components, preventing conflicts.
1

Create a CSS Module

Name your file with .module.css extension (e.g., Header.module.css)
2

Import the module

Import styles as an object in your component
3

Apply scoped classes

Use the imported object to apply classes
import logo from '../assets/logo.png';
import classes from './Header.module.css';

export default function Header() {
  return (
    <header>
      <img src={logo} alt="A canvas" />
      <h1>ReactArt</h1>
      <p className={classes.paragraph}>
        A community of artists and art-lovers.
      </p>
    </header>
  );
}
CSS Modules work out-of-the-box with Vite and Create React App. Element selectors (like header, h1) remain global, while class selectors get scoped.

Styled Components

Styled-components is a popular CSS-in-JS library that allows you to write CSS directly in your JavaScript files.

Installation

npm install styled-components

Basic Usage

import { styled } from 'styled-components';

const Button = styled.button`
  padding: 1rem 2rem;
  font-weight: 600;
  text-transform: uppercase;
  border-radius: 0.25rem;
  color: #1f2937;
  background-color: #f0b322;
  border: none;

  &:hover {
    background-color: #f0920e;
  }
`;

export default Button;

Dynamic Styling with Props

import { styled } from 'styled-components';

const Label = styled.label`
  display: block;
  margin-bottom: 0.5rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: ${({ $invalid }) => ($invalid ? '#f87171' : '#6b7280')};
`;

const Input = styled.input`
  width: 100%;
  padding: 0.75rem 1rem;
  line-height: 1.5;
  background-color: ${({ $invalid }) => ($invalid ? '#fed2d2' : '#d1d5db')};
  color: ${({ $invalid }) => ($invalid ? '#ef4444' : '#374151')};
  border: 1px solid ${({ $invalid }) => ($invalid ? '#f73f3f' : 'transparent')};
  border-radius: 0.25rem;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
`;

export default function CustomInput({ label, invalid, ...props }) {
  return (
    <p>
      <Label $invalid={invalid}>{label}</Label>
      <Input $invalid={invalid} {...props} />
    </p>
  );
}
Use the $ prefix for props that shouldn’t be passed to the DOM (like $invalid). This prevents React warnings about unknown DOM attributes.

Nested Styles and Media Queries

import { styled } from 'styled-components';
import logo from '../assets/logo.png';

const StyledHeader = styled.header`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-top: 2rem;
  margin-bottom: 2rem;

  & img {
    object-fit: contain;
    margin-bottom: 2rem;
    width: 11rem;
    height: 11rem;
  }
  
  & h1 {
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: 0.4em;
    text-align: center;
    text-transform: uppercase;
    color: #9a3412;
    font-family: 'Pacifico', cursive;
    margin: 0;
  }
  
  & p {
    text-align: center;
    color: #a39191;
    margin: 0;
  }
  
  @media (min-width: 768px) {
    margin-bottom: 4rem;
  
    & h1 {
      font-size: 2.25rem;
    }
  }
`;

export default function Header() {
  return (
    <StyledHeader>
      <img src={logo} alt="A canvas" />
      <h1>ReactArt</h1>
      <p>A community of artists and art-lovers.</p>
    </StyledHeader>
  );
}
Styled-components supports all CSS features including pseudo-selectors, media queries, animations, and nested selectors using the & symbol.

Tailwind CSS

Tailwind CSS is a utility-first CSS framework that provides pre-built classes for rapid UI development.

Setup with Vite

1

Install Tailwind

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
2

Configure template paths

tailwind.config.js
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
3

Add Tailwind directives

index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

Usage Example

import { useState } from 'react';
import Button from './Button.jsx';
import Input from './Input.jsx';

export default function AuthInputs() {
  const [enteredEmail, setEnteredEmail] = useState('');
  const [enteredPassword, setEnteredPassword] = useState('');
  const [submitted, setSubmitted] = useState(false);

  const emailNotValid = submitted && !enteredEmail.includes('@');
  const passwordNotValid = submitted && enteredPassword.trim().length < 6;

  function handleLogin() {
    setSubmitted(true);
  }

  return (
    <div
      id="auth-inputs"
      className="w-full max-w-sm p-8 mx-auto rounded shadow-md bg-gradient-to-b from-stone-700 to-stone-800"
    >
      <div className="flex flex-col gap-2 mb-6">
        <Input
          label="Email"
          invalid={emailNotValid}
          type="email"
          onChange={(event) => setEnteredEmail(event.target.value)}
        />
        <Input
          invalid={passwordNotValid}
          label="Password"
          type="password"
          onChange={(event) => setEnteredPassword(event.target.value)}
        />
      </div>
      <div className="flex justify-end gap-4">
        <button type="button" className="text-amber-400 hover:text-amber-500">
          Create a new account
        </button>
        <Button onClick={handleLogin}>Sign In</Button>
      </div>
    </div>
  );
}
Tailwind’s utility classes make it easy to build responsive, dynamic UIs without writing custom CSS. Classes like hover:, focus:, and responsive prefixes (sm:, md:, lg:) enable complex styling with minimal code.

Choosing a Styling Approach

Inline Styles

Best for: Highly dynamic values, one-off stylesAvoid when: You need pseudo-selectors or media queries

CSS Modules

Best for: Traditional CSS workflow, component-scoped stylesAvoid when: You need highly dynamic prop-based styling

Styled Components

Best for: Component libraries, dynamic theming, full CSS features in JSAvoid when: You prefer separation of concerns or have performance constraints

Tailwind CSS

Best for: Rapid prototyping, consistent design systems, utility-first approachAvoid when: You prefer semantic class names or have a custom design system

Best Practices

  • Choose one primary styling approach per project
  • Use inline styles sparingly, only for truly dynamic values
  • Create reusable styled components for common patterns
  • CSS Modules and regular CSS have the best performance
  • Styled-components and CSS-in-JS add runtime overhead
  • Tailwind generates optimized, purged CSS in production
  • Document your team’s chosen approach
  • Use linting tools to enforce styling conventions
  • Consider developer experience and team familiarity

Refs & Portals

Learn about DOM manipulation and portals

Animations

Add smooth transitions and animations

Build docs developers (and LLMs) love