We are still cooking the magic in the way!
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.