Styling in React
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.cssfiles 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
classNamefor 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.