AgentSkillsCN

Tailwind Patterns

Tailwind 设计模式

SKILL.md

Tailwind Patterns Skill

Utility-first CSS patterns for modern web applications

PRINCIPLES

  1. Utility-first: Compose styles from utility classes
  2. Mobile-first: Design for mobile, enhance for desktop
  3. Component extraction: Extract repeated patterns
  4. Design tokens: Use consistent spacing, colors, typography

UTILITY COMPOSITION

Class Merging with cn()

typescript
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// Usage - handles conflicts correctly
<button className={cn(
  'px-4 py-2 rounded bg-blue-500',
  isActive && 'bg-blue-700',
  className // allows override
)} />

Conditional Classes

typescript
// ✅ Clean conditional styling
<div className={cn(
  'p-4 rounded-lg border',
  variant === 'primary' && 'bg-blue-500 text-white',
  variant === 'secondary' && 'bg-gray-100 text-gray-900',
  disabled && 'opacity-50 cursor-not-allowed'
)} />

// ✅ With variants map
const variants = {
  primary: 'bg-blue-500 text-white hover:bg-blue-600',
  secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
  danger: 'bg-red-500 text-white hover:bg-red-600',
} as const;

<button className={cn('px-4 py-2 rounded', variants[variant])} />

RESPONSIVE DESIGN

Breakpoint System

typescript
// Mobile-first breakpoints (min-width)
// sm: 640px  | md: 768px | lg: 1024px | xl: 1280px | 2xl: 1536px

<div className={cn(
  'flex flex-col',     // Mobile: stack vertically
  'md:flex-row',       // Tablet+: horizontal
  'lg:gap-8',          // Desktop+: larger gap
)}>
  <aside className="w-full md:w-64 lg:w-80">Sidebar</aside>
  <main className="flex-1">Content</main>
</div>

// Grid responsive
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
  {items.map(item => <Card key={item.id} />)}
</div>

Container Patterns

typescript
// Centered container with responsive padding
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
  <main className="max-w-4xl mx-auto">Content</main>
</div>

// Full-width section with constrained content
<section className="w-full bg-gray-100">
  <div className="container mx-auto py-12 px-4">
    Section content
  </div>
</section>

DARK MODE

Class Strategy

typescript
// tailwind.config.ts
export default {
  darkMode: 'class', // or 'media' for system preference
}

// Component with dark mode
<div className={cn(
  'bg-white text-gray-900',
  'dark:bg-gray-900 dark:text-white'
)}>
  <p className="text-gray-600 dark:text-gray-400">
    Subtle text
  </p>
</div>

// Theme toggle
function ThemeToggle() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  useEffect(() => {
    document.documentElement.classList.toggle('dark', theme === 'dark');
  }, [theme]);
  
  return (
    <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
      {theme === 'light' ? '🌙' : '☀️'}
    </button>
  );
}

COMPONENT PATTERNS

Button Component

typescript
import { cn } from '@/lib/utils';
import { Slot } from '@radix-ui/react-slot';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
  size?: 'sm' | 'md' | 'lg';
  asChild?: boolean;
}

const buttonVariants = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
  secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500',
  outline: 'border border-gray-300 bg-transparent hover:bg-gray-50 focus:ring-gray-500',
  ghost: 'bg-transparent hover:bg-gray-100 focus:ring-gray-500',
  destructive: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
};

const buttonSizes = {
  sm: 'h-8 px-3 text-sm',
  md: 'h-10 px-4',
  lg: 'h-12 px-6 text-lg',
};

export function Button({
  className,
  variant = 'primary',
  size = 'md',
  asChild = false,
  ...props
}: ButtonProps) {
  const Comp = asChild ? Slot : 'button';
  
  return (
    <Comp
      className={cn(
        'inline-flex items-center justify-center rounded-md font-medium',
        'transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
        'disabled:opacity-50 disabled:pointer-events-none',
        buttonVariants[variant],
        buttonSizes[size],
        className
      )}
      {...props}
    />
  );
}

Card Component

typescript
export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      className={cn(
        'rounded-lg border bg-white shadow-sm',
        'dark:bg-gray-800 dark:border-gray-700',
        className
      )}
      {...props}
    />
  );
}

export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  return <div className={cn('p-6 pb-4', className)} {...props} />;
}

export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
  return <h3 className={cn('text-lg font-semibold', className)} {...props} />;
}

export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  return <div className={cn('p-6 pt-0', className)} {...props} />;
}

// Usage
<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
  </CardHeader>
  <CardContent>
    <p>Card content here</p>
  </CardContent>
</Card>

Input Component

typescript
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  error?: boolean;
}

export const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ className, error, ...props }, ref) => {
    return (
      <input
        ref={ref}
        className={cn(
          'flex h-10 w-full rounded-md border px-3 py-2',
          'bg-white text-sm placeholder:text-gray-400',
          'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent',
          'disabled:cursor-not-allowed disabled:opacity-50',
          'dark:bg-gray-800 dark:border-gray-700',
          error && 'border-red-500 focus:ring-red-500',
          className
        )}
        {...props}
      />
    );
  }
);

LAYOUT PATTERNS

Flexbox Layouts

typescript
// Center everything
<div className="flex items-center justify-center min-h-screen">
  <Content />
</div>

// Space between header items
<header className="flex items-center justify-between px-4 h-16">
  <Logo />
  <Nav />
  <UserMenu />
</header>

// Sticky footer layout
<div className="flex flex-col min-h-screen">
  <Header />
  <main className="flex-1">{children}</main>
  <Footer />
</div>

Grid Layouts

typescript
// Auto-fit responsive grid
<div className="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-6">
  {items.map(item => <Card key={item.id} />)}
</div>

// Sidebar + content layout
<div className="grid grid-cols-1 lg:grid-cols-[240px_1fr] gap-6">
  <aside>Sidebar</aside>
  <main>Content</main>
</div>

// Dashboard grid
<div className="grid grid-cols-12 gap-4">
  <div className="col-span-12 lg:col-span-8">Main content</div>
  <div className="col-span-12 lg:col-span-4">Sidebar</div>
</div>

ANIMATION PATTERNS

Transitions

typescript
// Smooth hover effects
<button className={cn(
  'transition-all duration-200 ease-in-out',
  'hover:scale-105 hover:shadow-lg',
  'active:scale-95'
)}>
  Click me
</button>

// Fade in
<div className="animate-in fade-in duration-500">
  Content
</div>

// Custom animation
// tailwind.config.ts
{
  theme: {
    extend: {
      animation: {
        'slide-up': 'slideUp 0.3s ease-out',
      },
      keyframes: {
        slideUp: {
          '0%': { transform: 'translateY(10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
      },
    },
  },
}

ANTI-PATTERNS

❌ Avoid

typescript
// ❌ Inline styles with Tailwind
<div className="p-4" style={{ marginTop: '20px' }}>

// ❌ Too many arbitrary values
<div className="w-[347px] h-[89px] mt-[23px]">

// ❌ Not using design tokens
<div className="text-[#3b82f6]"> // Use text-blue-500

// ❌ Forgetting dark mode
<div className="bg-white text-black"> // No dark variant

✅ Prefer

typescript
// ✅ Consistent spacing scale
<div className="p-4 mt-5">

// ✅ Use design tokens
<div className="text-blue-500 dark:text-blue-400">

// ✅ Extend config for custom values
// tailwind.config.ts
{
  theme: {
    extend: {
      spacing: { '18': '4.5rem' }
    }
  }
}

QUICK REFERENCE

UtilityClass
Flexbox centerflex items-center justify-center
Absolute centerabsolute inset-0 m-auto
Truncate texttruncate or line-clamp-2
Screen reader onlysr-only
Focus ringfocus:ring-2 focus:ring-blue-500
Smooth transitiontransition-all duration-200