CSS Styling Methods: From Vanilla to CSS-in-JS
Each styling method has strengths. Understanding trade-offs helps choose the right approach.
Quick Navigation: Vanilla CSS • CSS Modules • Tailwind CSS • CSS-in-JS • Comparison & Decision Matrix
Quick Decision Guide
Styling Methods Comparison:
Vanilla CSS: Simple, no build step. Best for small projects.
CSS Modules: Scoped styles, no runtime. Great for React/Vue.
Tailwind: Utility classes, rapid development. Popular choice.
CSS-in-JS: Dynamic styles, component-coupled. Adds runtime overhead.
Component Libraries: Pre-built components. Fast but opinionated.
Choose based on: Project size, team preference, performance needs.
Vanilla CSS
Traditional Stylesheets
/* styles.css */
.button {
background: #007bff;
color: white;
padding: 0.5rem 1rem;
border-radius: 4px;
}
.button:hover {
background: #0056b3;
}<link rel="stylesheet" href="styles.css">
<button class="button">Click me</button>Pros & Cons
Pros:
Cons:
Best for: Simple sites, static pages, small projects
CSS Modules
Scoped CSS Files
/* Button.module.css */
.button {
background: #007bff;
padding: 0.5rem 1rem;
}
.primary {
background: #007bff;
}
.secondary {
background: #6c757d;
}// Button.jsx
import styles from './Button.module.css';
function Button({ variant = 'primary' }) {
return (
<button className={styles[variant]}>
Click me
</button>
);
}Generated HTML:
<button class="Button_primary__a3xZ">Click me</button>Pros & Cons
Pros:
Cons:
Best for: React/Vue apps, team familiar with CSS
Tailwind CSS
Utility-First Framework
function Button({ variant = 'primary' }) {
const baseClasses = "px-4 py-2 rounded font-semibold";
const variantClasses = {
primary: "bg-blue-500 hover:bg-blue-700 text-white",
secondary: "bg-gray-500 hover:bg-gray-700 text-white"
};
return (
<button className={`${baseClasses} ${variantClasses[variant]}`}>
Click me
</button>
);
}With clsx/cn Utility
import { clsx } from 'clsx';
<button className={clsx(
'px-4 py-2 rounded',
variant === 'primary' && 'bg-blue-500',
variant === 'secondary' && 'bg-gray-500',
disabled && 'opacity-50 cursor-not-allowed'
)}>
Click me
</button>Pros & Cons
Pros:
Cons:
Best for: Fast prototyping, teams wanting consistency
CSS-in-JS
Styled-Components
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.variant === 'primary' ? '#007bff' : '#6c757d'};
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
&:hover {
opacity: 0.9;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
// Usage
<Button variant="primary">Click me</Button>
<Button variant="secondary" disabled>Disabled</Button>Emotion
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const buttonStyle = css`
background: #007bff;
padding: 0.5rem 1rem;
border-radius: 4px;
`;
<button css={buttonStyle}>Click me</button>Pros & Cons
Pros:
Cons:
Best for: Apps needing dynamic theming, prop-based styles
Comparison & Decision Matrix
Performance Comparison
Method | Bundle Size | Runtime Cost | Build Time
----------------|-------------|--------------|------------
Vanilla CSS | Small | None | None
CSS Modules | Small | None | Fast
Tailwind | Small* | None | Fast
CSS-in-JS | Large | High | Slow
* After purging unused stylesDecision Matrix
Choose Vanilla CSS when:
Choose CSS Modules when:
Choose Tailwind when:
Choose CSS-in-JS when:
Hybrid Approaches
// Tailwind + CSS Modules for custom styles
import styles from './Button.module.css';
<button className={`${styles.custom} px-4 py-2 bg-blue-500`}>
Hybrid
</button>
// Tailwind + Styled-components for dynamic needs
import styled from 'styled-components';
const Button = styled.button.attrs({
className: 'px-4 py-2 rounded'
})`
background: ${props => props.theme.primary};
`;