Skip to main content

🎨 Styling in React

You've built functional components - now let's make them beautiful! React offers many ways to style your components, from traditional CSS files to modern CSS-in-JS solutions. Each approach has its strengths, and choosing the right one can make your development faster and your apps more maintainable. In this lesson, we'll explore all the major styling approaches so you can pick the best tool for each job. Let's paint this canvas! πŸ–ŒοΈ

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Apply traditional CSS stylesheets to React components
  • Use CSS Modules for scoped, component-specific styles
  • Write inline styles with JavaScript objects
  • Implement dynamic and conditional styling
  • Understand CSS-in-JS solutions and their benefits
  • Use Tailwind CSS for utility-first styling
  • Choose the right styling approach for your project
  • Apply responsive design principles in React

Estimated Time: 70-85 minutes

Project: Style a complete dashboard interface using multiple styling techniques

In This Lesson

πŸ—ΊοΈ Styling Approaches Overview

React doesn't enforce a specific styling approach - you have options! Let's understand the landscape before diving into each method.

πŸ“– Key Concept

Unlike a traditional website β€” where the markup lives in HTML and the styles live in a separate stylesheet across the project β€” a React component can keep its markup, its logic, and its styles together. This "component-based styling" is a genuine shift in how we organize UI: styles travel with the component that owns them, so a button and its look move, copy, and delete as a single self-contained unit.

The Styling Spectrum

React styling approaches range from traditional to cutting-edge:

Approach Learning Curve Performance Best For
Plain CSS Easy (you know it!) Excellent Simple projects, global styles
CSS Modules Easy Excellent Component-scoped styles
Inline Styles Easy Good Dynamic styles, simple components
Styled Components Moderate Good CSS-in-JS, dynamic theming
Tailwind CSS Moderate Excellent Rapid development, utility-first
Emotion/JSS Moderate-Hard Good Advanced CSS-in-JS needs
graph TD A[React Styling] --> B[Traditional CSS] A --> C[Modern Approaches] B --> D[Plain CSS Files] B --> E[CSS Modules] C --> F[Inline Styles] C --> G[CSS-in-JS] C --> H[Utility-First] G --> I[Styled Components] G --> J[Emotion] H --> K[Tailwind CSS] style A fill:#667eea,stroke:#333,stroke-width:2px,color:#fff style B fill:#4CAF50,stroke:#333,stroke-width:2px style C fill:#2196F3,stroke:#333,stroke-width:2px

What We'll Cover

This lesson focuses on the most practical, widely-used approaches:

  • Traditional CSS - The foundation everyone knows
  • CSS Modules - Scoped styles without conflicts
  • Inline Styles - JavaScript-powered dynamic styling
  • CSS-in-JS - Modern component-centric styling
  • Tailwind CSS - Utility-first rapid development

πŸ’‘ No "Best" Approach

There's no single "best" way to style React apps. Each approach has trade-offs. Many professional apps use multiple approaches - global CSS for resets, CSS Modules for components, and inline styles for dynamic values. Choose based on your project needs!

One Button, Five Styling Approaches The same button expressed five ways: plain CSS, CSS Modules, inline styles, styled-components, and Tailwind CSS, each with the code you would write. The Same Button, Five Ways Plain CSS <button className="btn"> CSS Modules className={styles.btn} Inline style={{ background: '#667eea' }} styled-comp const B = styled.button`…`; Tailwind className="px-4 py-2 bg-indigo-500"
Figure 1: One button, five styling dialects. They all render the same pixels β€” the difference is where the styling lives and how it scales as your app grows. We work through each below.

πŸ“„ Traditional CSS Stylesheets

Let's start with what you already know - regular CSS files! This is the simplest approach and works great for many projects.

Basic CSS Import

You can import CSS files directly into your components:

// Button.tsx
import './Button.css';

function Button({ text, onClick }: ButtonProps) {
    return (
        <button className="btn" onClick={onClick}>
            {text}
        </button>
    );
}

export default Button;
/* Button.css */
.btn {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    background-color: #667eea;
    color: white;
    font-size: 1rem;
    cursor: pointer;
    transition: background-color 0.2s;
}

.btn:hover {
    background-color: #5568d3;
}

.btn:active {
    background-color: #4451b8;
}

.btn:disabled {
    background-color: #ccc;
    cursor: not-allowed;
}

Global vs Component Styles

Structure your CSS for maintainability:

src/
β”œβ”€β”€ index.css              # Global styles, resets
β”œβ”€β”€ App.tsx
β”œβ”€β”€ App.css                # App-level styles
└── components/
    β”œβ”€β”€ Button/
    β”‚   β”œβ”€β”€ Button.tsx
    β”‚   └── Button.css     # Button-specific styles
    └── Card/
        β”œβ”€β”€ Card.tsx
        └── Card.css       # Card-specific styles

Class Name Composition

Build class names dynamically:

interface ButtonProps {
    text: string;
    variant?: 'primary' | 'secondary' | 'danger';
    size?: 'small' | 'medium' | 'large';
    disabled?: boolean;
    onClick?: () => void;
}

function Button({ 
    text, 
    variant = 'primary', 
    size = 'medium', 
    disabled = false,
    onClick 
}: ButtonProps) {
    // Method 1: Template literal
    const className = `btn btn-${variant} btn-${size}`;
    
    // Method 2: Array join
    const classes = ['btn', `btn-${variant}`, `btn-${size}`];
    if (disabled) classes.push('btn-disabled');
    const classNameAlt = classes.join(' ');
    
    return (
        <button 
            className={className}
            disabled={disabled}
            onClick={onClick}
        >
            {text}
        </button>
    );
}
/* Button variants */
.btn-primary {
    background-color: #667eea;
}

.btn-secondary {
    background-color: #6c757d;
}

.btn-danger {
    background-color: #dc3545;
}

/* Button sizes */
.btn-small {
    padding: 0.25rem 0.5rem;
    font-size: 0.875rem;
}

.btn-medium {
    padding: 0.5rem 1rem;
    font-size: 1rem;
}

.btn-large {
    padding: 0.75rem 1.5rem;
    font-size: 1.125rem;
}

Composing Class Names with a Helper

Once your class logic grows past a simple template literal, reach for a tiny helper. In 2026 the go-to is clsx β€” it's roughly 200 bytes, ships its own TypeScript types (no separate @types package to install), and has the same API as the older classnames. It joins string arguments and includes object keys whose value is truthy:

# clsx bundles its own types β€” nothing extra to install
npm install clsx
import clsx from 'clsx';
import './Button.css';

function Button({
    text,
    variant = 'primary',
    size = 'medium',
    disabled = false,
    loading = false,
    onClick
}: ButtonProps) {
    const buttonClasses = clsx('btn', {
        [`btn-${variant}`]: variant,
        [`btn-${size}`]: size,
        'btn-disabled': disabled,
        'btn-loading': loading,
    });
    
    return (
        <button 
            className={buttonClasses}
            disabled={disabled || loading}
            onClick={onClick}
        >
            {loading ? 'Loading...' : text}
        </button>
    );
}

βœ… Pros of Traditional CSS

  • Familiar - no new syntax to learn
  • Works with existing CSS knowledge
  • Great tooling support (IDE autocomplete, linters)
  • Easy to share styles across components
  • Excellent performance

⚠️ Cons of Traditional CSS

  • Global namespace - class names can conflict
  • No automatic scoping to components
  • Dead code can accumulate (unused styles)
  • Need to manage class name uniqueness manually

BEM Naming Convention

Use BEM (Block Element Modifier) to avoid conflicts:

/* Block */
.card { }

/* Elements */
.card__header { }
.card__body { }
.card__footer { }

/* Modifiers */
.card--primary { }
.card--large { }
.card__header--highlighted { }
function Card({ variant = 'default', size = 'medium' }: CardProps) {
    return (
        <div className={`card card--${variant} card--${size}`}>
            <div className="card__header">
                <h2>Card Title</h2>
            </div>
            <div className="card__body">
                <p>Card content</p>
            </div>
            <div className="card__footer">
                <button>Action</button>
            </div>
        </div>
    );
}

πŸ”’ CSS Modules

CSS Modules solve the global namespace problem by automatically scoping styles to components. They're like regular CSS but with superpowers!

πŸ“– Definition

CSS Modules: A CSS file whose class names are scoped locally by default. At build time your bundler (Vite, Next.js, Parcel β€” all support them out of the box) rewrites each class name to a unique string, so two files can both declare .title without ever colliding.

Creating a CSS Module

Name your CSS file with the .module.css extension:

Button.tsx
Button.module.css    ← Notice the .module.css extension
/* Button.module.css */
.button {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
    transition: all 0.2s;
}

.primary {
    background-color: #667eea;
    color: white;
}

.secondary {
    background-color: #6c757d;
    color: white;
}

.large {
    padding: 0.75rem 1.5rem;
    font-size: 1.125rem;
}

Using CSS Modules

Import the CSS Module as an object:

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

interface ButtonProps {
    text: string;
    variant?: 'primary' | 'secondary';
    size?: 'medium' | 'large';
    onClick?: () => void;
}

function Button({ 
    text, 
    variant = 'primary', 
    size = 'medium',
    onClick 
}: ButtonProps) {
    // Access classes as object properties
    return (
        <button 
            className={`${styles.button} ${styles[variant]} ${styles[size]}`}
            onClick={onClick}
        >
            {text}
        </button>
    );
}

export default Button;

How CSS Modules Work

At build time, class names are transformed:

/* You write: */
.button { }

/* Browser sees: */
.Button_button__2Xk3q { }

The generated class name is unique, preventing conflicts!

🎨 Interactive: CSS Module Scoping

See how CSS Modules transform your class names to prevent conflicts:

CSS Module Class Name Transformation What You Write .button { padding: 1rem; } .primary { background: blue; } .card { border-radius: 8px; } Build Browser Sees .Button_button__x7Ks2 .Button_primary__a3Bm9 .Card_card__q8Np4 Why This Matters: No More Conflicts! ❌ Plain CSS // Header.css AND Card.css .title { } // CONFLICT! βœ… CSS Modules Header_title__abc123 Card_title__xyz789 // No conflict!

Composition in CSS Modules

CSS Modules support composition - reusing styles:

/* Button.module.css */
.base {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
}

.primary {
    composes: base;
    background-color: #667eea;
    color: white;
}

.secondary {
    composes: base;
    background-color: #6c757d;
    color: white;
}

/* Can even compose from other files */
.special {
    composes: primary;
    composes: animated from './animations.module.css';
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
// Simpler usage - no need to combine classes!
function Button({ variant = 'primary' }: ButtonProps) {
    return (
        <button className={styles[variant]}>
            Click me
        </button>
    );
}

Global Styles in CSS Modules

Sometimes you need global classes:

/* Button.module.css */

/* Local by default */
.button {
    padding: 0.5rem 1rem;
}

/* Explicitly global */
:global(.btn-reset) {
    all: unset;
}

/* Mix local and global */
.button :global(.icon) {
    margin-right: 0.5rem;
}

Using with clsx

Combine CSS Modules with the clsx helper β€” you pass the scoped styles.* values instead of raw strings:

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

function Button({
    variant = 'primary',
    size = 'medium',
    disabled = false
}: ButtonProps) {
    const buttonClasses = clsx(
        styles.button,
        styles[variant],
        styles[size],
        {
            [styles.disabled]: disabled
        }
    );
    
    return (
        <button className={buttonClasses}>
            Click me
        </button>
    );
}

βœ… Pros of CSS Modules

  • Automatic scoping - no naming conflicts
  • Still write regular CSS
  • No runtime cost - processed at build time
  • Works with existing tools
  • Composition feature is powerful
  • Easy to understand what styles apply where

⚠️ Things to Watch

  • Syntax is slightly different from plain CSS
  • Need to use bracket notation for dynamic classes: styles[variant]
  • Can't use kebab-case class names without brackets
  • Harder to override styles from parent components

TypeScript Support

Out of the box, TypeScript types a .module.css import loosely as { readonly [key: string]: string } β€” so styles.anything compiles even if that class doesn't exist. To get real autocomplete and catch typos, add the editor plugin:

# Install the TypeScript language-service plugin
npm install --save-dev typescript-plugin-css-modules
// tsconfig.json
{
  "compilerOptions": {
    "plugins": [{ "name": "typescript-plugin-css-modules" }]
  }
}

πŸ’‘ 2026 note

The plugin powers editor autocomplete but doesn't fail a tsc build. If you want the compiler itself to reject unknown class names, generate real .d.ts files for each module with a tool such as typed-css-modules, or lean on a typed styling layer like Vanilla Extract or Panda CSS where the class names are TypeScript values to begin with.

With the plugin installed, your editor knows exactly which classes exist:

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

// TypeScript knows what classes exist!
styles.button    // βœ… Autocomplete works
styles.primary   // βœ… Autocomplete works
styles.invalid   // ❌ TypeScript error!

✍️ Inline Styles

React lets you write styles directly in JavaScript using the style prop. This is perfect for dynamic styling based on props or state!

Basic Inline Styles

Pass a JavaScript object to the style prop:

function Box() {
    return (
        <div style={{
            backgroundColor: '#667eea',
            color: 'white',
            padding: '1rem',
            borderRadius: '8px',
            boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
        }}>
            Styled Box
        </div>
    );
}

⚠️ Remember: camelCase Properties

CSS properties become camelCase in JavaScript:

  • background-color β†’ backgroundColor
  • font-size β†’ fontSize
  • margin-top β†’ marginTop

πŸ’‘ Type your style objects with React.CSSProperties

The style prop is typed as React.CSSProperties β€” an interface with every valid camelCase CSS property. Annotate your style objects with it and TypeScript checks each property and gives you autocomplete for the values:

const boxStyle: React.CSSProperties = {
    backgroundColor: '#667eea',
    padding: 16,              // βœ… numbers are OK β€” React adds 'px' for lengths
    borderRadius: '8px',
    // 'background-color': 'red' // ❌ hyphenated key is not assignable
    // display: 'flx',           // ❌ not a valid value for 'display'
};

Numbers are allowed for length-style properties (React appends px), but a handful of properties are unitless β€” lineHeight, zIndex, flexGrow, opacity β€” so lineHeight: 1.6 stays 1.6, not 1.6px.

Check Yourself: Is It a Valid Style Object?

Click each candidate below to see whether it would satisfy React.CSSProperties β€” and if not, why the compiler rejects it.

const style: React.CSSProperties = { … }

πŸ‘† Click an example above to see what TypeScript thinks.

Dynamic Inline Styles

The real power of inline styles is dynamic values:

interface ProgressBarProps {
    percentage: number;
    color?: string;
    height?: number;
}

function ProgressBar({ 
    percentage, 
    color = '#667eea',
    height = 20 
}: ProgressBarProps) {
    const containerStyle = {
        width: '100%',
        height: `${height}px`,
        backgroundColor: '#e0e0e0',
        borderRadius: '10px',
        overflow: 'hidden'
    };
    
    const fillStyle = {
        width: `${percentage}%`,
        height: '100%',
        backgroundColor: color,
        transition: 'width 0.3s ease'
    };
    
    return (
        <div style={containerStyle}>
            <div style={fillStyle} />
        </div>
    );
}

// Usage with dynamic values
<ProgressBar percentage={75} color="#4CAF50" height={30} />

Conditional Inline Styles

Change styles based on conditions:

interface MessageProps {
    type: 'info' | 'success' | 'warning' | 'error';
    text: string;
}

function Message({ type, text }: MessageProps) {
    const getBackgroundColor = () => {
        switch (type) {
            case 'info': return '#2196F3';
            case 'success': return '#4CAF50';
            case 'warning': return '#FFC107';
            case 'error': return '#F44336';
            default: return '#666';
        }
    };
    
    const messageStyle = {
        padding: '1rem',
        borderRadius: '4px',
        backgroundColor: getBackgroundColor(),
        color: 'white',
        marginBottom: '1rem'
    };
    
    return <div style={messageStyle}>{text}</div>;
}

Extracting Style Objects

For cleaner code, extract style objects. Define them outside the component so they aren't recreated on every render, and type them so TypeScript validates every property:

// Typed with React.CSSProperties β€” no `as const` gymnastics needed.
// Without the annotation, 'column' would widen to `string` and error.
const styles: Record<string, React.CSSProperties> = {
    container: {
        display: 'flex',
        flexDirection: 'column',   // βœ… checked against the allowed union
        gap: '1rem',
        padding: '2rem'
    },
    header: {
        fontSize: '2rem',
        fontWeight: 'bold',
        color: '#333'
    },
    content: {
        fontSize: '1rem',
        lineHeight: 1.6,
        color: '#666'
    }
};

function Article() {
    return (
        <div style={styles.container}>
            <h1 style={styles.header}>Article Title</h1>
            <p style={styles.content}>Article content...</p>
        </div>
    );
}

Merging Style Objects

Combine multiple style objects:

const baseButtonStyle = {
    padding: '0.5rem 1rem',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer'
};

const primaryButtonStyle = {
    ...baseButtonStyle,
    backgroundColor: '#667eea',
    color: 'white'
};

const disabledButtonStyle = {
    ...baseButtonStyle,
    backgroundColor: '#ccc',
    cursor: 'not-allowed'
};

function Button({ disabled }: { disabled: boolean }) {
    const buttonStyle = disabled ? disabledButtonStyle : primaryButtonStyle;
    
    return <button style={buttonStyle}>Click me</button>;
}

βœ… Pros of Inline Styles

  • Dynamic values are easy - just use JavaScript
  • No naming conflicts - styles are scoped to element
  • Full power of JavaScript for calculations
  • Easy to see what styles apply to an element
  • Props flow directly to styles

⚠️ Cons of Inline Styles

  • No pseudo-classes (:hover, :focus, etc.)
  • No media queries for responsive design
  • No CSS animations (only transitions)
  • Verbose for complex styles
  • Slight performance impact (vs static CSS)
  • No autoprefixing for browser compatibility

When to Use Inline Styles

Inline styles work best for:

  • Dynamic values calculated from props/state
  • Values that change frequently
  • Component-specific styles that won't be reused
  • Quick prototyping

Avoid inline styles for:

  • Static styles that don't change
  • Complex layouts requiring media queries
  • Styles needing pseudo-classes
  • Reusable design systems

🎭 Dynamic and Conditional Styling

React's real power comes from making styles react to your data. Let's master dynamic styling techniques!

Conditional Classes

Apply classes based on conditions:

interface ButtonProps {
    text: string;
    variant: 'primary' | 'secondary';
    isActive: boolean;
    isLoading: boolean;
}

function Button({ text, variant, isActive, isLoading }: ButtonProps) {
    // Method 1: Template literal
    const className = `btn btn-${variant} ${isActive ? 'active' : ''} ${isLoading ? 'loading' : ''}`;
    
    // Method 2: Array filter join
    const classNameAlt = [
        'btn',
        `btn-${variant}`,
        isActive && 'active',
        isLoading && 'loading'
    ].filter(Boolean).join(' ');
    
    // Method 3: clsx helper (recommended)
    const classNameBest = clsx('btn', `btn-${variant}`, {
        active: isActive,
        loading: isLoading
    });
    
    return <button className={classNameBest}>{text}</button>;
}

Style Based on Props

Calculate styles from prop values:

interface AvatarProps {
    name: string;
    size?: number;
    color?: string;
    imageUrl?: string;
}

function Avatar({ name, size = 40, color, imageUrl }: AvatarProps) {
    // Generate color from name if not provided
    const getColorFromName = (str: string) => {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            hash = str.charCodeAt(i) + ((hash << 5) - hash);
        }
        const hue = hash % 360;
        return `hsl(${hue}, 70%, 60%)`;
    };
    
    const backgroundColor = color || getColorFromName(name);
    const initials = name.split(' ').map(n => n[0]).join('').toUpperCase();
    
    const avatarStyle = {
        width: size,
        height: size,
        borderRadius: '50%',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: size * 0.4,
        fontWeight: 'bold',
        color: 'white',
        backgroundColor: backgroundColor,
        backgroundImage: imageUrl ? `url(${imageUrl})` : 'none',
        backgroundSize: 'cover',
        backgroundPosition: 'center'
    };
    
    return (
        <div style={avatarStyle}>
            {!imageUrl && initials}
        </div>
    );
}

// Usage
<Avatar name="Alice Johnson" size={60} />
<Avatar name="Bob Smith" size={40} color="#FF6B6B" />
<Avatar name="Charlie Brown" imageUrl="/charlie.jpg" />

Theme-Based Styling

Create a theme system:

// Theme definition
const theme = {
    colors: {
        primary: '#667eea',
        secondary: '#6c757d',
        success: '#4CAF50',
        danger: '#F44336',
        text: '#333',
        textLight: '#666',
        background: '#fff',
        border: '#e0e0e0'
    },
    spacing: {
        xs: '0.25rem',
        sm: '0.5rem',
        md: '1rem',
        lg: '1.5rem',
        xl: '2rem'
    },
    borderRadius: {
        sm: '4px',
        md: '8px',
        lg: '12px',
        full: '9999px'
    }
};

// Use theme in component
interface CardProps {
    children: React.ReactNode;
    variant?: 'default' | 'primary' | 'success';
}

function Card({ children, variant = 'default' }: CardProps) {
    const getBackgroundColor = () => {
        switch (variant) {
            case 'primary': return theme.colors.primary;
            case 'success': return theme.colors.success;
            default: return theme.colors.background;
        }
    };
    
    const cardStyle = {
        padding: theme.spacing.lg,
        borderRadius: theme.borderRadius.md,
        backgroundColor: getBackgroundColor(),
        border: `1px solid ${theme.colors.border}`,
        boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
    };
    
    return <div style={cardStyle}>{children}</div>;
}

Animation with State

Animate components by changing styles:

import { useState } from 'react';

function AnimatedBox() {
    const [isExpanded, setIsExpanded] = useState(false);
    
    const boxStyle = {
        width: isExpanded ? '200px' : '100px',
        height: isExpanded ? '200px' : '100px',
        backgroundColor: isExpanded ? '#4CAF50' : '#667eea',
        borderRadius: isExpanded ? '50%' : '8px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: 'white',
        cursor: 'pointer',
        transition: 'all 0.3s ease',
        transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)'
    };
    
    return (
        <div 
            style={boxStyle}
            onClick={() => setIsExpanded(!isExpanded)}
        >
            Click me!
        </div>
    );
}

Live Playground: A Box Driven by Inline Styles

Dynamic styling shines when the numbers come from real input. Move the sliders below β€” each one feeds a value straight into a React.CSSProperties object applied to the preview box, exactly the way useState values would in a real component:

Box
const style: React.CSSProperties = {
  width: 100,
  height: 100,
  borderRadius: 8,
  backgroundColor: '#667eea',
  transform: 'rotate(0deg)',
};

Notice there's no CSS file here at all β€” the object is the styling, computed live from JavaScript. That's the sweet spot for inline styles: values that genuinely change at runtime, like this box, a progress bar's width, or a user-picked accent color.

Responsive Values

Adjust styles based on screen size:

import { useState, useEffect } from 'react';

function useWindowWidth() {
    const [width, setWidth] = useState(window.innerWidth);
    
    useEffect(() => {
        const handleResize = () => setWidth(window.innerWidth);
        window.addEventListener('resize', handleResize);
        return () => window.removeEventListener('resize', handleResize);
    }, []);
    
    return width;
}

function ResponsiveGrid() {
    const width = useWindowWidth();
    
    const getColumns = () => {
        if (width < 768) return 1;
        if (width < 1024) return 2;
        return 3;
    };
    
    const gridStyle = {
        display: 'grid',
        gridTemplateColumns: `repeat(${getColumns()}, 1fr)`,
        gap: '1rem'
    };
    
    return (
        <div style={gridStyle}>
            {/* Grid items */}
        </div>
    );
}

πŸ’‘ Pro Tip: Combine Approaches

Use CSS/CSS Modules for static styles and inline styles for dynamic values:

function ProgressBar({ percentage }: { percentage: number }) {
    return (
        <div className="progress-container">
            <div 
                className="progress-fill"
                style={{ width: `${percentage}%` }}  // Dynamic value
            />
        </div>
    );
}

πŸ’… CSS-in-JS Solutions

CSS-in-JS libraries let you write CSS directly in your JavaScript with the full power of both languages. For years the most popular was styled-components, and it's still a great way to learn the pattern β€” so we'll use it as our example below.

⚠️ A 2026 reality check on runtime CSS-in-JS

The libraries in this section β€” styled-components and Emotion β€” are runtime CSS-in-JS: they generate and inject styles in the browser as your components render. Two things have cooled the industry on that approach:

  • React Server Components. Runtime CSS-in-JS needs the browser and React Context, so it forces components to be Client Components. That fights the default in modern frameworks (Next.js App Router), where you want to render on the server.
  • styled-components is now in maintenance mode (announced by its maintainers in 2025) β€” it still works, but it isn't getting new features.

The momentum has shifted to zero-runtime options that produce plain CSS at build time: CSS Modules, Tailwind, Vanilla Extract, Linaria, Panda CSS, and StyleX. Learn the concept here; for a brand-new app in 2026, reach for a zero-runtime tool first.

Introduction to styled-components

styled-components lets you create components with built-in styles:

# styled-components v6 bundles its own types β€” no @types package needed
npm install styled-components
import styled from 'styled-components';

// Create a styled component
const Button = styled.button`
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    background-color: #667eea;
    color: white;
    font-size: 1rem;
    cursor: pointer;
    transition: background-color 0.2s;
    
    &:hover {
        background-color: #5568d3;
    }
    
    &:active {
        background-color: #4451b8;
    }
`;

// Use it like a regular component
function App() {
    return <Button>Click me</Button>;
}

Props in styled-components

styled-components can use props to change styles:

interface ButtonProps {
    $variant?: 'primary' | 'secondary' | 'danger';
    $size?: 'small' | 'medium' | 'large';
}

const Button = styled.button<ButtonProps>`
    padding: ${props => {
        switch (props.$size) {
            case 'small': return '0.25rem 0.5rem';
            case 'large': return '0.75rem 1.5rem';
            default: return '0.5rem 1rem';
        }
    }};
    
    font-size: ${props => {
        switch (props.$size) {
            case 'small': return '0.875rem';
            case 'large': return '1.125rem';
            default: return '1rem';
        }
    }};
    
    background-color: ${props => {
        switch (props.$variant) {
            case 'secondary': return '#6c757d';
            case 'danger': return '#F44336';
            default: return '#667eea';
        }
    }};
    
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.2s;
    
    &:hover {
        opacity: 0.9;
    }
    
    &:disabled {
        opacity: 0.5;
        cursor: not-allowed;
    }
`;

// Usage
<Button $variant="primary" $size="large">Large Primary</Button>
<Button $variant="danger" $size="small">Small Danger</Button>

⚠️ Transient Props

In styled-components v5.1+, prefix props meant only for styling with $ to prevent them from being passed to the DOM. This avoids React warnings about invalid HTML attributes.

Extending Styles

Create variations by extending styled components:

const Button = styled.button`
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
`;

const PrimaryButton = styled(Button)`
    background-color: #667eea;
    color: white;
`;

const OutlineButton = styled(Button)`
    background-color: transparent;
    color: #667eea;
    border: 2px solid #667eea;
`;

const IconButton = styled(Button)`
    padding: 0.5rem;
    width: 40px;
    height: 40px;
    border-radius: 50%;
`;

Theming with styled-components

Create a theme provider for global styles:

import { ThemeProvider } from 'styled-components';

const theme = {
    colors: {
        primary: '#667eea',
        secondary: '#6c757d',
        text: '#333',
        background: '#fff'
    },
    spacing: {
        sm: '0.5rem',
        md: '1rem',
        lg: '1.5rem'
    }
};

// Components can access theme
const Container = styled.div`
    padding: ${props => props.theme.spacing.lg};
    background-color: ${props => props.theme.colors.background};
    color: ${props => props.theme.colors.text};
`;

const Title = styled.h1`
    color: ${props => props.theme.colors.primary};
    margin-bottom: ${props => props.theme.spacing.md};
`;

// Wrap your app
function App() {
    return (
        <ThemeProvider theme={theme}>
            <Container>
                <Title>Themed App</Title>
            </Container>
        </ThemeProvider>
    );
}

Global Styles

Define global styles with createGlobalStyle:

import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    
    body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu;
        line-height: 1.6;
        color: #333;
    }
    
    h1, h2, h3, h4, h5, h6 {
        margin-bottom: 1rem;
    }
`;

function App() {
    return (
        <>
            <GlobalStyle />
            {/* Your app components */}
        </>
    );
}

βœ… Pros of CSS-in-JS

  • Scoped styles automatically
  • Full power of JavaScript in styles
  • Dynamic theming is easy
  • Dead code elimination (unused styles removed)
  • Better TypeScript integration
  • Can use props directly in styles

⚠️ Cons of CSS-in-JS

  • Learning curve - new syntax to learn
  • Runtime overhead (styles generated in browser)
  • Larger bundle size
  • Can't use traditional CSS tools
  • Server-side rendering requires extra setup

Other CSS-in-JS Libraries

Alternatives to styled-components:

  • Emotion - Similar to styled-components, slightly faster
  • JSS - More flexible, used by Material-UI
  • Linaria - Zero-runtime CSS-in-JS
  • Vanilla Extract - Type-safe, zero-runtime

🌬️ Tailwind CSS

Tailwind is a utility-first CSS framework that lets you build designs rapidly by composing utility classes. It's become incredibly popular in the React ecosystem!

What is Utility-First CSS?

Instead of writing custom CSS, you use pre-made utility classes:

// Traditional approach
<button className="custom-button">Click me</button>

/* custom-button {
    padding: 0.5rem 1rem;
    background-color: blue;
    color: white;
    border-radius: 0.25rem;
} */

// Tailwind approach
<button className="px-4 py-2 bg-blue-500 text-white rounded">
    Click me
</button>

Installing Tailwind (v4)

Tailwind CSS v4 (released 2025) is a big simplification: there's no tailwind.config.js to generate and no PostCSS/autoprefixer setup. For a Vite project you add the official plugin and a single CSS import:

# Tailwind v4 with the Vite plugin
npm install tailwindcss @tailwindcss/vite
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [react(), tailwindcss()],
});
/* index.css β€” one line replaces the old three @tailwind directives */
@import "tailwindcss";

πŸ’‘ v3 vs v4

If you land in an older codebase you'll see the v3 setup instead: a tailwind.config.js with a content array, npx tailwindcss init -p, and the three @tailwind base; @tailwind components; @tailwind utilities; directives. In v4 you configure your design tokens right in CSS with a @theme { … } block instead of a JS config file.

Common Tailwind Utilities

Here are the most frequently used classes:

Category Example Classes What They Do
Spacing p-4, m-2, px-6, mt-8 Padding and margins
Colors bg-blue-500, text-red-600 Background and text colors
Typography text-xl, font-bold Font size and weight
Layout flex, grid, block, hidden Display properties
Sizing w-full, h-screen, max-w-lg Width and height
Borders border, rounded-lg Border and border-radius

Building Components with Tailwind

interface ButtonProps {
    children: React.ReactNode;
    variant?: 'primary' | 'secondary' | 'danger';
    size?: 'sm' | 'md' | 'lg';
    onClick?: () => void;
}

function Button({ 
    children, 
    variant = 'primary', 
    size = 'md',
    onClick 
}: ButtonProps) {
    const baseClasses = 'font-semibold rounded transition-colors duration-200';
    
    const variantClasses = {
        primary: 'bg-blue-500 hover:bg-blue-600 text-white',
        secondary: 'bg-gray-500 hover:bg-gray-600 text-white',
        danger: 'bg-red-500 hover:bg-red-600 text-white'
    };
    
    const sizeClasses = {
        sm: 'px-3 py-1 text-sm',
        md: 'px-4 py-2 text-base',
        lg: 'px-6 py-3 text-lg'
    };
    
    const className = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`;
    
    return (
        <button className={className} onClick={onClick}>
            {children}
        </button>
    );
}

// Usage
<Button variant="primary" size="lg">Large Primary</Button>
<Button variant="danger" size="sm">Small Danger</Button>

Responsive Design with Tailwind

Tailwind makes responsive design incredibly easy:

function ResponsiveCard() {
    return (
        <div className="
            p-4 
            md:p-6 
            lg:p-8
            bg-white 
            rounded-lg 
            shadow-md
            w-full 
            md:w-1/2 
            lg:w-1/3
        ">
            <h2 className="text-xl md:text-2xl lg:text-3xl font-bold mb-4">
                Responsive Card
            </h2>
            <p className="text-sm md:text-base text-gray-600">
                This card adapts to different screen sizes.
            </p>
        </div>
    );
}

// Breakpoints:
// sm: 640px
// md: 768px
// lg: 1024px
// xl: 1280px
// 2xl: 1536px

Custom Tailwind Classes

Use @apply to create custom component classes:

/* index.css */
@layer components {
    .btn-primary {
        @apply px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors;
    }
    
    .card {
        @apply p-6 bg-white rounded-lg shadow-md;
    }
    
    .input-field {
        @apply w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500;
    }
}
// Use custom classes
function Form() {
    return (
        <div className="card">
            <input type="text" className="input-field" />
            <button className="btn-primary">Submit</button>
        </div>
    );
}

βœ… Pros of Tailwind

  • Extremely fast development
  • No need to name things
  • Consistent design system out of the box
  • Responsive design is trivial
  • Small final bundle (unused classes removed)
  • Great documentation and community

⚠️ Cons of Tailwind

  • Long className strings can be ugly
  • Learning curve for utility names
  • HTML gets more cluttered
  • Harder to make global style changes
  • Not everyone likes the approach

πŸ“± Responsive Design in React

Making your React apps look great on all screen sizes is crucial. Let's explore responsive design techniques!

Media Queries in CSS

Traditional media queries work perfectly in React:

/* Component.module.css */
.container {
    padding: 1rem;
}

.grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
}

/* Tablet and up */
@media (min-width: 768px) {
    .container {
        padding: 2rem;
    }
    
    .grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Desktop and up */
@media (min-width: 1024px) {
    .container {
        padding: 3rem;
    }
    
    .grid {
        grid-template-columns: repeat(3, 1fr);
        gap: 2rem;
    }
}

JavaScript-Based Responsive Design

Use JavaScript to detect screen size and adjust components:

import { useState, useEffect } from 'react';

// Custom hook for window width
function useWindowWidth() {
    const [width, setWidth] = useState(
        typeof window !== 'undefined' ? window.innerWidth : 0
    );
    
    useEffect(() => {
        const handleResize = () => setWidth(window.innerWidth);
        window.addEventListener('resize', handleResize);
        return () => window.removeEventListener('resize', handleResize);
    }, []);
    
    return width;
}

// Custom hook for breakpoints
function useBreakpoint() {
    const width = useWindowWidth();
    
    return {
        isMobile: width < 768,
        isTablet: width >= 768 && width < 1024,
        isDesktop: width >= 1024,
        width
    };
}

// Usage in component
function ResponsiveNav() {
    const { isMobile, isDesktop } = useBreakpoint();
    
    return (
        <nav>
            {isMobile && <MobileMenu />}
            {isDesktop && <DesktopMenu />}
        </nav>
    );
}

Container Queries (Modern Approach)

React components can adapt to their container size:

/* Modern CSS Container Queries */
.card-container {
    container-type: inline-size;
}

.card {
    padding: 1rem;
}

.card-title {
    font-size: 1rem;
}

/* When container is at least 400px wide */
@container (min-width: 400px) {
    .card {
        padding: 2rem;
    }
    
    .card-title {
        font-size: 1.5rem;
    }
}

Mobile-First vs Desktop-First

Choose your approach wisely:

/* Mobile-First (Recommended) */
/* Base styles for mobile */
.container {
    padding: 1rem;
    font-size: 14px;
}

/* Enhanced for larger screens */
@media (min-width: 768px) {
    .container {
        padding: 2rem;
        font-size: 16px;
    }
}

/* Desktop-First (Alternative) */
/* Base styles for desktop */
.container {
    padding: 3rem;
    font-size: 16px;
}

/* Simplified for smaller screens */
@media (max-width: 767px) {
    .container {
        padding: 1rem;
        font-size: 14px;
    }
}

βœ… Mobile-First Benefits

  • Smaller base bundle size
  • Progressive enhancement mindset
  • Better performance on mobile
  • Simpler to add features for larger screens

🎨 Interactive: Responsive Breakpoints

Drag the slider to change the simulated viewport width. Watch the grid reflow the way a mobile-first CSS layout would β€” one column on a phone, more columns as space allows:

Viewport: 800px β€” Tablet Β· 2 columns

320px  Β·  1440px

Responsive Images

Optimize images for different screen sizes:

function ResponsiveImage({ 
    src, 
    alt, 
    sizes 
}: { 
    src: string; 
    alt: string; 
    sizes?: string;
}) {
    return (
        <picture>
            <source 
                media="(min-width: 1024px)"
                srcSet={`${src}-large.jpg`}
            />
            <source 
                media="(min-width: 768px)"
                srcSet={`${src}-medium.jpg`}
            />
            <img 
                src={`${src}-small.jpg`}
                alt={alt}
                loading="lazy"
                style={{ width: '100%', height: 'auto' }}
            />
        </picture>
    );
}

Viewport Units

Use viewport-based units for fluid sizing:

.hero {
    /* 100% of viewport height */
    height: 100vh;
    
    /* 50% of viewport width */
    width: 50vw;
    
    /* Minimum of 2rem or 5% of viewport width */
    padding: max(2rem, 5vw);
    
    /* Fluid font size */
    font-size: clamp(1rem, 2.5vw, 3rem);
}

πŸ‹οΈ Hands-on Practice

πŸ‹οΈ Exercise 1: Style a Dashboard Card

Objective: Create a styled dashboard card using CSS Modules with responsive design.

Requirements:

  1. Create a Card component with CSS Module styling
  2. Include header, body, and footer sections
  3. Add hover effects and transitions
  4. Make it responsive (stack on mobile, side-by-side on desktop)
  5. Support light and dark variants

Starter Code:

// DashboardCard.tsx
import styles from './DashboardCard.module.css';

interface DashboardCardProps {
    title: string;
    value: string | number;
    change: number;
    variant?: 'light' | 'dark';
}

function DashboardCard({ title, value, change, variant = 'light' }: DashboardCardProps) {
    // TODO: Implement component
    return <div></div>;
}

export default DashboardCard;
πŸ’‘ Hint

Use CSS Grid or Flexbox for layout. Add a className that combines the base style with the variant. Use CSS transitions for smooth hover effects.

βœ… Solution
// DashboardCard.tsx
import styles from './DashboardCard.module.css';

interface DashboardCardProps {
    title: string;
    value: string | number;
    change: number;
    variant?: 'light' | 'dark';
}

function DashboardCard({ 
    title, 
    value, 
    change, 
    variant = 'light' 
}: DashboardCardProps) {
    const isPositive = change >= 0;
    const cardClass = `${styles.card} ${styles[variant]}`;
    const changeClass = isPositive ? styles.positive : styles.negative;
    
    return (
        <div className={cardClass}>
            <div className={styles.header}>
                <h3 className={styles.title}>{title}</h3>
            </div>
            
            <div className={styles.body}>
                <div className={styles.value}>{value}</div>
                <div className={`${styles.change} ${changeClass}`}>
                    {isPositive ? '↑' : '↓'} {Math.abs(change)}%
                </div>
            </div>
        </div>
    );
}

export default DashboardCard;
/* DashboardCard.module.css */
.card {
    padding: 1.5rem;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    transition: transform 0.2s, box-shadow 0.2s;
}

.card:hover {
    transform: translateY(-4px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.light {
    background-color: white;
    color: #333;
}

.dark {
    background-color: #2d3748;
    color: white;
}

.header {
    margin-bottom: 1rem;
}

.title {
    font-size: 0.875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    opacity: 0.7;
}

.body {
    display: flex;
    align-items: baseline;
    gap: 1rem;
}

.value {
    font-size: 2rem;
    font-weight: bold;
}

.change {
    font-size: 0.875rem;
    font-weight: 600;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
}

.positive {
    color: #10b981;
    background-color: #d1fae5;
}

.negative {
    color: #ef4444;
    background-color: #fee2e2;
}

/* Responsive */
@media (max-width: 768px) {
    .card {
        padding: 1rem;
    }
    
    .value {
        font-size: 1.5rem;
    }
}

πŸ‹οΈ Exercise 2: Build a Theme Switcher

Objective: Create a theme switching system with light and dark modes.

Requirements:

  1. Create a theme context with light/dark modes
  2. Build a toggle button to switch themes
  3. Style components differently based on theme
  4. Persist theme choice in localStorage
βœ… Solution
// ThemeContext.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';

type Theme = 'light' | 'dark';

interface ThemeContextType {
    theme: Theme;
    toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: ReactNode }) {
    const [theme, setTheme] = useState<Theme>(() => {
        const saved = localStorage.getItem('theme');
        return (saved as Theme) || 'light';
    });
    
    useEffect(() => {
        localStorage.setItem('theme', theme);
        document.documentElement.setAttribute('data-theme', theme);
    }, [theme]);
    
    const toggleTheme = () => {
        setTheme(prev => prev === 'light' ? 'dark' : 'light');
    };
    
    return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    );
}

export function useTheme() {
    const context = useContext(ThemeContext);
    if (!context) {
        throw new Error('useTheme must be used within ThemeProvider');
    }
    return context;
}

// ThemeToggle.tsx
function ThemeToggle() {
    const { theme, toggleTheme } = useTheme();
    
    return (
        <button 
            onClick={toggleTheme}
            className="theme-toggle"
            aria-label="Toggle theme"
        >
            {theme === 'light' ? 'πŸŒ™' : 'β˜€οΈ'}
        </button>
    );
}
/* Global theme styles */
:root[data-theme="light"] {
    --bg-primary: #ffffff;
    --bg-secondary: #f7fafc;
    --text-primary: #1a202c;
    --text-secondary: #4a5568;
    --border-color: #e2e8f0;
}

:root[data-theme="dark"] {
    --bg-primary: #1a202c;
    --bg-secondary: #2d3748;
    --text-primary: #f7fafc;
    --text-secondary: #cbd5e0;
    --border-color: #4a5568;
}

body {
    background-color: var(--bg-primary);
    color: var(--text-primary);
    transition: background-color 0.3s, color 0.3s;
}

.theme-toggle {
    padding: 0.5rem;
    border: none;
    background: var(--bg-secondary);
    border-radius: 50%;
    cursor: pointer;
    font-size: 1.5rem;
    transition: transform 0.2s;
}

.theme-toggle:hover {
    transform: scale(1.1);
}

🎯 Quick Quiz

Question 1: What's the main benefit of CSS Modules?

Question 2: When should you use inline styles in React?

Question 3: What is Tailwind CSS?

🎯 Choosing the Right Approach

With so many options, how do you choose? Here's a practical guide based on different scenarios.

Decision Matrix

Scenario Recommended Approach Why?
Small project, learning Plain CSS or CSS Modules Simple, no extra dependencies
Rapid prototyping Tailwind CSS Fast development, no naming
Component library CSS Modules or Styled Components Encapsulated, reusable styles
Dynamic theming CSS-in-JS (Styled Components) Theme props, JavaScript power
Large team, design system Tailwind or CSS Modules Consistency, scalability
Marketing site Plain CSS or Tailwind Performance, SEO
Complex web app CSS Modules + Tailwind combo Structure + flexibility

A Quick Decision Flow

When you're not sure, this flowchart gets you to a sensible default in three questions:

flowchart TD A["Styling a React component"] --> B{"Value changes at
runtime (props/state)?"} B -->|"Yes, lots of it"| D["Inline style prop
(typed React.CSSProperties)"] B -->|"Mostly static"| C{"Want zero-config
utility classes?"} C -->|"Yes"| E["Tailwind CSS"] C -->|"No, prefer real CSS"| F["CSS Modules"] D --> G["Ship it"] E --> G F --> G style D fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff style E fill:#06B6D4,stroke:#333,stroke-width:2px,color:#fff style F fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff style G fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff

Notice runtime CSS-in-JS isn't the default recommendation anymore β€” reach for it only when you specifically need JavaScript-driven theming and you're not in a React Server Components app.

Mixing Approaches

You can combine multiple styling methods in one project:

// Global styles with plain CSS
import './index.css';

// Component styles with CSS Modules
import styles from './Button.module.css';

// Dynamic styles with inline
const dynamicStyle = {
    width: `${percentage}%`
};

// Utility classes with Tailwind
function Component() {
    return (
        <div className="flex gap-4">  {/* Tailwind */}
            <button 
                className={styles.button}  {/* CSS Module */}
                style={dynamicStyle}        {/* Inline */}
            >
                Click me
            </button>
        </div>
    );
}

Performance Considerations

  • Best Performance: Plain CSS, CSS Modules (compiled at build time)
  • Good Performance: Tailwind (purges unused styles)
  • Runtime Overhead: CSS-in-JS (generates styles in browser)
  • Small Impact: Inline styles (for dynamic values only)

Team Considerations

  • Junior developers: Plain CSS or Tailwind (familiar concepts)
  • Mixed CSS/JS skills: CSS Modules (separation of concerns)
  • JavaScript-focused team: CSS-in-JS (everything in JS)
  • Large teams: Design system + Tailwind (consistency)

βœ… Our Recommendation for Beginners

Start with CSS Modules - they offer the best balance of:

  • Familiar CSS syntax
  • Automatic scoping
  • No new dependencies
  • Great performance
  • Easy to learn

Once comfortable, explore Tailwind for speed or styled-components for advanced needs.

πŸ“ Summary

πŸŽ‰ Congratulations!

You've mastered styling in React! Here's what you now know:

  • Styling approaches - Plain CSS, CSS Modules, inline, CSS-in-JS, Tailwind
  • CSS Modules - Scoped styles with .module.css files
  • Inline styles - JavaScript objects for dynamic styling
  • Conditional styling - Applying styles based on props/state
  • CSS-in-JS - styled-components and theme providers
  • Tailwind CSS - Utility-first rapid development
  • Responsive design - Media queries and mobile-first approach
  • Choosing approaches - When to use each method

🎯 Key Takeaways

  • No single "best" approach - choose based on needs
  • CSS Modules prevent naming conflicts automatically
  • Type your inline styles with React.CSSProperties
  • Runtime CSS-in-JS is fading β€” prefer zero-runtime tools
  • Tailwind accelerates development
  • You can mix multiple approaches
  • Mobile-first is the modern standard
  • Performance matters - choose wisely

Quick Reference

Method Syntax Best For
Plain CSS className="btn" Global styles, simple projects
CSS Modules className={styles.btn} Component-scoped styles
Inline style={{'{color: "red"}'}} Dynamic values
Styled Components const Btn = styled.button`` Theming, CSS-in-JS
Tailwind className="px-4 py-2" Rapid development

πŸ“š Additional Resources

πŸš€ What's Next?

In the next lesson, we'll explore Events in React. You'll learn:

  • Handling user interactions (clicks, inputs, forms)
  • Event handling best practices
  • Synthetic events in React
  • Event delegation and bubbling
  • Form handling and validation
  • Keyboard and accessibility events

Your components look great - now let's make them interactive! 🎯

πŸŽ‰ Styling Maestro!

You now have a complete toolkit for styling React applications! From traditional CSS to modern utility-first frameworks, you can choose the right tool for any project. Your components will look professional and polished!

Time to make them interactive! πŸš€