Tailwind CSS thay đổi cách viết CSS. Utility classes thay vì custom CSS — nhanh hơn, nhất quán hơn, dễ maintain hơn.
Utility-First#
Thay vì: đặt tên class → mở file CSS → viết rules
<!-- ❌ Cách cũ -->
<div class="card">
<h2 class="card-title">Hello</h2>
</div>html.card { background: white; border-radius: 8px; padding: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.card-title { font-size: 1.25rem; font-weight: 600; color: #1a1a1a; }cssVới Tailwind — dùng class trực tiếp trong HTML:
<!-- ✅ Tailwind -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<h2 class="text-xl font-semibold text-gray-900">Hello</h2>
</div>htmlResponsive Design#
<!-- Mobile-first: mặc định cho mobile, sm: cho tablet, lg: cho desktop -->
<div class="
grid
grid-cols-1
sm:grid-cols-2
lg:grid-cols-3
xl:grid-cols-4
gap-4
">htmlBreakpoints mặc định:
sm: 640px → @media (min-width: 640px)
md: 768px
lg: 1024px
xl: 1280px
2xl: 1536pxcssDark Mode#
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<h1 class="text-2xl font-bold">Content</h1>
</div>htmlConfig tailwind.config.ts:
export default {
darkMode: 'class', // Dùng class toggle — không theo system
// hoặc
darkMode: 'media', // Theo system preference
};typescriptCustom Theme#
// tailwind.config.ts
import type { Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{astro,tsx,html}'],
theme: {
extend: {
colors: {
brand: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
900: '#1e3a5f',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
spacing: {
'18': '4.5rem',
'88': '22rem',
},
animation: {
'fade-in': 'fadeIn 0.3s ease-in-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
},
},
},
plugins: [],
} satisfies Config;typescriptComponents — Tái Sử Dụng#
Với React/Astro — tách thành component:
// Button.tsx
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
ghost: 'text-gray-600 hover:bg-gray-100',
};
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
export function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
return (
<button
className={`
inline-flex items-center justify-center
font-medium rounded-lg
transition-colors duration-200
focus:outline-none focus:ring-2 focus:ring-blue-500
disabled:opacity-50 disabled:cursor-not-allowed
${variants[variant]}
${sizes[size]}
`}
>
{children}
</button>
);
}tsxLayout Ví Dụ#
<!-- Card -->
<div class="max-w-sm rounded-xl overflow-hidden shadow-lg bg-white">
<img class="w-full h-48 object-cover" src="..." alt="..." />
<div class="p-6">
<div class="flex items-center gap-2 mb-2">
<span class="px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full">
Technology
</span>
</div>
<h3 class="text-xl font-semibold text-gray-900 mb-2">Title</h3>
<p class="text-gray-600 text-sm leading-relaxed">Description...</p>
<div class="mt-4 flex items-center gap-3">
<img class="w-8 h-8 rounded-full" src="avatar.jpg" alt="" />
<span class="text-sm text-gray-500">Alice • 3 min read</span>
</div>
</div>
</div>
<!-- Navbar -->
<nav class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center gap-8">
<span class="text-xl font-bold text-gray-900">Logo</span>
<div class="hidden md:flex items-center gap-6">
<a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Blog</a>
</div>
</div>
<button class="md:hidden p-2">☰</button>
</div>
</div>
</nav>htmlOptimization — Purge CSS#
Tailwind tự động loại bỏ CSS không dùng trong production:
export default {
content: [
'./src/**/*.{astro,tsx,html}',
'./node_modules/my-library/**/*.tsx', // Include cả library
],
};typescriptKết quả: file CSS thường < 10KB sau khi gzip.
Với Astro#
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
---
<Layout title="Home">
<main class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<div class="max-w-4xl mx-auto px-4 py-20">
<h1 class="text-5xl font-bold text-gray-900 mb-6 bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent">
Welcome
</h1>
<p class="text-xl text-gray-600 mb-8">
Built with Astro + Tailwind CSS
</p>
<a
href="/blog"
class="inline-flex items-center px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors"
>
Read Blog →
</a>
</div>
</main>
</Layout>astroVS Code Extensions#
- Tailwind CSS IntelliSense — autocomplete, hover preview
- Tailwind Fold — fold long class lists
- Headwind — sắp xếp class theo order
Tips#
<!-- arbitrary value — khi cần giá trị đặc biệt -->
<div class="w-[calc(100%-2rem)] top-[37px]">
<!-- ! cho important -->
<div class="!text-blue-600">
<!-- group hover -->
<div class="group cursor-pointer">
<h3 class="group-hover:text-blue-600">Title</h3>
<p class="opacity-0 group-hover:opacity-100 transition">Hidden content</p>
</div>
<!-- peer — dựa vào sibling state -->
<input class="peer" type="checkbox" />
<label class="peer-checked:text-blue-600">Check me</label>htmlKết Luận#
Tailwind CSS xoá bỏ việc đặt tên class, chuyển context switch giữa HTML và CSS. Nhanh hơn, nhất quán hơn. Học 20 class đầu khó — nhưng sau đó là tự nhiên. Kết hợp với component framework (React, Astro) cho kết quả tuyệt vời.