Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Styling trong React

CSS Modules

CSS Modules tạo scoped CSS, tránh conflict tên class.

// Button.module.css
.button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.primary {
  background-color: #007bff;
  color: white;
}

.secondary {
  background-color: #6c757d;
  color: white;
}

.button:hover {
  opacity: 0.9;
}
// Button.tsx
import styles from './Button.module.css';
import clsx from 'clsx'; // hoặc classnames

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  children: React.ReactNode;
  onClick?: () => void;
}

function Button({ variant = 'primary', children, onClick }: ButtonProps) {
  return (
    <button
      className={clsx(styles.button, styles[variant])}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// Class tên được hash: button_primary__xHk3a (tự động unique)

Tailwind CSS

Utility-first CSS framework, được dùng phổ biến nhất hiện nay.

npm install tailwindcss @tailwindcss/vite
// Component với Tailwind
function ProductCard({ product }: { product: Product }) {
  return (
    <div className="rounded-lg border border-gray-200 p-4 shadow-sm hover:shadow-md transition-shadow">
      <img
        src={product.imageUrl}
        alt={product.name}
        className="h-48 w-full object-cover rounded-md"
      />
      <div className="mt-3">
        <h3 className="text-lg font-semibold text-gray-900">{product.name}</h3>
        <p className="mt-1 text-sm text-gray-500">{product.description}</p>
        <div className="mt-3 flex items-center justify-between">
          <span className="text-xl font-bold text-blue-600">
            ${product.price}
          </span>
          <button className="rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50">
            Add to Cart
          </button>
        </div>
      </div>
    </div>
  );
}

clsx / tailwind-merge - Conditional Classes

npm install clsx tailwind-merge
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

// Utility function (nên tạo trong lib/utils.ts)
function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// Sử dụng
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  className?: string;
  children: React.ReactNode;
}

function Button({
  variant = 'primary',
  size = 'md',
  disabled,
  className,
  children,
}: ButtonProps) {
  return (
    <button
      disabled={disabled}
      className={cn(
        // Base styles
        'inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2',

        // Variant styles
        {
          'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500':
            variant === 'primary',
          'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500':
            variant === 'secondary',
          'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500':
            variant === 'danger',
        },

        // Size styles
        {
          'h-8 px-3 text-sm': size === 'sm',
          'h-10 px-4 text-base': size === 'md',
          'h-12 px-6 text-lg': size === 'lg',
        },

        // Disabled
        { 'cursor-not-allowed opacity-50': disabled },

        // Allow className override
        className
      )}
    >
      {children}
    </button>
  );
}

styled-components

CSS-in-JS, tạo styled components với template literals.

npm install styled-components
npm install -D @types/styled-components
import styled, { css, ThemeProvider, createGlobalStyle } from 'styled-components';

// Global styles
const GlobalStyle = createGlobalStyle`
  *, *::before, *::after {
    box-sizing: border-box;
  }
  
  body {
    margin: 0;
    font-family: 'Inter', sans-serif;
  }
`;

// Theme
const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    danger: '#dc3545',
    background: '#f8f9fa',
  },
  spacing: (n: number) => `${n * 4}px`,
  borderRadius: '6px',
};

// Styled components
const Card = styled.div`
  background: white;
  border-radius: ${({ theme }) => theme.borderRadius};
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: ${({ theme }) => theme.spacing(4)};
`;

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
}

const Button = styled.button<ButtonProps>`
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 500;
  transition: opacity 0.2s;

  &:hover {
    opacity: 0.9;
  }

  &:disabled {
    cursor: not-allowed;
    opacity: 0.5;
  }

  ${({ variant = 'primary', theme }) =>
    variant === 'primary'
      ? css`
          background: ${theme.colors.primary};
          color: white;
        `
      : variant === 'danger'
      ? css`
          background: ${theme.colors.danger};
          color: white;
        `
      : css`
          background: ${theme.colors.secondary};
          color: white;
        `}

  ${({ size = 'md' }) =>
    size === 'sm'
      ? css`padding: 4px 12px; font-size: 14px;`
      : size === 'lg'
      ? css`padding: 12px 24px; font-size: 18px;`
      : css`padding: 8px 16px; font-size: 16px;`}
`;

// Extend styled component
const PrimaryButton = styled(Button).attrs({ variant: 'primary' })`
  text-transform: uppercase;
  letter-spacing: 0.5px;
`;

// App với ThemeProvider
function App() {
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyle />
      <Card>
        <h2>Product</h2>
        <Button variant="primary">Add to Cart</Button>
        <Button variant="danger" size="sm">Delete</Button>
      </Card>
    </ThemeProvider>
  );
}

So sánh các Styling Approaches

CSS ModulesTailwind CSSstyled-components
Bundle sizeNhỏNhỏ (purge)Lớn hơn
DXTốtRất tốtTốt
Type-safeHạn chế✅ (với TS)
ThemingThủ côngConfig fileTheme object
Runtime✅ CSS-in-JS
Learning curveThấpTrung bìnhTrung bình
Phù hợpMọi dự ánDự án mớiComponent library

Khuyến nghị

  • Dự án mới: Tailwind CSS + clsx/tw-merge
  • Component library: styled-components hoặc vanilla-extract
  • Legacy codebase: CSS Modules