Documentation
Styling
Tailwind CSS

Tailwind CSS

Utility-first CSS for styling React components.

Class Order Convention

Follow this order for consistency:

Layout → Sizing → Spacing → Typography → Colors → Effects → States

<div className="flex items-center justify-between w-full max-w-md p-4 gap-2 text-sm font-medium text-gray-900 bg-white rounded-lg shadow-sm hover:bg-gray-50 transition-colors">
  Content
</div>

The cn() Utility

Combine classes conditionally using clsx and tailwind-merge:

// utils/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Conditional Classes

import { cn } from '@/utils/cn';
 
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'ghost';
  size: 'sm' | 'md' | 'lg';
  isFullWidth?: boolean;
  className?: string;
}
 
const Button = ({ variant, size, isFullWidth, className }: ButtonProps) => (
  <button
    className={cn(
      // Base styles
      'inline-flex items-center justify-center font-medium rounded-lg transition-colors',
      // Focus styles
      'focus:outline-none focus:ring-2 focus:ring-offset-2',
      // Variants
      {
        'bg-orange-500 text-white hover:bg-orange-600 focus:ring-orange-500':
          variant === 'primary',
        'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500':
          variant === 'secondary',
        'bg-transparent text-gray-600 hover:bg-gray-100 focus:ring-gray-500':
          variant === 'ghost',
      },
      // Sizes
      {
        'px-3 py-1.5 text-sm': size === 'sm',
        'px-4 py-2 text-base': size === 'md',
        'px-6 py-3 text-lg': size === 'lg',
      },
      // Conditional
      isFullWidth && 'w-full',
      // External className (always last)
      className
    )}
  />
);

Component Variants Pattern

import { cva, type VariantProps } from 'class-variance-authority';
 
const buttonVariants = cva(
  // Base styles
  'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
  {
    variants: {
      variant: {
        primary: 'bg-orange-500 text-white hover:bg-orange-600',
        secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
        outline: 'border border-gray-300 bg-transparent hover:bg-gray-50',
        ghost: 'bg-transparent hover:bg-gray-100',
        destructive: 'bg-red-500 text-white hover:bg-red-600',
      },
      size: {
        sm: 'h-8 px-3 text-sm',
        md: 'h-10 px-4 text-base',
        lg: 'h-12 px-6 text-lg',
      },
    },
    defaultVariants: {
      variant: 'primary',
      size: 'md',
    },
  }
);
 
interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}
 
const Button = ({ variant, size, className, ...props }: ButtonProps) => (
  <button
    className={cn(buttonVariants({ variant, size }), className)}
    {...props}
  />
);

Responsive Design

Mobile-first approach with breakpoint prefixes:

<div className="
  grid
  grid-cols-1      {/* Mobile: 1 column */}
  sm:grid-cols-2   {/* Small: 2 columns */}
  md:grid-cols-3   {/* Medium: 3 columns */}
  lg:grid-cols-4   {/* Large: 4 columns */}
  gap-4
">
  {items.map((item) => (
    <Card key={item.id} />
  ))}
</div>

Dark Mode

<div className="bg-white dark:bg-gray-900">
  <h1 className="text-gray-900 dark:text-white">
    Title
  </h1>
  <p className="text-gray-600 dark:text-gray-400">
    Description
  </p>
</div>

Common Patterns

Card

<div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-800 dark:bg-gray-900">
  {/* Content */}
</div>

Input

<input
  className="w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 placeholder-gray-500 focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
  placeholder="Enter text..."
/>

Badge

<span className="inline-flex items-center rounded-full bg-orange-100 px-2.5 py-0.5 text-xs font-medium text-orange-800 dark:bg-orange-900 dark:text-orange-200">
  New
</span>