CSS in React
CSS Styling in React
React provides several ways to style components using CSS. This tutorial covers various approaches from basic CSS files to inline styles, each with their own benefits and use cases.
Traditional CSS Files
Basic CSS Import
// styles.css
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.title {
color: #333;
font-size: 2rem;
margin-bottom: 1rem;
}
.button {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button:hover {
background-color: #0056b3;
}
// App.js
import './styles.css';
function App() {
return (
<div className="container">
<h1 className="title">Welcome to React</h1>
<button className="button">Click me</button>
</div>
);
}
Component-Specific CSS
// Button.css
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.btn-large {
padding: 15px 30px;
font-size: 18px;
}
// Button.js
import './Button.css';
function Button({ variant = 'primary', size = 'normal', children, ...props }) {
const classNames = [
'btn',
`btn-${variant}`,
size === 'large' && 'btn-large'
].filter(Boolean).join(' ');
return (
<button className={classNames} {...props}>
{children}
</button>
);
}
Inline Styles
Basic Inline Styles
function InlineStyleExample() {
const styles = {
container: {
backgroundColor: '#f8f9fa',
padding: '20px',
borderRadius: '8px'
},
heading: {
color: '#343a40',
fontSize: '24px',
marginBottom: '10px'
},
text: {
color: '#6c757d',
lineHeight: '1.6'
}
};
return (
<div style={styles.container}>
<h2 style={styles.heading}>Inline Styles</h2>
<p style={styles.text}>
This component uses inline styles defined as JavaScript objects.
</p>
</div>
);
}
Dynamic Inline Styles
function DynamicStyles({ theme = 'light', size = 'medium' }) {
const themes = {
light: {
backgroundColor: '#ffffff',
color: '#333333'
},
dark: {
backgroundColor: '#333333',
color: '#ffffff'
}
};
const sizes = {
small: { padding: '5px 10px', fontSize: '14px' },
medium: { padding: '10px 20px', fontSize: '16px' },
large: { padding: '15px 30px', fontSize: '18px' }
};
const combinedStyles = {
...themes[theme],
...sizes[size],
border: '1px solid #ddd',
borderRadius: '4px',
transition: 'all 0.3s ease'
};
return (
<div style={combinedStyles}>
Dynamic styled component
</div>
);
}
CSS Classes with Conditions
Conditional Classes
function ConditionalClasses({ isActive, isDisabled, size }) {
// Manual string concatenation
const className = `
button
${isActive ? 'button-active' : ''}
${isDisabled ? 'button-disabled' : ''}
${size ? `button-${size}` : ''}
`.trim();
return <button className={className}>Button</button>;
}
// Using array and join
function BetterConditionalClasses({ isActive, isDisabled, size }) {
const classes = [
'button',
isActive && 'button-active',
isDisabled && 'button-disabled',
size && `button-${size}`
].filter(Boolean).join(' ');
return <button className={classes}>Button</button>;
}
Utility Function for Classes
// classNames utility
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
}
// Usage
function Component({ variant, size, isActive, className }) {
return (
<div
className={classNames(
'base-class',
variant && `variant-${variant}`,
size && `size-${size}`,
isActive && 'active',
className // Additional classes from props
)}
>
Content
</div>
);
}
CSS Variables
Using CSS Custom Properties
// styles.css
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--spacing-unit: 8px;
--border-radius: 4px;
}
.button {
background-color: var(--primary-color);
padding: calc(var(--spacing-unit) * 2);
border-radius: var(--border-radius);
}
// Dynamic CSS Variables
function ThemedComponent({ primaryColor, spacing }) {
const style = {
'--primary-color': primaryColor || '#007bff',
'--spacing': `${spacing}px` || '8px'
};
return (
<div style={style} className="themed-container">
<button className="themed-button">
Themed Button
</button>
</div>
);
}
Responsive Design
Media Queries in CSS
// responsive.css
.container {
width: 100%;
padding: 20px;
}
.grid {
display: grid;
gap: 20px;
grid-template-columns: 1fr;
}
/* Tablet */
@media (min-width: 768px) {
.container {
max-width: 750px;
margin: 0 auto;
}
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
}
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Large Desktop */
@media (min-width: 1440px) {
.grid {
grid-template-columns: repeat(4, 1fr);
}
}
Responsive Utilities
// Responsive component
function ResponsiveComponent() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const isMobile = windowSize.width < 768;
const isTablet = windowSize.width >= 768 && windowSize.width < 1024;
const isDesktop = windowSize.width >= 1024;
return (
<div className={`
component
${isMobile ? 'component-mobile' : ''}
${isTablet ? 'component-tablet' : ''}
${isDesktop ? 'component-desktop' : ''}
`}>
{isMobile && <MobileLayout />}
{isTablet && <TabletLayout />}
{isDesktop && <DesktopLayout />}
</div>
);
}
Animations and Transitions
CSS Animations
// animations.css
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
.slide-in {
animation: slideIn 0.3s ease-out;
}
.pulse {
animation: pulse 2s infinite;
}
// Component with animations
function AnimatedComponent({ animate }) {
return (
<div className={animate ? 'box fade-in' : 'box'}>
<h2 className="slide-in">Animated Content</h2>
<button className="pulse">Pulsing Button</button>
</div>
);
}
Transition Effects
// transitions.css
.card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.expand-collapse {
overflow: hidden;
transition: max-height 0.3s ease;
}
.expand-collapse.collapsed {
max-height: 0;
}
.expand-collapse.expanded {
max-height: 500px; /* Adjust based on content */
}
// Expandable component
function ExpandableCard({ title, children }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="card">
<h3 onClick={() => setIsExpanded(!isExpanded)}>
{title} {isExpanded ? '−' : '+'}
</h3>
<div className={`expand-collapse ${isExpanded ? 'expanded' : 'collapsed'}`}>
<div className="content">
{children}
</div>
</div>
</div>
);
}
Theme Support
CSS-Based Theming
// themes.css
/* Light theme (default) */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--text-primary: #212529;
--text-secondary: #6c757d;
--border-color: #dee2e6;
}
/* Dark theme */
[data-theme="dark"] {
--bg-primary: #212529;
--bg-secondary: #343a40;
--text-primary: #f8f9fa;
--text-secondary: #adb5bd;
--border-color: #495057;
}
.app {
background-color: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
}
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
color: var(--text-primary);
}
// Theme provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<div className="app">
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} theme
</button>
{children}
</div>
);
}
Complex Styling Patterns
Compound Component Styles
// Card.css
.card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.card-header {
background-color: #f8f9fa;
padding: 15px 20px;
border-bottom: 1px solid #ddd;
}
.card-body {
padding: 20px;
}
.card-footer {
background-color: #f8f9fa;
padding: 15px 20px;
border-top: 1px solid #ddd;
}
// Card components
function Card({ children, className }) {
return (
<div className={`card ${className || ''}`}>
{children}
</div>
);
}
Card.Header = function CardHeader({ children }) {
return <div className="card-header">{children}</div>;
};
Card.Body = function CardBody({ children }) {
return <div className="card-body">{children}</div>;
};
Card.Footer = function CardFooter({ children }) {
return <div className="card-footer">{children}</div>;
};
// Usage
function Example() {
return (
<Card>
<Card.Header>Card Title</Card.Header>
<Card.Body>Card content goes here</Card.Body>
<Card.Footer>Card footer</Card.Footer>
</Card>
);
}
Dynamic Styling with Props
function DynamicButton({
variant = 'primary',
size = 'medium',
fullWidth = false,
rounded = false,
children
}) {
const baseStyles = {
border: 'none',
cursor: 'pointer',
fontWeight: '500',
transition: 'all 0.3s ease',
display: 'inline-block',
textAlign: 'center'
};
const variants = {
primary: {
backgroundColor: '#007bff',
color: 'white'
},
secondary: {
backgroundColor: '#6c757d',
color: 'white'
},
outline: {
backgroundColor: 'transparent',
color: '#007bff',
border: '2px solid #007bff'
}
};
const sizes = {
small: {
padding: '5px 10px',
fontSize: '14px'
},
medium: {
padding: '10px 20px',
fontSize: '16px'
},
large: {
padding: '15px 30px',
fontSize: '18px'
}
};
const style = {
...baseStyles,
...variants[variant],
...sizes[size],
width: fullWidth ? '100%' : 'auto',
borderRadius: rounded ? '50px' : '4px'
};
return (
<button style={style}>
{children}
</button>
);
}
Best Practices
- Organize styles - Keep related styles together
- Use CSS variables - For consistent theming
- Mobile-first approach - Start with mobile styles
- Avoid inline styles - Use for dynamic values only
- Namespace classes - Prevent conflicts with BEM or similar
- Optimize for performance - Minimize re-renders from style changes
- Use CSS-in-JS - For component-scoped styles (covered in other tutorials)
- Consistent naming - Follow a naming convention
CSS styling in React is flexible and powerful. Choose the approach that best fits your project's needs!