React.js Fundamentals

Styling in React

18 min Lesson 18 of 40

Introduction to Styling in React

React offers multiple approaches to styling components, each with unique advantages. This lesson explores inline styles, CSS Modules, styled-components, Tailwind CSS, and CSS-in-JS libraries, helping you choose the right approach for your projects.

Traditional CSS with className

The simplest approach uses regular CSS files with the className prop:

// src/components/Button.jsx
import './Button.css';

function Button({ children, variant = 'primary' }) {
  return (
    <button className={`btn btn-${variant}`}>
      {children}
    </button>
  );
}

export default Button;
/* src/components/Button.css */
.btn {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 0.375rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-primary {
  background-color: #3b82f6;
  color: white;
}

.btn-primary:hover {
  background-color: #2563eb;
}

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

.btn-secondary:hover {
  background-color: #4b5563;
}

Warning: Traditional CSS has global scope. Class names can conflict across components. Use naming conventions like BEM or CSS Modules to avoid conflicts.

CSS Modules: Scoped Styling

CSS Modules automatically scope styles to components, preventing naming conflicts:

// src/components/Card.jsx
import styles from './Card.module.css';

function Card({ title, content, featured }) {
  return (
    <div className={`${styles.card} ${featured ? styles.featured : ''}`}>
      <h3 className={styles.title}>{title}</h3>
      <p className={styles.content}>{content}</p>
    </div>
  );
}

// Alternative: using array join
function Card2({ title, content, featured }) {
  const cardClasses = [
    styles.card,
    featured && styles.featured
  ].filter(Boolean).join(' ');

  return (
    <div className={cardClasses}>
      <h3 className={styles.title}>{title}</h3>
      <p className={styles.content}>{content}</p>
    </div>
  );
}

export default Card;
/* src/components/Card.module.css */
.card {
  background: white;
  border-radius: 0.5rem;
  padding: 1.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s, box-shadow 0.2s;
}

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

.featured {
  border: 2px solid #3b82f6;
  background: linear-gradient(to bottom right, #eff6ff, white);
}

.title {
  font-size: 1.25rem;
  font-weight: 700;
  color: #1f2937;
  margin-bottom: 0.5rem;
}

.content {
  color: #6b7280;
  line-height: 1.6;
}

Tip: CSS Modules work with Create React App and Vite out of the box. Just name your files *.module.css.

Inline Styles: JavaScript Objects

React supports inline styles using JavaScript objects with camelCase properties:

function InlineStylesExample() {
  const containerStyle = {
    backgroundColor: '#f3f4f6',
    padding: '2rem',
    borderRadius: '0.5rem',
    maxWidth: '600px',
    margin: '0 auto'
  };

  const headingStyle = {
    fontSize: '2rem',
    fontWeight: 'bold',
    color: '#1f2937',
    marginBottom: '1rem'
  };

  const textStyle = {
    color: '#4b5563',
    lineHeight: 1.6
  };

  return (
    <div style={containerStyle}>
      <h2 style={headingStyle}>Inline Styles</h2>
      <p style={textStyle}>This component uses inline styles.</p>
    </div>
  );
}

// Dynamic inline styles based on props
function Alert({ type, message }) {
  const baseStyle = {
    padding: '1rem',
    borderRadius: '0.375rem',
    marginBottom: '1rem',
    border: '1px solid'
  };

  const typeStyles = {
    success: {
      backgroundColor: '#d1fae5',
      borderColor: '#10b981',
      color: '#065f46'
    },
    error: {
      backgroundColor: '#fee2e2',
      borderColor: '#ef4444',
      color: '#991b1b'
    },
    warning: {
      backgroundColor: '#fef3c7',
      borderColor: '#f59e0b',
      color: '#92400e'
    }
  };

  const alertStyle = { ...baseStyle, ...typeStyles[type] };

  return <div style={alertStyle}>{message}</div>;
}

Note: Inline styles don't support pseudo-classes (:hover, :focus) or media queries. Use CSS/CSS Modules for these features.

styled-components: CSS-in-JS

styled-components is a popular CSS-in-JS library that allows you to write CSS directly in JavaScript:

// Installation
npm install styled-components

// src/components/StyledButton.jsx
import styled from 'styled-components';

// Basic styled component
const StyledButton = styled.button`
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 0.375rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
  background-color: #3b82f6;
  color: white;

  &:hover {
    background-color: #2563eb;
    transform: translateY(-1px);
  }

  &:active {
    transform: translateY(0);
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

// Styled component with props
const Button = styled.button`
  padding: ${props => props.size === 'large' ? '1rem 2rem' : '0.5rem 1rem'};
  background-color: ${props => {
    switch(props.variant) {
      case 'primary': return '#3b82f6';
      case 'success': return '#10b981';
      case 'danger': return '#ef4444';
      default: return '#6b7280';
    }
  }};
  color: white;
  border: none;
  border-radius: 0.375rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;

  &:hover {
    opacity: 0.9;
    transform: translateY(-2px);
  }
`;

// Extending styled components
const IconButton = styled(Button)`
  display: flex;
  align-items: center;
  gap: 0.5rem;

  svg {
    width: 1.25rem;
    height: 1.25rem;
  }
`;

// Usage
function App() {
  return (
    <div>
      <StyledButton>Click Me</StyledButton>
      <Button variant="primary" size="large">Primary</Button>
      <Button variant="success">Success</Button>
      <Button variant="danger">Danger</Button>
      <IconButton variant="primary">
        <span>➤</span> With Icon
      </IconButton>
    </div>
  );
}

Best Practice: Create a theme object for consistent colors, spacing, and typography across styled-components.

Tailwind CSS with React

Tailwind is a utility-first CSS framework that provides pre-built classes:

// Installation for Vite
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

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

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

// src/components/TailwindCard.jsx
function TailwindCard({ title, description, image, featured }) {
  return (
    <div className={`
      rounded-lg shadow-md overflow-hidden transition-all duration-300
      hover:shadow-xl hover:-translate-y-1
      ${featured ? 'ring-2 ring-blue-500 bg-gradient-to-br from-blue-50 to-white' : 'bg-white'}
    `}>
      <img
        src={image}
        alt={title}
        className="w-full h-48 object-cover"
      />
      <div className="p-6">
        <h3 className="text-xl font-bold text-gray-800 mb-2">
          {title}
        </h3>
        <p className="text-gray-600 leading-relaxed">
          {description}
        </p>
        <button className="
          mt-4 px-6 py-2 bg-blue-500 text-white font-semibold rounded-md
          hover:bg-blue-600 active:bg-blue-700 transition-colors
          focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
        ">
          Learn More
        </button>
      </div>
    </div>
  );
}

// Reusable button component with Tailwind
function Button({ children, variant = 'primary', size = 'md', ...props }) {
  const baseClasses = 'font-semibold rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';

  const variantClasses = {
    primary: 'bg-blue-500 hover:bg-blue-600 text-white focus:ring-blue-500',
    secondary: 'bg-gray-500 hover:bg-gray-600 text-white focus:ring-gray-500',
    success: 'bg-green-500 hover:bg-green-600 text-white focus:ring-green-500',
    danger: 'bg-red-500 hover:bg-red-600 text-white focus:ring-red-500',
    outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50 focus:ring-blue-500'
  };

  const sizeClasses = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg'
  };

  const className = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`;

  return (
    <button className={className} {...props}>
      {children}
    </button>
  );
}

Conditional and Dynamic Styling

Techniques for applying styles conditionally:

// Using template literals
function ConditionalStyling({ isActive, isDisabled, size }) {
  return (
    <button
      className={`
        btn
        ${isActive ? 'btn-active' : 'btn-inactive'}
        ${isDisabled ? 'btn-disabled' : ''}
        ${size === 'large' ? 'btn-lg' : 'btn-md'}
      `}
    >
      Click Me
    </button>
  );
}

// Using classnames library (recommended)
import classNames from 'classnames';

function BetterConditional({ isActive, isDisabled, size }) {
  const buttonClasses = classNames(
    'btn',
    {
      'btn-active': isActive,
      'btn-inactive': !isActive,
      'btn-disabled': isDisabled,
      'btn-lg': size === 'large',
      'btn-md': size === 'medium',
      'btn-sm': size === 'small'
    }
  );

  return <button className={buttonClasses}>Click Me</button>;
}

// Dynamic inline styles
function ProgressBar({ progress, color = 'blue' }) {
  const colorMap = {
    blue: '#3b82f6',
    green: '#10b981',
    red: '#ef4444'
  };

  return (
    <div style={{
      width: '100%',
      height: '1rem',
      backgroundColor: '#e5e7eb',
      borderRadius: '0.25rem',
      overflow: 'hidden'
    }}>
      <div style={{
        width: `${progress}%`,
        height: '100%',
        backgroundColor: colorMap[color],
        transition: 'width 0.3s ease'
      }} />
    </div>
  );
}

CSS Variables with React

Use CSS custom properties for theming:

// src/App.jsx
import { useState } from 'react';
import './theme.css';

function App() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <div data-theme={theme}>
      <button onClick={toggleTheme}>Toggle Theme</button>
      <div className="card">
        <h2>Themed Card</h2>
        <p>This card adapts to the theme.</p>
      </div>
    </div>
  );
}

/* src/theme.css */
:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f3f4f6;
  --text-primary: #1f2937;
  --text-secondary: #6b7280;
  --border-color: #e5e7eb;
  --accent: #3b82f6;
}

[data-theme="dark"] {
  --bg-primary: #1f2937;
  --bg-secondary: #111827;
  --text-primary: #f9fafb;
  --text-secondary: #d1d5db;
  --border-color: #374151;
  --accent: #60a5fa;
}

body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
}

.card {
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-color);
  padding: 1.5rem;
  border-radius: 0.5rem;
}

.card h2 {
  color: var(--accent);
}

Exercise 1: CSS Modules Todo App

Task: Create a todo list application using CSS Modules:

  • Build TodoItem, TodoList, and AddTodo components
  • Use separate *.module.css files for each component
  • Style completed todos differently (strikethrough, opacity)
  • Add hover effects on todo items
  • Create a priority system with different colors (high/medium/low)

Exercise 2: Tailwind Product Card

Task: Build a product card component with Tailwind CSS:

  • Include product image, title, price, rating, and "Add to Cart" button
  • Add responsive design (stacks on mobile, grid on desktop)
  • Implement a "featured" variant with different styling
  • Add hover effects and transitions
  • Create a discount badge for sale items

Exercise 3: Theme Switcher

Task: Build a theme switcher using CSS variables:

  • Create light, dark, and high-contrast themes
  • Store the selected theme in localStorage
  • Apply theme on initial load based on stored preference
  • Create a dropdown to select between themes
  • Theme should affect all components (buttons, cards, text)

Summary

In this lesson, you explored various styling approaches in React:

  • Traditional CSS with className for simple styling needs
  • CSS Modules for scoped, component-specific styles
  • Inline styles for dynamic, JavaScript-driven styling
  • styled-components for powerful CSS-in-JS with full CSS features
  • Tailwind CSS for rapid development with utility classes
  • Conditional styling techniques and dynamic class management
  • CSS variables for theming and design systems

In the next lesson, we'll dive into component lifecycle, performance optimization with React.memo, useMemo, and useCallback.