Mô hình tư duy nâng cao với Tailwind CSS trong phát triển UI hiện đại

Trong bối cảnh các framework CSS ngày càng hướng tới sự linh hoạt và kiểm soát thấp hơn, Tailwind CSS không còn chỉ là một bộ tiện ích — mà đã trở thành nền tảng để xây dựng hệ thống thiết kế có tính cá nhân hoá cao. Sự thay đổi này đòi hỏi lập trình viên phải chuyển từ tư duy "sử dụng sẵn" sang tư duy "xây dựng nền tảng".

Hiểu đúng bản chất: Tailwind là công cụ cơ sở, không phải giải pháp đóng gói

Tailwind không cung cấp lớp trừu tượng hóa như .btn-primary hay .card-default theo mặc định — mà cung cấp khả năng tạo ra những lớp đó một cách có chủ đích. Ví dụ, thay vì phụ thuộc vào một cấu hình cố định:

// tailwind.config.js
theme: {
  extend: {
    colors: {
      primary: '#3b82f6',
      danger: '#ef4444',
    }
  }
}

Bạn hoàn toàn có thể viết trực tiếp giá trị CSS dạng tùy chỉnh ngay trong class:

<div class="w-[18rem] h-[calc(100vh-4rem)] bg-gradient-to-r from-indigo-500 to-purple-600"></div>

Điều này phản ánh triết lý cốt lõi: Tailwind không hạn chế bạn — mà trao quyền cho bạn quyết định mức độ trừu tượng hóa phù hợp với dự án.

Đóng gói theo hai cấp độ chiến lược

Cấp 1: Các utility class tùy chỉnh (reusable primitives)

Khi một mẫu bố cục xuất hiện thường xuyên — ví dụ: căn giữa cả hai trục — bạn không nên lặp lại flex justify-center items-center ở nhiều nơi. Thay vào đó, định nghĩa một utility class tái sử dụng được:

// plugins/center-plugin.js
module.exports = function ({ addUtilities }) {
  const newUtilities = {
    '.center': {
      display: 'flex',
      'justify-content': 'center',
      'align-items': 'center',
    },
  }
  addUtilities(newUtilities)
}

Sau đó đăng ký plugin trong tailwind.config.js. Cách này đảm bảo class mới được nhận diện bởi IntelliSense và tích hợp đầy đủ với hệ thống JIT.

Cấp 2: Thành phần ngữ nghĩa (semantic layout components)

Đây là bước tiến quan trọng: biến các nhóm class Tailwind thành thành phần React có ý nghĩa rõ ràng. Dưới đây là một phiên bản tối ưu của thành phần Flex, được thiết kế để giảm độ phức tạp khi sử dụng:

// components/Flex.jsx
import { twMerge } from 'tailwind-merge'
import { clsx } from 'clsx'

export default function Flex({ 
  children, 
  direction = 'row', 
  justify, 
  align = 'center', 
  gap, 
  className, 
  ...rest 
}) {
  // Xử lý các thuộc tính động (bg-, border-, rounded-, p-, m-, w-, h-...)
  const dynamicClasses = Object.entries(rest)
    .filter(([key]) => 
      key.startsWith('bg-') || 
      key.startsWith('border') || 
      key.startsWith('rounded') || 
      key.startsWith('p-') || 
      key.startsWith('m-') || 
      key.startsWith('w-') || 
      key.startsWith('h-')
    )
    .map(([key]) => key)
    .join(' ')

  const baseClasses = clsx(
    'flex',
    `flex-${direction}`,
    `items-${align}`,
    justify && `justify-${justify}`,
    gap && `gap-${gap}`,
    dynamicClasses,
    className
  )

  return <div className={twMerge(baseClasses)} {...rest}>{children}</div>
}

Với thành phần này, cú pháp sử dụng trở nên cực kỳ súc tích và mang tính mô tả:

<Flex direction="col" justify="between" gap="4" bg-slate-50 p-6 border border-slate-200 rounded-xl>
  <Header />
  <Content />
  <Footer />
</Flex>

Tối ưu hoá sâu hơn bằng lớp nền chung

Thay vì sao chép logic xử lý thuộc tính động ở từng thành phần, ta tạo một thành phần nền — TailwindBox — chịu trách nhiệm chuẩn hoá và hợp nhất mọi class Tailwind từ props:

// components/TailwindBox.jsx
import { twMerge } from 'tailwind-merge'
import { clsx } from 'clsx'

export function TailwindBox({ 
  as: Component = 'div', 
  className, 
  children, 
  ...props 
}) {
  const dynamic = Object.keys(props)
    .filter(key => 
      /^bg-|^border|^rounded|^p-|^m-|^w-|^h-|^text-|^font-/.test(key)
    )
    .join(' ')

  const merged = twMerge(clsx(dynamic, className))

  return <Component className={merged} {...props}>{children}</Component>
}

Giờ đây, các thành phần như Flex, Grid, Stack chỉ cần tập trung vào logic ngữ nghĩa riêng — còn việc xử lý class Tailwind được ủy thác cho TailwindBox.

Lợi ích thực tiễn sau khi áp dụng mô hình này

  • Giảm 70% số lần gõ class: Các cấu hình phổ biến được mã hoá thành prop, không cần nhớ tên class
  • Tăng tính nhất quán: Mọi thành phần đều tuân thủ cùng một cơ chế xử lý màu sắc, khoảng cách, bo góc
  • Dễ mở rộng theo thiết kế hệ thống: Khi thiết kế hệ thống cập nhật màu sắc hoặc kích thước, chỉ cần điều chỉnh một nơi
  • Không làm mất đi sức mạnh gốc của Tailwind: Vẫn giữ nguyên khả năng viết class tùy chỉnh khi cần — nhờ hỗ trợ classNametwMerge
  • Tương thích tốt với các công cụ phân tích: ESLint, Prettier, và IDE vẫn hiểu rõ cấu trúc JSX

Kết luận

Tailwind CSS không yêu cầu bạn từ bỏ tư duy thiết kế — mà yêu cầu bạn nâng cấp nó. Việc đóng gói thông minh không làm giảm tính linh hoạt, mà ngược lại, giúp bạn kiểm soát tốt hơn luồng phát triển UI. Mô hình này đặc biệt hiệu quả trong các dự án có thiết kế hệ thống rõ ràng, hoặc khi đội ngũ muốn thống nhất cách biểu đạt giao diện qua code — biến mỗi thẻ JSX thành một tuyên bố rõ ràng về mục đích và hành vi thị giác.

Thẻ: tailwindcss react css-in-js ui-engineering design-system

Đăng vào ngày 19 tháng 8 lúc 05:52