Menu

CSS trong React: className, style, CSS Modules, Tailwind

Bạn tạo kiểu cho component React bằng className và CSS thông thường, bằng prop style cho các giá trị tính từ JavaScript, bằng CSS Modules cho tên class có phạm vi riêng, hoặc bằng các class tiện ích của Tailwind. Tìm hiểu từng cách, class có điều kiện và khi nào nên dùng cách nào.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Component React được tạo kiểu bằng CSS thông thường. Gán class cho phần tử bằng prop className và viết quy tắc trong một stylesheet, dùng prop style cho các giá trị đến từ JavaScript, và chuyển sang CSS Modules hoặc Tailwind khi dự án lớn dần. Ví dụ dưới đây khai báo CSS trong một phần tử <style> để chạy được ngay trên trang này; trong một dự án thật, phần CSS đó sẽ nằm trong một tệp .css.

Đổi #2563eb thành #16a34a trong quy tắc .btn và nút sẽ chuyển sang màu xanh lá. Mọi thứ bạn biết về CSS đều dùng được: selector, media query, trạng thái hover, animation.

className, không phải class

JSX được biên dịch thành JavaScript, nơi class là từ khóa dành riêng, nên React dùng className, cũng là tên mà chính DOM dùng cho thuộc tính đó (element.className). Trình duyệt vẫn nhận một thuộc tính class bình thường. Cách đổi tên tương tự áp dụng cho htmlFor thay vì for trên label. React 19 chấp nhận class kèm một cảnh báo ở development, nhưng className mới là tên đúng và là tên mọi codebase đều dùng.

className nhận một chuỗi, nên nhiều class được ngăn cách bằng dấu cách: className="btn btn-primary". Vì giá trị là JavaScript, bạn có thể tính toán nó.

Class có điều kiện

Để chỉ thêm class trong một số trường hợp, hãy tạo chuỗi bằng toán tử ba ngôi hoặc template literal. Thanh tab làm nổi bật tab đang chọn là ví dụ kinh điển.

Click Lessons và đường gạch chân sẽ di chuyển, vì giờ chỉ chuỗi của nút đó chứa active. Reviews nhận class disabled và thuộc tính disabled, nên nó có màu xám và bỏ qua mọi cú click. Template literal để lại dấu cách thừa khi một điều kiện sai, nhưng trình duyệt bỏ qua chúng. Khi component có nhiều class điều kiện, một mảng sẽ gọn gàng hơn:

const classes = ['tab', isActive && 'active', isDisabled && 'disabled']
    .filter(Boolean)
    .join(' ');

Nhiều dự án dùng package nhỏ clsx cho đúng việc này: clsx('tab', { active: isActive, disabled: isDisabled }).

Prop style

Prop style nhận một object JavaScript, không phải một chuỗi. Đó là lý do nó có hai cặp ngoặc nhọn: cặp ngoài mở một biểu thức JavaScript, cặp trong là object literal. Tên thuộc tính viết kiểu camelCase (backgroundColor, fontSize, borderRadius), còn giá trị là chuỗi hoặc số. Một con số trên thuộc tính độ dài được hiểu là pixel, nên padding: 12 nghĩa là 12px.

Inline style phát huy tác dụng khi giá trị được tính lúc render, chẳng hạn chiều rộng của một thanh tiến độ.

Click Add 10% cho đến khi thanh đầy và nó chuyển sang xanh lá. Để ý width: percent + '%': một con số trần sẽ được hiểu là pixel, nên đơn vị khác px phải viết trong chuỗi. Các thuộc tính không có đơn vị vẫn là số thường, như opacity: 0.8, zIndex: 2, flexGrow: 1 và lineHeight: 1.5 (đây là hệ số nhân, không phải pixel).

Inline style có giới hạn. Nó không thể diễn tả :hover, :focus, media query hay animation keyframe, và mỗi phần tử mang một bản sao quy tắc riêng. Dùng nó cho các giá trị động và để phần còn lại trong CSS.

Biến CSS từ props

Một giải pháp dung hòa tốt là đặt một CSS custom property trong style và dùng nó trong stylesheet. Quy tắc vẫn nằm trong CSS, kể cả trạng thái hover, chỉ có giá trị là đến từ JavaScript.

Chọn một màu mới và cả hai nút đổi theo, còn hiệu ứng tô màu khi hover vẫn hoạt động vì đó là CSS thuần. Tên custom property giữ nguyên dấu gạch ngang trong object style, nên chúng phải nằm trong dấu nháy: { '--accent': accent }.

Tệp CSS

Trong một dự án thật, bạn viết style trong các tệp .css và import chúng từ component cần dùng. Với Vite, chỉ cần import là đủ: công cụ build thấy import './Button.css', thêm stylesheet vào trang trong lúc development, và đóng gói nó thành một tệp CSS cho production. Không có gì được export để dùng; câu lệnh import chỉ tồn tại vì tác dụng phụ của nó.

// Button.jsx
import './Button.css';

export default function Button({ children }) {
    return <button className="button">{children}</button>;
}
/* Button.css */
.button {
    background: #2563eb;
    color: white;
    border-radius: 6px;
}

Điểm cần lưu ý là mọi stylesheet được import đều là toàn cục. Một quy tắc .button trong Button.css áp dụng cho mọi phần tử có class đó, ở bất kỳ đâu trong ứng dụng, và hai tệp cùng khai báo .title sẽ ghi đè lẫn nhau. Các nhóm xử lý chuyện này bằng quy ước đặt tên như BEM (.card__title), hoặc bằng CSS Modules.

CSS Modules

Tệp có đuôi .module.css là một CSS Module. Import nó cho bạn một object ánh xạ mỗi tên class bạn viết sang một tên duy nhất được sinh tự động, nên một class chỉ áp dụng ở nơi bạn dùng nó. Vite và Next.js hỗ trợ sẵn, không cần cấu hình gì.

// Card.jsx
import styles from './Card.module.css';

export default function Card({ title, children, featured }) {
    return (
        <section className={featured ? `${styles.card} ${styles.featured}` : styles.card}>
            <h3 className={styles.title}>{title}</h3>
            {children}
        </section>
    );
}
/* Card.module.css */
.card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; }
.featured { border-color: #2563eb; }
.title { margin: 0; }

Trong trình duyệt, class sẽ có dạng như _card_1x2ab_1. Một component khác có thể khai báo .title của riêng nó mà hai bên không bao giờ đụng nhau. Bạn viết CSS bình thường, kể cả quy tắc hover và media query, và đó là lý do CSS Modules là lựa chọn mặc định phổ biến cho style của component.

Tailwind CSS với React

Tailwind đi theo hướng khác: thay vì viết quy tắc, bạn ghép các class tiện ích nhỏ ngay trong className. px-4 đặt padding ngang, rounded-lg bo tròn góc, hover:bg-blue-700 đổi màu nền khi hover. Tailwind quét các tệp của bạn và chỉ sinh CSS cho những class bạn dùng.

Cài đặt nó trong một ứng dụng React dùng Vite (Tailwind v4) gồm ba bước:

npm install tailwindcss @tailwindcss/vite
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
    plugins: [react(), tailwindcss()],
});
/* src/index.css, imported from main.jsx */
@import "tailwindcss";

Sau đó tạo kiểu cho component bằng class:

function UpgradeCard({ active }) {
    return (
        <div className="max-w-xs rounded-xl border border-gray-200 p-4">
            <h3 className="mb-2 text-lg font-semibold">Pro plan</h3>
            <button
                className={`rounded-md px-4 py-2 text-white ${active ? 'bg-green-600' : 'bg-blue-600 hover:bg-blue-700'}`}
            >
                {active ? 'Active' : 'Upgrade'}
            </button>
        </div>
    );
}

Các trình soạn thảo trên trang này không có Tailwind, nên ví dụ được hiển thị dưới dạng code tĩnh. Class có điều kiện hoạt động y như ví dụ thanh tab ở trên, nhưng hãy viết đầy đủ từng tên class: Tailwind tìm class bằng cách quét văn bản mã nguồn, nên một tên được ghép lúc chạy như `bg-${color}-600` sẽ không bao giờ được sinh ra.

Khi nào dùng cách nào

  • Tệp CSS thuần hợp với ứng dụng nhỏ và style toàn cục (reset, font, bố cục trang). Cẩn thận với việc trùng tên class khi ứng dụng lớn dần.
  • CSS Modules hợp với phần lớn style của component: CSS bình thường, tự động có phạm vi riêng, không cần thêm thư viện.
  • Tailwind hợp với các nhóm thích tạo kiểu ngay trong markup và muốn một thang thiết kế chung cho khoảng cách và màu sắc.
  • Prop style dành cho các giá trị tính lúc render: một chiều rộng, một vị trí, màu do người dùng chọn. Kết hợp nó với biến CSS để giữ quy tắc hover và media trong CSS.

Bạn có thể kết hợp chúng. Một ứng dụng điển hình có một stylesheet toàn cục, CSS Modules hoặc Tailwind cho component, và vài inline style cho giá trị động. Các thư viện CSS-in-JS như styled-components và Emotion là một lựa chọn khác mà bạn sẽ gặp trong các codebase cũ hơn. Trang cài đặt React hướng dẫn cách khởi tạo một dự án Vite nơi tất cả những cách này đều hoạt động.

Câu hỏi thường gặp

Vì sao React dùng className thay vì class?

JSX được biên dịch thành JavaScript, và class là từ khóa dành riêng trong đó. React dùng tên thuộc tính DOM, className, cũng là tên của element.className trong JavaScript thuần. Trong HTML, nó vẫn trở thành một thuộc tính class bình thường.

Viết inline style trong React như thế nào?

Truyền một object vào prop style, với tên thuộc tính viết kiểu camelCase: style={{ backgroundColor: 'teal', fontSize: 18 }}. Cặp ngoặc nhọn ngoài mở một biểu thức JavaScript, cặp bên trong là object. Với các thuộc tính nhận độ dài, số sẽ được hiểu là pixel.

Thêm class theo điều kiện trong React như thế nào?

Tạo chuỗi bằng JavaScript: className={isActive ? 'tab active' : 'tab'} hoặc một template literal như className={`tab ${isActive ? 'active' : ''}`}. Khi có nhiều điều kiện, một hàm hỗ trợ nhỏ như package clsx giúp code dễ đọc hơn.

Tệp CSS trong React có phải là toàn cục không?

Đúng vậy. import './App.css' thêm stylesheet vào toàn bộ trang, nên tên class trong đó có thể đụng với các tệp khác. CSS Modules (App.module.css) đổi mỗi class thành một tên duy nhất để nó chỉ áp dụng ở nơi bạn import.

Dùng Tailwind với React như thế nào?

Trong một dự án Vite, cài tailwindcss và @tailwindcss/vite, thêm plugin vào vite.config.js, và đặt @import "tailwindcss"; ở đầu tệp CSS chính. Sau đó viết các class tiện ích trong className, như className="px-4 py-2 rounded bg-blue-600 text-white".

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu