Tailwind, Radix & shadcn/ui · Part 6 — Plugins & the Ecosystem
Round out a pro setup: the typography plugin for prose, forms for sane inputs, tw-animate for enter/exit motion, and writing your own utilities and variants in v4 with @utility and @custom-variant. With a live plugin showcase.
You can build almost anything with core utilities {Bạn dựng được gần như mọi thứ với utility lõi}. Plugins add purpose-built batches of them for common needs, and v4 lets you author your own utilities and variants directly in CSS {Plugin thêm các gói utility chuyên dụng cho nhu cầu phổ biến, và v4 cho bạn tự viết utility/variant ngay trong CSS}. This part covers the three plugins you’ll install in almost every project, plus the v4 extension primitives {Phần này nói về ba plugin bạn sẽ cài trong hầu hết project, cùng các primitive mở rộng của v4}.
1. How plugins load in v4 {Cách nạp plugin ở v4}
In v4 you load a plugin from CSS with @plugin, not from a JS config {Ở v4 bạn nạp plugin từ CSS bằng @plugin, không phải từ config JS}:
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
That’s the whole wiring {Đó là toàn bộ phần ghép nối}. Compare the before/after of each plugin in the showcase below {So sánh trước/sau của từng plugin trong showcase bên dưới}:
2. @tailwindcss/typography — the prose class {@tailwindcss/typography — class prose}
When you render content you don’t control element-by-element — Markdown, MDX, a CMS — you can’t put utilities on each tag {Khi bạn render nội dung không kiểm soát từng phần tử — Markdown, MDX, CMS — bạn không thể gắn utility lên từng thẻ}. The prose class styles the entire subtree with sensible typographic defaults {Class prose style cả cây con với mặc định typography hợp lý}:
npm install -D @tailwindcss/typography
<article class="prose lg:prose-xl dark:prose-invert">
<!-- raw HTML from Markdown: h1..h6, p, ul, blockquote, code, img… -->
</article>
Modifiers {Các bộ chỉnh}: prose-sm…prose-xl (scale), prose-invert (dark), prose-slate/prose-indigo (color theme), and per-element overrides like prose-headings:font-display or prose-a:text-indigo-500 {… và override theo phần tử như prose-headings:font-display hay prose-a:text-indigo-500}. This is what styles most blog post bodies {Đây là thứ style phần thân của hầu hết bài blog}.
3. @tailwindcss/forms — sane form controls {@tailwindcss/forms — control form tử tế}
Native form controls render differently across browsers and resist styling {Control form gốc render khác nhau giữa các trình duyệt và khó style}. This plugin gives them a clean, utility-friendly base so border-*, rounded-*, and focus:ring-* actually work as expected {Plugin này cho chúng một nền thân thiện với utility để border-*, rounded-*, focus:ring-* hoạt động đúng kỳ vọng}:
npm install -D @tailwindcss/forms
<input
type="email"
class="w-full rounded-lg border-slate-300 focus:border-indigo-500 focus:ring-indigo-500"
/>
Without it, styling a <select> or a checkbox to match your design is painful {Không có nó, style một <select> hay checkbox cho khớp thiết kế là cực hình}. With it, they behave like any other element {Có nó, chúng hành xử như mọi phần tử khác}.
4. Animation utilities — tw-animate-css {Utility animation — tw-animate-css}
shadcn components animate using enter/exit utilities — animate-in, fade-in, zoom-in-95, slide-in-from-bottom-2, paired with data-[state] {Component shadcn animate bằng utility enter/exit — animate-in, fade-in, zoom-in-95, slide-in-from-bottom-2, kết hợp với data-[state]}. In the v4 era these come from tw-animate-css (the successor to tailwindcss-animate, which targeted v3) {Ở thời v4 chúng đến từ tw-animate-css (kế thừa tailwindcss-animate vốn cho v3)}:
npm install -D tw-animate-css
@import "tailwindcss";
@import "tw-animate-css";
<!-- A dialog content that animates open/closed via Radix's data-state -->
<div
class="data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95
data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
>
…
</div>
You’ll see this exact pattern when we wire up Radix and shadcn dialogs (Parts 7–9) {Bạn sẽ thấy đúng mẫu này khi ta ghép dialog của Radix và shadcn (Phần 7–9)}.
5. Author your own utility — @utility {Tự viết utility — @utility}
v4 makes custom utilities first-class in CSS {v4 đưa custom utility thành công dân hạng nhất trong CSS}. Define one and it participates in variants (hover:, md:) like any built-in {Định nghĩa một cái và nó tham gia variant (hover:, md:) như mọi utility có sẵn}:
/* a reusable text-balance utility */
@utility text-balance {
text-wrap: balance;
}
/* a tap-highlight reset */
@utility tap-transparent {
-webkit-tap-highlight-color: transparent;
}
<h1 class="text-balance md:text-balance">…</h1>
6. Author your own variant — @custom-variant {Tự viết variant — @custom-variant}
We already used this for dark mode in Part 3 {Ta đã dùng cái này cho dark mode ở Phần 3}. The general form lets you invent any stateful prefix {Dạng tổng quát cho bạn phát minh bất kỳ tiền tố trạng thái nào}:
/* style when a parent/self has aria-current="page" */
@custom-variant current (&[aria-current="page"]);
/* style when inside a [data-theme="brand"] scope */
@custom-variant brand (&:where([data-theme="brand"] *));
<a class="text-slate-500 current:text-indigo-500" aria-current="page">Home</a>
This is how you keep app-specific states (aria-*, custom data-*) declarative instead of branching in JS {Đây là cách giữ các trạng thái riêng của app (aria-*, data-* tùy biến) khai báo thay vì rẽ nhánh trong JS}.
7. The rest of a pro toolbox {Phần còn lại của bộ đồ nghề pro}
- Prettier +
prettier-plugin-tailwindcss— auto-sorts class names into a canonical order; ends all “where do I put this class” debates {tự sắp xếp tên class theo thứ tự chuẩn; chấm dứt mọi tranh cãi “đặt class này ở đâu”}. - ESLint
eslint-plugin-tailwindcss— flags conflicting/invalid classes {cảnh báo class xung đột/không hợp lệ}. @tailwindcss/aspect-ratio— rarely needed now thataspect-*is core, but exists for legacy {hiếm cần vìaspect-*đã vào lõi, nhưng vẫn có cho legacy}.
Install the Prettier plugin first thing on any team project — consistent class order makes diffs and reviews dramatically calmer {Cài plugin Prettier ngay đầu tiên ở mọi project nhóm — thứ tự class nhất quán làm diff và review dễ chịu hơn nhiều}.
8. Exercises {Bài tập}
1. Load the typography plugin in v4 and render a Markdown article with prose that inverts in dark mode {Nạp plugin typography ở v4 và render một bài Markdown bằng prose đảo màu khi dark mode}.
Solution {Lời giải}
@plugin "@tailwindcss/typography";<article class="prose dark:prose-invert">…</article>2. Write a custom @utility named text-pretty that sets text-wrap: pretty {Viết một @utility tên text-pretty đặt text-wrap: pretty}.
Solution {Lời giải}
@utility text-pretty { text-wrap: pretty; }3. Create a @custom-variant called expanded that applies when an element has aria-expanded="true" {Tạo @custom-variant tên expanded áp khi phần tử có aria-expanded="true"}.
Solution {Lời giải}
@custom-variant expanded (&[aria-expanded="true"]);<button class="expanded:bg-indigo-500" aria-expanded="true">…</button>Stretch {Nâng cao}: in the showcase’s animate tab, identify which two utilities create the shadcn dialog “pop”: a fade plus a slight zoom {trong tab animate, xác định hai utility tạo hiệu ứng “bật” của dialog shadcn: một fade cộng một zoom nhẹ}.
Key takeaways {Điểm chính}
- v4 loads plugins from CSS via
@plugin— no JS config needed {v4 nạp plugin từ CSS qua@plugin— không cần config JS}. typography(prose) styles un-classable content;formsmakes inputs utility-friendly;tw-animate-csspowers shadcn’s enter/exit motion {typographystyle nội dung không gắn class được;formslàm input thân thiện utility;tw-animate-csscấp motion enter/exit cho shadcn}.- v4 lets you author utilities (
@utility) and variants (@custom-variant) in CSS {v4 cho bạn tự viết utility (@utility) và variant (@custom-variant) trong CSS}. - Add Prettier’s Tailwind plugin for automatic, consistent class ordering {Thêm plugin Tailwind của Prettier để tự sắp xếp class nhất quán}.
Next up {Tiếp theo}
We’ve mastered Tailwind {Ta đã thành thạo Tailwind}. Part 7 — Radix UI primitives begins the second pillar: headless, accessible behavior {Phần 7 — Radix UI primitives mở pillar thứ hai: hành vi headless, dễ tiếp cận}. We’ll install Radix, learn why “headless” matters, and build a fully accessible Dialog and Dropdown — styled with everything you just learned {Ta sẽ cài Radix, hiểu vì sao “headless” quan trọng, và dựng một Dialog và Dropdown dễ tiếp cận hoàn chỉnh — style bằng tất cả những gì bạn vừa học}.