Tailwind CSS vs CSS Modules vs Styled Components: Mana yang Terbaik?
Selasa, 23 Des 2025
Pernah nggak sih kamu bingung mau pakai styling approach yang mana untuk project React? Tailwind CSS lagi hype banget, CSS Modules udah proven dan stabil, Styled Components juga punya fan base yang solid. Masing-masing punya kelebihan dan kekurangan tersendiri.
Di artikel ini, kita bakal bahas tuntas perbandingan ketiga approach ini. Bukan cuma teori, tapi juga contoh kode nyata dan rekomendasi kapan harus pakai yang mana.
Kenapa Pemilihan Styling Approach Itu Penting?
Styling approach yang kamu pilih bakal mempengaruhi banyak hal:
- Developer Experience (DX) - Seberapa nyaman tim kamu bekerja sehari-hari
- Performance - Bundle size dan runtime performance aplikasi
- Maintainability - Seberapa mudah kode di-maintain dalam jangka panjang
- Scalability - Apakah approach ini cocok untuk project besar?
Salah pilih di awal bisa bikin refactoring yang menyakitkan di kemudian hari. Jadi, let’s dive in!
Overview Masing-Masing Approach
Tailwind CSS
Tailwind CSS adalah utility-first CSS framework. Instead of writing custom CSS, kamu pakai utility classes langsung di HTML/JSX.
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Click Me
</button>
Filosofi: “Why write CSS when you can compose utilities?”
CSS Modules
CSS Modules adalah CSS biasa, tapi dengan scoped class names. Setiap class di-hash secara otomatis sehingga nggak ada konflik naming.
/* Button.module.css */
.button {
background-color: #3b82f6;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
import styles from './Button.module.css';
<button className={styles.button}>Click Me</button>
Filosofi: “CSS yang kamu kenal, tapi lebih aman.”
Styled Components
Styled Components adalah CSS-in-JS library yang memungkinkan kamu menulis CSS langsung di JavaScript menggunakan tagged template literals.
import styled from 'styled-components';
const Button = styled.button`
background-color: #3b82f6;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
&:hover {
background-color: #2563eb;
}
`;
<Button>Click Me</Button>
Filosofi: “Component-driven styling dengan full power of JavaScript.”
Comparison Table
| Aspek | Tailwind CSS | CSS Modules | Styled Components |
|---|---|---|---|
| Bundle Size | Kecil (dengan purge) | Kecil | Lebih besar (~12KB) |
| Runtime Performance | Excellent | Excellent | Sedikit overhead |
| Learning Curve | Medium | Low | Medium |
| Developer Experience | Great (setelah terbiasa) | Good | Great |
| Type Safety | Limited | Limited | Good (with TS) |
| Dynamic Styling | Via class toggling | Via class toggling | Native support |
| Theming | Config-based | Manual | Built-in |
| SSR Support | Native | Native | Perlu setup |
| Tooling | VS Code extension, Prettier | Built-in support | VS Code extension |
Contoh Kode: Button Component
Mari kita lihat bagaimana membuat button component yang sama dengan ketiga approach.
Tailwind CSS Button
// Button.jsx
const Button = ({ variant = 'primary', size = 'md', children, ...props }) => {
const baseStyles = 'font-semibold rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2';
const variants = {
primary: 'bg-blue-500 hover:bg-blue-600 text-white focus:ring-blue-500',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800 focus:ring-gray-500',
danger: 'bg-red-500 hover:bg-red-600 text-white focus:ring-red-500',
};
const sizes = {
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={`${baseStyles} ${variants[variant]} ${sizes[size]}`}
{...props}
>
{children}
</button>
);
};
// Usage
<Button variant="primary" size="md">Click Me</Button>
<Button variant="danger" size="lg">Delete</Button>
CSS Modules Button
/* Button.module.css */
.button {
font-weight: 600;
border-radius: 0.5rem;
transition: background-color 0.2s;
border: none;
cursor: pointer;
}
.button:focus {
outline: none;
box-shadow: 0 0 0 2px offset, 0 0 0 4px var(--ring-color);
}
/* Variants */
.primary {
background-color: #3b82f6;
color: white;
--ring-color: #3b82f6;
}
.primary:hover {
background-color: #2563eb;
}
.secondary {
background-color: #e5e7eb;
color: #1f2937;
--ring-color: #6b7280;
}
.secondary:hover {
background-color: #d1d5db;
}
.danger {
background-color: #ef4444;
color: white;
--ring-color: #ef4444;
}
.danger:hover {
background-color: #dc2626;
}
/* Sizes */
.sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
.md {
padding: 0.5rem 1rem;
font-size: 1rem;
}
.lg {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
// Button.jsx
import styles from './Button.module.css';
const Button = ({ variant = 'primary', size = 'md', children, ...props }) => {
const classNames = [
styles.button,
styles[variant],
styles[size],
].join(' ');
return (
<button className={classNames} {...props}>
{children}
</button>
);
};
// Usage
<Button variant="primary" size="md">Click Me</Button>
<Button variant="danger" size="lg">Delete</Button>
Styled Components Button
// Button.jsx
import styled, { css } from 'styled-components';
const variantStyles = {
primary: css`
background-color: #3b82f6;
color: white;
--ring-color: #3b82f6;
&:hover {
background-color: #2563eb;
}
`,
secondary: css`
background-color: #e5e7eb;
color: #1f2937;
--ring-color: #6b7280;
&:hover {
background-color: #d1d5db;
}
`,
danger: css`
background-color: #ef4444;
color: white;
--ring-color: #ef4444;
&:hover {
background-color: #dc2626;
}
`,
};
const sizeStyles = {
sm: css`
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
`,
md: css`
padding: 0.5rem 1rem;
font-size: 1rem;
`,
lg: css`
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
`,
};
const StyledButton = styled.button`
font-weight: 600;
border-radius: 0.5rem;
transition: background-color 0.2s;
border: none;
cursor: pointer;
&:focus {
outline: none;
box-shadow: 0 0 0 2px white, 0 0 0 4px var(--ring-color);
}
${({ variant }) => variantStyles[variant]}
${({ size }) => sizeStyles[size]}
`;
const Button = ({ variant = 'primary', size = 'md', children, ...props }) => (
<StyledButton variant={variant} size={size} {...props}>
{children}
</StyledButton>
);
// Usage
<Button variant="primary" size="md">Click Me</Button>
<Button variant="danger" size="lg">Delete</Button>
Kelebihan dan Kekurangan
Tailwind CSS
Kelebihan:
- ⚡ Development speed yang sangat cepat setelah terbiasa
- 📦 Bundle size kecil (unused classes di-purge)
- 🎨 Konsistensi design lewat config
- 🔧 Nggak perlu context switching antara file CSS dan JSX
- 📱 Responsive dan state variants built-in (
hover:,md:, dll)
Kekurangan:
- 📚 Learning curve untuk menghapal utility classes
- 🤮 “Ugly” HTML dengan banyak classes (subjektif)
- 🔄 Complex dynamic styles butuh workaround
- 📝 Repetisi classes untuk component yang sama
CSS Modules
Kelebihan:
- ✅ CSS yang familiar, nggak perlu belajar hal baru
- 🔒 Scoped styles by default, no naming conflicts
- 📦 Zero runtime overhead
- 🛠️ Great tooling support out of the box
- 🎯 Clear separation of concerns
Kekurangan:
- 📁 File terpisah untuk setiap component
- 🔄 Dynamic styling agak tricky
- 🎨 Theming perlu setup manual
- 📝 Naming conventions perlu disepakati tim
Styled Components
Kelebihan:
- 🧩 True component-driven styling
- 🔄 Dynamic styling dengan props sangat powerful
- 🎨 Theming built-in dan mudah
- 📦 Automatic critical CSS extraction
- 🔍 Dead code elimination otomatis
Kekurangan:
- 📦 Bundle size lebih besar (~12KB gzipped)
- ⚡ Runtime overhead untuk style injection
- 🔧 SSR setup bisa tricky
- 📚 Perlu belajar API dan patterns baru
- 🐛 Debugging bisa lebih challenging
Kapan Pakai yang Mana?
Pilih Tailwind CSS kalau:
- Kamu mau development speed yang cepat
- Project-mu butuh konsistensi design yang ketat
- Tim-mu sudah familiar atau mau invest waktu untuk belajar
- Kamu build landing pages atau marketing sites
- Kamu suka approach utility-first
Pilih CSS Modules kalau:
- Tim-mu sudah jago CSS dan nggak mau belajar hal baru
- Kamu butuh zero runtime overhead
- Project legacy yang mau di-modernize gradually
- Kamu prefer separation of concerns yang jelas
- SSR performance adalah prioritas utama
Pilih Styled Components kalau:
- Kamu build component library atau design system
- Dynamic theming adalah requirement penting
- Kamu suka colocate styles dengan component logic
- Tim-mu comfortable dengan CSS-in-JS paradigm
- Kamu butuh prop-based styling yang powerful
Kombinasi Approaches
Plot twist: kamu nggak harus pilih satu!
Banyak tim sukses mengkombinasikan beberapa approach:
Tailwind + CSS Modules
// Tailwind untuk utility, CSS Modules untuk complex styles
import styles from './Card.module.css';
const Card = ({ children }) => (
<div className={`${styles.card} p-4 rounded-lg shadow-md`}>
{children}
</div>
);
Tailwind + Styled Components
import styled from 'styled-components';
import tw from 'twin.macro'; // Library untuk combine Tailwind dengan SC
const Button = styled.button`
${tw`bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded`}
/* Custom styles yang nggak ada di Tailwind */
animation: pulse 2s infinite;
`;
Tips Kombinasi:
- Tailwind untuk layout dan spacing - Utility classes perfect untuk ini
- CSS Modules atau SC untuk complex components - Animations, complex states
- Global CSS untuk typography dan resets - Base styles yang berlaku everywhere
Kesimpulan dan Rekomendasi
Setelah panjang lebar membahas ketiganya, here’s my take:
Untuk project baru di 2024-2025, Tailwind CSS adalah pilihan default yang solid. Ekosistem-nya mature, DX-nya excellent (setelah terbiasa), dan performance-nya top-notch.
Tapi, ini bukan berarti yang lain jelek:
- CSS Modules tetap pilihan yang safe dan proven, terutama kalau tim-mu sudah jago CSS tradisional
- Styled Components masih excellent untuk design systems dan apps yang butuh dynamic theming kompleks
Yang terpenting adalah konsistensi dalam satu project. Pilih satu approach utama, dan stick with it. Mixing terlalu banyak approach bisa bikin codebase jadi messy.
Quick Decision Framework:
Mau cepat develop? → Tailwind CSS
Tim jago CSS tradisional? → CSS Modules
Butuh dynamic theming kompleks? → Styled Components
Nggak yakin? → Tailwind CSS (safest bet di 2024)
Gimana? Udah ada gambaran mau pakai yang mana? Share di kolom komentar approach favorit kamu dan kenapa!
Happy coding! 🚀