Xây dựng các thành phần phức tạp từ một tập hợp hạn chế các tiện ích nguyên thủy.
Bạn style các thành phần với Tailwind bằng cách kết hợp nhiều class trình bày đơn mục đích (utility classes) trực tiếp trong markup của bạn:
You have a new message!
<div class="mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg outline outline-black/5 dark:bg-slate-800 dark:shadow-none dark:-outline-offset-1 dark:outline-white/10"> <img class="size-12 shrink-0" src="/img/logo.svg" alt="ChitChat Logo" /> <div> <div class="text-xl font-medium text-black dark:text-white">ChitChat</div> <p class="text-gray-500 dark:text-gray-400">You have a new message!</p> </div></div>Ví dụ, trong UI ở trên chúng tôi đã sử dụng:
flex, shrink-0, và p-6) để kiểm soát bố cục tổng thểmax-w-sm và mx-auto) để giới hạn chiều rộng thẻ và căn giữa nó theo chiều ngangbg-white, rounded-xl, và shadow-lg) để style giao diện của thẻsize-12) để đặt chiều rộng và chiều cao của hình ảnh logogap-x-4) để xử lý khoảng cách giữa logo và văn bảntext-xl, text-black, font-medium, v.v.) để style văn bản của thẻViệc style theo cách này mâu thuẫn với nhiều best practice truyền thống, nhưng một khi bạn thử nó, bạn sẽ nhanh chóng nhận thấy một số lợi ích thực sự quan trọng:
Những lợi ích này tạo ra sự khác biệt lớn trên các dự án nhỏ, nhưng chúng thậm chí còn có giá trị hơn đối với các nhóm làm việc trên các dự án dài hạn ở quy mô lớn.
Một phản ứng phổ biến đối với cách tiếp cận này là tự hỏi, "đây không phải chỉ là inline styles sao?" và theo một số cách thì đúng là vậy — bạn đang áp dụng các kiểu trực tiếp vào các phần tử thay vì gán cho chúng một tên class và sau đó style class đó.
Nhưng sử dụng utility classes có nhiều lợi thế quan trọng so với inline styles, ví dụ:
Thành phần này hoàn toàn phản hồi và bao gồm một nút với các kiểu hover và active, và được xây dựng hoàn toàn bằng các utility classes:

Erin Lindford
Product Engineer
<div class="flex flex-col gap-2 p-8 sm:flex-row sm:items-center sm:gap-6 sm:py-4 ..."> <img class="mx-auto block h-24 rounded-full sm:mx-0 sm:shrink-0" src="/img/erin-lindford.jpg" alt="" /> <div class="space-y-2 text-center sm:text-left"> <div class="space-y-0.5"> <p class="text-lg font-semibold text-black">Erin Lindford</p> <p class="font-medium text-gray-500">Product Engineer</p> </div> <button class="border-purple-200 text-purple-600 hover:border-transparent hover:bg-purple-600 hover:text-white active:bg-purple-700 ..."> Message </button> </div></div>Để style một phần tử trên các trạng thái như hover hoặc focus, hãy thêm tiền tố tên trạng thái vào bất kỳ tiện ích nào, ví dụ hover:bg-sky-700:
Hover over this button to see the background color change
<button class="bg-sky-500 hover:bg-sky-700 ...">Save changes</button>Các tiền tố này được gọi là biến thể trong Tailwind, và chúng chỉ áp dụng các kiểu từ một utility class khi điều kiện cho biến thể đó khớp.
Dưới đây là CSS được tạo trông như thế nào cho class hover:bg-sky-700:
.hover\:bg-sky-700 { &:hover { background-color: var(--color-sky-700); }}Lưu ý cách class này không làm gì cả trừ khi phần tử được hover? Công việc duy nhất của nó là cung cấp các kiểu hover — không gì khác.
Điều này khác với cách bạn viết CSS truyền thống, nơi một class đơn lẻ thường sẽ cung cấp các kiểu cho nhiều trạng thái:
<button class="btn">Save changes</button><style> .btn { background-color: var(--color-sky-500); &:hover { background-color: var(--color-sky-700); } }</style>Bạn thậm chí có thể xếp chồng các biến thể trong Tailwind để áp dụng một tiện ích khi nhiều điều kiện khớp, giống như kết hợp hover: và disabled:
<button class="bg-sky-500 disabled:hover:bg-sky-500 ...">Save changes</button>Tìm hiểu thêm trong tài liệu về style các phần tử trên hover, focus, và các trạng thái khác.
Cũng giống như các trạng thái hover và focus, bạn có thể style các phần tử ở các breakpoint khác nhau bằng cách thêm tiền tố breakpoint vào bất kỳ tiện ích nào:
Resize this example to see the layout change
<div class="grid grid-cols-2 sm:grid-cols-3"> <!-- ... --></div>Trong ví dụ trên, tiền tố sm: đảm bảo rằng grid-cols-3 chỉ kích hoạt ở breakpoint sm và lớn hơn, mặc định là 40rem:
.sm\:grid-cols-3 { @media (width >= 40rem) { grid-template-columns: repeat(3, minmax(0, 1fr)); }}Tìm hiểu thêm trong tài liệu thiết kế phản hồi.
Style một phần tử trong chế độ tối chỉ là vấn đề thêm tiền tố dark: vào bất kỳ tiện ích nào bạn muốn áp dụng khi chế độ tối được kích hoạt:
Light mode
Writes upside-down
The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.
Dark mode
Writes upside-down
The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.
<div class="bg-white dark:bg-gray-800 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5"> <div> <span class="inline-flex items-center justify-center rounded-md bg-indigo-500 p-2 shadow-lg"> <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true" > <!-- ... --> </svg> </span> </div> <h3 class="text-gray-900 dark:text-white mt-5 text-base font-medium tracking-tight ">Writes upside-down</h3> <p class="text-gray-500 dark:text-gray-400 mt-2 text-sm "> The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space. </p></div>Cũng giống như với các trạng thái hover hoặc media queries, điều quan trọng cần hiểu là một utility class đơn lẻ sẽ không bao giờ bao gồm cả kiểu sáng và tối — bạn style mọi thứ trong chế độ tối bằng cách sử dụng nhiều class, một cho kiểu chế độ sáng và một cho kiểu chế độ tối.
.dark\:bg-gray-800 { @media (prefers-color-scheme: dark) { background-color: var(--color-gray-800); }}Tìm hiểu thêm trong tài liệu chế độ tối.
Rất nhiều lần với Tailwind, bạn thậm chí sẽ sử dụng nhiều class để xây dựng giá trị cho một thuộc tính CSS duy nhất, ví dụ như thêm nhiều bộ lọc vào một phần tử:
<div class="blur-sm grayscale"> <!-- ... --></div>Cả hai hiệu ứng này đều dựa vào thuộc tính filter trong CSS, vì vậy Tailwind sử dụng các biến CSS để có thể kết hợp các hiệu ứng này lại với nhau:
.blur-sm { --tw-blur: blur(var(--blur-sm)); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}.grayscale { --tw-grayscale: grayscale(100%); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}CSS được tạo ở trên đã được đơn giản hóa một chút, nhưng thủ thuật ở đây là mỗi tiện ích đặt một biến CSS chỉ cho hiệu ứng mà nó định áp dụng. Sau đó, thuộc tính filter xem xét tất cả các biến này, quay lại không có gì nếu biến chưa được đặt.
Tailwind sử dụng cùng một cách tiếp cận này cho gradients, màu bóng, biến đổi, và nhiều hơn nữa.
Nhiều tiện ích trong Tailwind được điều khiển bởi các biến theme, như bg-blue-500, text-xl, và shadow-md, ánh xạ tới bảng màu, thang đo kiểu chữ và bóng của bạn.
Khi bạn cần sử dụng một giá trị một lần nằm ngoài theme của mình, hãy sử dụng cú pháp ngoặc vuông đặc biệt để chỉ định các giá trị tùy ý:
<button class="bg-[#316ff6] ..."> Sign in with Facebook</button>Điều này có thể hữu ích cho các màu một lần nằm ngoài bảng màu của bạn (như màu xanh Facebook ở trên), nhưng cũng khi bạn cần một giá trị tùy chỉnh phức tạp như một lưới rất cụ thể:
<div class="grid grid-cols-[24rem_2.5rem_minmax(0,1fr)]"> <!-- ... --></div>Nó cũng hữu ích khi bạn cần sử dụng các tính năng CSS như calc(), ngay cả khi bạn đang sử dụng các giá trị theme của mình:
<div class="max-h-[calc(100dvh-(--spacing(6)))]"> <!-- ... --></div>Thậm chí còn có một cú pháp để tạo CSS hoàn toàn tùy ý bao gồm tên thuộc tính tùy ý, có thể hữu ích để đặt các biến CSS:
<div class="[--gutter-width:1rem] lg:[--gutter-width:2rem]"> <!-- ... --></div>Tìm hiểu thêm trong tài liệu về sử dụng giá trị tùy ý.
Tailwind CSS không phải là một stylesheet tĩnh lớn như bạn có thể quen thuộc với các framework CSS khác — nó tạo ra CSS cần thiết dựa trên các class bạn thực sự đang sử dụng khi bạn biên dịch CSS của mình.
Nó thực hiện điều này bằng cách quét tất cả các file trong dự án của bạn để tìm bất kỳ ký hiệu nào trông giống như tên class:
export default function Button({ size, children }) { let sizeClasses = { md: "px-4 py-2 rounded-md text-base", lg: "px-5 py-3 rounded-lg text-lg", }[size]; return ( <button type="button" className={`font-bold ${sizeClasses}`}> {children} </button> );}Sau khi tìm thấy tất cả các class tiềm năng, Tailwind tạo ra CSS cho mỗi class và biên dịch tất cả thành một stylesheet chỉ chứa các kiểu bạn thực sự cần.
Vì CSS được tạo dựa trên tên class, Tailwind có thể nhận ra các class sử dụng giá trị tùy ý như bg-[#316ff6] và tạo ra CSS cần thiết, ngay cả khi giá trị không phải là một phần của theme của bạn.
Tìm hiểu thêm về cách hoạt động này trong phát hiện class trong file nguồn.
Đôi khi bạn cần style một phần tử dưới sự kết hợp của các điều kiện, ví dụ như trong chế độ tối, tại một breakpoint cụ thể, khi được hover, và khi phần tử có một thuộc tính dữ liệu cụ thể.
Dưới đây là ví dụ về cách nó trông như thế nào với Tailwind:
<button class="dark:lg:data-current:hover:bg-indigo-600 ..."> <!-- ... --></button>@media (prefers-color-scheme: dark) and (width >= 64rem) { button[data-current]:hover { background-color: var(--color-indigo-600); }}Tailwind cũng hỗ trợ những thứ như group-hover, cho phép bạn style một phần tử khi một phần tử cha cụ thể được hover:
<a href="#" class="group rounded-lg p-8"> <!-- ... --> <span class="group-hover:underline">Read more…</span></a>@media (hover: hover) { a:hover span { text-decoration-line: underline; }}Cú pháp group-* này cũng hoạt động với các biến thể khác, như group-focus, group-active, và nhiều hơn nữa.
Đối với các kịch bản thực sự phức tạp (đặc biệt là khi style HTML mà bạn không kiểm soát), Tailwind hỗ trợ biến thể tùy ý cho phép bạn viết bất kỳ selector nào bạn muốn, trực tiếp trong tên class:
<div class="[&>[data-active]+span]:text-blue-600 ..."> <span data-active><!-- ... --></span> <span>This text will be blue</span></div>div > [data-active] + span { color: var(--color-blue-600);}Inline styles vẫn rất hữu ích trong các dự án Tailwind CSS, đặc biệt là khi một giá trị đến từ một nguồn động như cơ sở dữ liệu hoặc API:
export function BrandedButton({ buttonColor, textColor, children }) { return ( <button style={{ backgroundColor: buttonColor, color: textColor, }} className="rounded-md px-3 py-1.5 font-medium" > {children} </button> );}Bạn cũng có thể sử dụng inline style cho các giá trị tùy ý rất phức tạp khó đọc khi được định dạng dưới dạng tên class:
<div class="grid-[2fr_max(0,var(--gutter-width))_calc(var(--gutter-width)+10px)]"><div style="grid-template-columns: 2fr max(0, var(--gutter-width)) calc(var(--gutter-width) + 10px)"> <!-- ... --></div>Một mẫu hữu ích khác là đặt các biến CSS dựa trên các nguồn động bằng cách sử dụng inline styles, sau đó tham chiếu các biến đó bằng các utility class:
export function BrandedButton({ buttonColor, buttonColorHover, textColor, children }) { return ( <button style={{ "--bg-color": buttonColor, "--bg-color-hover": buttonColorHover, "--text-color": textColor, }} className="bg-(--bg-color) text-(--text-color) hover:bg-(--bg-color-hover) ..." > {children} </button> );}Khi bạn xây dựng toàn bộ dự án chỉ với các utility class, bạn chắc chắn sẽ thấy mình lặp lại các mẫu nhất định để tạo lại cùng một thiết kế ở những nơi khác nhau.
Ví dụ, ở đây các utility class cho mỗi hình ảnh avatar được lặp lại năm lần riêng biệt:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</h4> <span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span> </div> <div class="mt-3 flex -space-x-2 overflow-hidden"> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> </div> <div class="mt-3 text-sm font-medium"> <a href="#" class="text-blue-500">+ 198 others</a> </div></div>Đừng hoảng sợ! Trong thực tế, đây không phải là vấn đề bạn có thể lo lắng, và các chiến lược để giải quyết nó là những điều bạn đã làm hàng ngày.
Rất nhiều lần một phần tử thiết kế xuất hiện nhiều hơn một lần trong trang được hiển thị thực sự chỉ được tác giả viết một lần vì markup thực tế được hiển thị trong một vòng lặp.
Ví dụ, các avatar trùng lặp ở đầu hướng dẫn này gần như chắc chắn sẽ được hiển thị trong một vòng lặp trong một dự án thực tế:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</h4> <span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span> </div> <div class="mt-3 flex -space-x-2 overflow-hidden"> {#each contributors as user} <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src={user.avatarUrl} alt={user.handle} /> {/each} </div> <div class="mt-3 text-sm font-medium"> <a href="#" class="text-blue-500">+ 198 others</a> </div></div>Khi các phần tử được hiển thị trong một vòng lặp như thế này, danh sách class thực tế chỉ được viết một lần nên không có vấn đề trùng lặp thực sự nào để giải quyết.
Khi sự trùng lặp được bản địa hóa cho một nhóm các phần tử trong một file duy nhất, cách dễ nhất để giải quyết nó là sử dụng chỉnh sửa đa con trỏ để nhanh chóng chọn và chỉnh sửa danh sách class cho mỗi phần tử cùng một lúc:
<nav class="flex justify-center space-x-4"> <a href="/dashboard" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Home </a> <a href="/team" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Team </a> <a href="/projects" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Projects </a> <a href="/reports" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Reports </a></nav>Bạn sẽ ngạc nhiên về mức độ thường xuyên mà điều này trở thành giải pháp tốt nhất. Nếu bạn có thể nhanh chóng chỉnh sửa tất cả các danh sách class trùng lặp cùng một lúc, không có lợi ích gì khi giới thiệu thêm bất kỳ sự trừu tượng nào.
Nếu bạn cần tái sử dụng một số kiểu trên nhiều file, chiến lược tốt nhất là tạo một component nếu bạn đang sử dụng một framework front-end như React, Svelte, hoặc Vue, hoặc một template partial nếu bạn đang sử dụng một ngôn ngữ tạo mẫu như Blade, ERB, Twig, hoặc Nunjucks.
export function VacationCard({ img, imgAlt, eyebrow, title, pricing, url }) { return ( <div> <img className="rounded-lg" src={img} alt={imgAlt} /> <div className="mt-4"> <div className="text-xs font-bold text-sky-500">{eyebrow}</div> <div className="mt-1 font-bold text-gray-700"> <a href={url} className="hover:underline"> {title} </a> </div> <div className="mt-2 text-sm text-gray-600">{pricing}</div> </div> </div> );}Bây giờ bạn có thể sử dụng component này ở bao nhiêu nơi tùy thích, trong khi vẫn có một nguồn sự thật duy nhất cho các kiểu để chúng có thể dễ dàng được cập nhật cùng nhau ở một nơi.
Nếu bạn đang sử dụng một ngôn ngữ tạo mẫu như ERB hoặc Twig thay vì một cái gì đó như React hoặc Vue, việc tạo một template partial cho một cái gì đó nhỏ như một nút có thể cảm thấy quá mức cần thiết so với một class CSS đơn giản như btn.
Mặc dù chúng tôi thực sự khuyên bạn nên tạo các template partial thích hợp cho các thành phần phức tạp hơn, việc viết một số CSS tùy chỉnh là hoàn toàn ổn khi một template partial cảm thấy nặng nề.
Dưới đây là một class btn-primary có thể trông như thế nào, sử dụng các biến theme để giữ cho thiết kế nhất quán:
<button class="btn-primary">Save changes</button>@import "tailwindcss";@layer components { .btn-primary { border-radius: calc(infinity * 1px); background-color: var(--color-violet-500); padding-inline: --spacing(5); padding-block: --spacing(2); font-weight: var(--font-weight-semibold); color: var(--color-white); box-shadow: var(--shadow-md); &:hover { @media (hover: hover) { background-color: var(--color-violet-700); } } }}Tuy nhiên, một lần nữa, đối với bất cứ điều gì phức tạp hơn chỉ là một phần tử HTML đơn lẻ, chúng tôi thực sự khuyên bạn nên sử dụng các template partial để các kiểu và cấu trúc có thể được đóng gói ở một nơi.
Khi bạn thêm hai class nhắm mục tiêu cùng một thuộc tính CSS, class xuất hiện sau trong stylesheet sẽ thắng. Vì vậy, trong ví dụ này, phần tử sẽ nhận display: grid mặc dù flex đứng cuối cùng trong thuộc tính class thực tế:
<div class="grid flex"> <!-- ... --></div>.flex { display: flex;}.grid { display: grid;}Nói chung, bạn không bao giờ nên thêm hai class xung đột vào cùng một phần tử — chỉ thêm class mà bạn thực sự muốn có hiệu lực:
export function Example({ gridLayout }) { return <div className={gridLayout ? "grid" : "flex"}>{/* ... */}</div>;}Sử dụng các thư viện dựa trên component như React hoặc Vue, điều này thường có nghĩa là hiển thị các prop cụ thể cho các tùy chỉnh kiểu thay vì để người tiêu dùng thêm các class bổ sung từ bên ngoài component, vì các kiểu đó thường sẽ xung đột.
Khi bạn thực sự cần buộc một utility class cụ thể có hiệu lực và không có cách nào khác để quản lý độ đặc hiệu, bạn có thể thêm ! vào cuối tên class để làm cho tất cả các khai báo trở thành !important:
<div class="bg-teal-500 bg-red-500!"> <!-- ... --></div>.bg-red-500\! { background-color: var(--color-red-500) !important;}.bg-teal-500 { background-color: var(--color-teal-500);}Nếu bạn đang thêm Tailwind vào một dự án có CSS phức tạp hiện có với các quy tắc độ đặc hiệu cao, bạn có thể sử dụng cờ important khi nhập Tailwind để đánh dấu tất cả các tiện ích là !important:
@import "tailwindcss" important;@layer utilities { .flex { display: flex !important; } .gap-4 { gap: 1rem !important; } .underline { text-decoration-line: underline !important; }}Nếu dự án của bạn có tên class xung đột với các tiện ích Tailwind CSS, bạn có thể thêm tiền tố cho tất cả các class và biến CSS do Tailwind tạo ra bằng cách sử dụng tùy chọn prefix:
@import "tailwindcss" prefix(tw);@layer theme { :root { --tw-color-red-500: oklch(0.637 0.237 25.331); }}@layer utilities { .tw\:text-red-500 { color: var(--tw-color-red-500); }}