Styling in React
Styling Approaches
| Approach | Pros | Cons |
|---|---|---|
| Inline styles | Simple, dynamic | No pseudo-classes, verbose |
| CSS files | Familiar, full CSS | Global scope, naming conflicts |
| CSS Modules | Scoped, no conflicts | Slightly verbose |
| Styled Components | Component-scoped, dynamic | Runtime overhead, learning curve |
| Tailwind CSS | Fast, consistent, no CSS files | Long class strings, learning curve |
// Button.module.css
// .btn { padding: 8px 16px; border-radius: 4px; cursor: pointer; }
// .primary { background: #3498db; color: white; }
// .danger { background: #e74c3c; color: white; }
// .sm { padding: 4px 8px; font-size: 0.875rem; }
// .lg { padding: 12px 24px; font-size: 1.125rem; }
// Button.jsx — CSS Modules
import styles from './Button.module.css'
function Button({ label, variant = 'primary', size = 'md' }) {
// CSS Modules auto-generates unique class names — no conflicts!
const className = [
styles.btn,
styles[variant], // styles.primary or styles.danger
styles[size], // styles.sm, styles.md, styles.lg
].filter(Boolean).join(' ')
return <button className={className}>{label}</button>
}
// With clsx library for conditional classes
import clsx from 'clsx' // npm install clsx
function Button2({ label, variant, size, disabled, loading }) {
return (
<button
className={clsx(
styles.btn,
styles[variant],
styles[size],
{ [styles.disabled]: disabled, [styles.loading]: loading }
)}
disabled={disabled || loading}
>
{label}
</button>
)
}
// npm install styled-components
import styled, { css, ThemeProvider } from 'styled-components'
// Basic styled component
const Button = styled.button`
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
border: none;
font-size: 1rem;
transition: all 0.2s;
/* Dynamic styles based on props */
background: ${props => props.variant === 'danger' ? '#e74c3c' : '#3498db'};
color: white;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Conditional CSS with css helper */
${props => props.size === 'lg' && css`
padding: 12px 24px;
font-size: 1.125rem;
`}
`
// Extend existing component
const DangerButton = styled(Button)`
background: #e74c3c;
&:hover { background: #c0392b; }
`
// Theme support
const theme = {
colors: { primary: '#3498db', danger: '#e74c3c' },
spacing: { sm: '4px', md: '8px', lg: '16px' }
}
function App() {
return (
<ThemeProvider theme={theme}>
<Button variant="primary">Save</Button>
<DangerButton>Delete</DangerButton>
<Button size="lg">Large Button</Button>
</ThemeProvider>
)
}
// Tailwind CSS — utility-first CSS framework
// npm install -D tailwindcss postcss autoprefixer
// npx tailwindcss init -p
// tailwind.config.js
// content: ['./src/**/*.{js,jsx,ts,tsx}']
// Button with Tailwind
function Button({ label, variant = 'primary', size = 'md', disabled }) {
const baseClasses = 'rounded font-medium transition-all duration-200 cursor-pointer'
const variantClasses = {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-500 text-white hover:bg-red-600',
}
const sizeClasses = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
}
return (
<button
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
disabled={disabled}
>
{label}
</button>
)
}
// With clsx + tailwind-merge (handles conflicting classes)
import clsx from 'clsx'
import { twMerge } from 'tailwind-merge' // npm install tailwind-merge
function cn(...inputs) {
return twMerge(clsx(inputs))
}
function Card({ className, children }) {
return (
<div className={cn(
'rounded-lg border bg-white p-6 shadow-sm',
className // allows overriding from parent
)}>
{children}
</div>
)
}
// Usage
<Card className="bg-blue-50 border-blue-200">
Custom card
</Card>
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.