jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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-smprose-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 that aspect-* 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; forms makes inputs utility-friendly; tw-animate-css powers shadcn’s enter/exit motion {typography style nội dung không gắn class được; forms làm input thân thiện utility; tw-animate-css cấ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}.