Tailwind Design System

Build production-ready design systems with Tailwind CSS and type-safe variants

✨ The solution you've been looking for

Verified
Tested and verified by our team
25450 Stars

Build scalable design systems with Tailwind CSS, design tokens, component libraries, and responsive patterns. Use when creating component libraries, implementing design systems, or standardizing UI patterns.

tailwind design-system css components tokens dark-mode responsive accessibility
Repository

See It In Action

Interactive preview & real-world examples

Live Demo
Skill Demo Animation

AI Conversation Simulator

See how users interact with this skill

User Prompt

Help me create a Button component with multiple variants (primary, secondary, destructive) and sizes using CVA and Tailwind CSS

Skill Processing

Analyzing request...

Agent Response

A fully typed Button component with proper variants, accessibility features, and consistent styling patterns

Quick Start (3 Steps)

Get up and running in minutes

1

Install

claude-code skill install tailwind-design-system

claude-code skill install tailwind-design-system
2

Config

3

First Trigger

@tailwind-design-system help

Commands

CommandDescriptionRequired Args
@tailwind-design-system building-a-component-libraryCreate reusable UI components with consistent styling and type-safe variantsNone
@tailwind-design-system implementing-design-tokensSet up semantic color tokens and theming system for consistent brand applicationNone
@tailwind-design-system creating-responsive-grid-systemsBuild flexible, responsive layouts with consistent spacing and breakpointsNone

Typical Use Cases

Building a Component Library

Create reusable UI components with consistent styling and type-safe variants

Implementing Design Tokens

Set up semantic color tokens and theming system for consistent brand application

Creating Responsive Grid Systems

Build flexible, responsive layouts with consistent spacing and breakpoints

Overview

Tailwind Design System

Build production-ready design systems with Tailwind CSS, including design tokens, component variants, responsive patterns, and accessibility.

When to Use This Skill

  • Creating a component library with Tailwind
  • Implementing design tokens and theming
  • Building responsive and accessible components
  • Standardizing UI patterns across a codebase
  • Migrating to or extending Tailwind CSS
  • Setting up dark mode and color schemes

Core Concepts

1. Design Token Hierarchy

Brand Tokens (abstract)
    └── Semantic Tokens (purpose)
        └── Component Tokens (specific)

Example:
    blue-500 → primary → button-bg

2. Component Architecture

Base styles → Variants → Sizes → States → Overrides

Quick Start

 1// tailwind.config.ts
 2import type { Config } from "tailwindcss";
 3
 4const config: Config = {
 5  content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
 6  darkMode: "class",
 7  theme: {
 8    extend: {
 9      colors: {
10        // Semantic color tokens
11        primary: {
12          DEFAULT: "hsl(var(--primary))",
13          foreground: "hsl(var(--primary-foreground))",
14        },
15        secondary: {
16          DEFAULT: "hsl(var(--secondary))",
17          foreground: "hsl(var(--secondary-foreground))",
18        },
19        destructive: {
20          DEFAULT: "hsl(var(--destructive))",
21          foreground: "hsl(var(--destructive-foreground))",
22        },
23        muted: {
24          DEFAULT: "hsl(var(--muted))",
25          foreground: "hsl(var(--muted-foreground))",
26        },
27        accent: {
28          DEFAULT: "hsl(var(--accent))",
29          foreground: "hsl(var(--accent-foreground))",
30        },
31        background: "hsl(var(--background))",
32        foreground: "hsl(var(--foreground))",
33        border: "hsl(var(--border))",
34        ring: "hsl(var(--ring))",
35      },
36      borderRadius: {
37        lg: "var(--radius)",
38        md: "calc(var(--radius) - 2px)",
39        sm: "calc(var(--radius) - 4px)",
40      },
41    },
42  },
43  plugins: [require("tailwindcss-animate")],
44};
45
46export default config;
 1/* globals.css */
 2@tailwind base;
 3@tailwind components;
 4@tailwind utilities;
 5
 6@layer base {
 7  :root {
 8    --background: 0 0% 100%;
 9    --foreground: 222.2 84% 4.9%;
10    --primary: 222.2 47.4% 11.2%;
11    --primary-foreground: 210 40% 98%;
12    --secondary: 210 40% 96.1%;
13    --secondary-foreground: 222.2 47.4% 11.2%;
14    --muted: 210 40% 96.1%;
15    --muted-foreground: 215.4 16.3% 46.9%;
16    --accent: 210 40% 96.1%;
17    --accent-foreground: 222.2 47.4% 11.2%;
18    --destructive: 0 84.2% 60.2%;
19    --destructive-foreground: 210 40% 98%;
20    --border: 214.3 31.8% 91.4%;
21    --ring: 222.2 84% 4.9%;
22    --radius: 0.5rem;
23  }
24
25  .dark {
26    --background: 222.2 84% 4.9%;
27    --foreground: 210 40% 98%;
28    --primary: 210 40% 98%;
29    --primary-foreground: 222.2 47.4% 11.2%;
30    --secondary: 217.2 32.6% 17.5%;
31    --secondary-foreground: 210 40% 98%;
32    --muted: 217.2 32.6% 17.5%;
33    --muted-foreground: 215 20.2% 65.1%;
34    --accent: 217.2 32.6% 17.5%;
35    --accent-foreground: 210 40% 98%;
36    --destructive: 0 62.8% 30.6%;
37    --destructive-foreground: 210 40% 98%;
38    --border: 217.2 32.6% 17.5%;
39    --ring: 212.7 26.8% 83.9%;
40  }
41}

Patterns

Pattern 1: CVA (Class Variance Authority) Components

 1// components/ui/button.tsx
 2import { cva, type VariantProps } from 'class-variance-authority'
 3import { forwardRef } from 'react'
 4import { cn } from '@/lib/utils'
 5
 6const buttonVariants = cva(
 7  // Base styles
 8  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
 9  {
10    variants: {
11      variant: {
12        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
14        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
15        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
16        ghost: 'hover:bg-accent hover:text-accent-foreground',
17        link: 'text-primary underline-offset-4 hover:underline',
18      },
19      size: {
20        default: 'h-10 px-4 py-2',
21        sm: 'h-9 rounded-md px-3',
22        lg: 'h-11 rounded-md px-8',
23        icon: 'h-10 w-10',
24      },
25    },
26    defaultVariants: {
27      variant: 'default',
28      size: 'default',
29    },
30  }
31)
32
33export interface ButtonProps
34  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35    VariantProps<typeof buttonVariants> {
36  asChild?: boolean
37}
38
39const Button = forwardRef<HTMLButtonElement, ButtonProps>(
40  ({ className, variant, size, asChild = false, ...props }, ref) => {
41    const Comp = asChild ? Slot : 'button'
42    return (
43      <Comp
44        className={cn(buttonVariants({ variant, size, className }))}
45        ref={ref}
46        {...props}
47      />
48    )
49  }
50)
51Button.displayName = 'Button'
52
53export { Button, buttonVariants }
54
55// Usage
56<Button variant="destructive" size="lg">Delete</Button>
57<Button variant="outline">Cancel</Button>
58<Button asChild><Link href="/home">Home</Link></Button>

Pattern 2: Compound Components

 1// components/ui/card.tsx
 2import { cn } from '@/lib/utils'
 3import { forwardRef } from 'react'
 4
 5const Card = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
 6  ({ className, ...props }, ref) => (
 7    <div
 8      ref={ref}
 9      className={cn(
10        'rounded-lg border bg-card text-card-foreground shadow-sm',
11        className
12      )}
13      {...props}
14    />
15  )
16)
17Card.displayName = 'Card'
18
19const CardHeader = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
20  ({ className, ...props }, ref) => (
21    <div
22      ref={ref}
23      className={cn('flex flex-col space-y-1.5 p-6', className)}
24      {...props}
25    />
26  )
27)
28CardHeader.displayName = 'CardHeader'
29
30const CardTitle = forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
31  ({ className, ...props }, ref) => (
32    <h3
33      ref={ref}
34      className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
35      {...props}
36    />
37  )
38)
39CardTitle.displayName = 'CardTitle'
40
41const CardDescription = forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
42  ({ className, ...props }, ref) => (
43    <p
44      ref={ref}
45      className={cn('text-sm text-muted-foreground', className)}
46      {...props}
47    />
48  )
49)
50CardDescription.displayName = 'CardDescription'
51
52const CardContent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
53  ({ className, ...props }, ref) => (
54    <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
55  )
56)
57CardContent.displayName = 'CardContent'
58
59const CardFooter = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
60  ({ className, ...props }, ref) => (
61    <div
62      ref={ref}
63      className={cn('flex items-center p-6 pt-0', className)}
64      {...props}
65    />
66  )
67)
68CardFooter.displayName = 'CardFooter'
69
70export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter }
71
72// Usage
73<Card>
74  <CardHeader>
75    <CardTitle>Account</CardTitle>
76    <CardDescription>Manage your account settings</CardDescription>
77  </CardHeader>
78  <CardContent>
79    <form>...</form>
80  </CardContent>
81  <CardFooter>
82    <Button>Save</Button>
83  </CardFooter>
84</Card>

Pattern 3: Form Components

 1// components/ui/input.tsx
 2import { forwardRef } from 'react'
 3import { cn } from '@/lib/utils'
 4
 5export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 6  error?: string
 7}
 8
 9const Input = forwardRef<HTMLInputElement, InputProps>(
10  ({ className, type, error, ...props }, ref) => {
11    return (
12      <div className="relative">
13        <input
14          type={type}
15          className={cn(
16            'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
17            error && 'border-destructive focus-visible:ring-destructive',
18            className
19          )}
20          ref={ref}
21          aria-invalid={!!error}
22          aria-describedby={error ? `${props.id}-error` : undefined}
23          {...props}
24        />
25        {error && (
26          <p
27            id={`${props.id}-error`}
28            className="mt-1 text-sm text-destructive"
29            role="alert"
30          >
31            {error}
32          </p>
33        )}
34      </div>
35    )
36  }
37)
38Input.displayName = 'Input'
39
40// components/ui/label.tsx
41import { cva, type VariantProps } from 'class-variance-authority'
42
43const labelVariants = cva(
44  'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
45)
46
47const Label = forwardRef<HTMLLabelElement, React.LabelHTMLAttributes<HTMLLabelElement>>(
48  ({ className, ...props }, ref) => (
49    <label ref={ref} className={cn(labelVariants(), className)} {...props} />
50  )
51)
52Label.displayName = 'Label'
53
54// Usage with React Hook Form
55import { useForm } from 'react-hook-form'
56import { zodResolver } from '@hookform/resolvers/zod'
57import * as z from 'zod'
58
59const schema = z.object({
60  email: z.string().email('Invalid email address'),
61  password: z.string().min(8, 'Password must be at least 8 characters'),
62})
63
64function LoginForm() {
65  const { register, handleSubmit, formState: { errors } } = useForm({
66    resolver: zodResolver(schema),
67  })
68
69  return (
70    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
71      <div className="space-y-2">
72        <Label htmlFor="email">Email</Label>
73        <Input
74          id="email"
75          type="email"
76          {...register('email')}
77          error={errors.email?.message}
78        />
79      </div>
80      <div className="space-y-2">
81        <Label htmlFor="password">Password</Label>
82        <Input
83          id="password"
84          type="password"
85          {...register('password')}
86          error={errors.password?.message}
87        />
88      </div>
89      <Button type="submit" className="w-full">Sign In</Button>
90    </form>
91  )
92}

Pattern 4: Responsive Grid System

 1// components/ui/grid.tsx
 2import { cn } from '@/lib/utils'
 3import { cva, type VariantProps } from 'class-variance-authority'
 4
 5const gridVariants = cva('grid', {
 6  variants: {
 7    cols: {
 8      1: 'grid-cols-1',
 9      2: 'grid-cols-1 sm:grid-cols-2',
10      3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
11      4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',
12      5: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5',
13      6: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',
14    },
15    gap: {
16      none: 'gap-0',
17      sm: 'gap-2',
18      md: 'gap-4',
19      lg: 'gap-6',
20      xl: 'gap-8',
21    },
22  },
23  defaultVariants: {
24    cols: 3,
25    gap: 'md',
26  },
27})
28
29interface GridProps
30  extends React.HTMLAttributes<HTMLDivElement>,
31    VariantProps<typeof gridVariants> {}
32
33export function Grid({ className, cols, gap, ...props }: GridProps) {
34  return (
35    <div className={cn(gridVariants({ cols, gap, className }))} {...props} />
36  )
37}
38
39// Container component
40const containerVariants = cva('mx-auto w-full px-4 sm:px-6 lg:px-8', {
41  variants: {
42    size: {
43      sm: 'max-w-screen-sm',
44      md: 'max-w-screen-md',
45      lg: 'max-w-screen-lg',
46      xl: 'max-w-screen-xl',
47      '2xl': 'max-w-screen-2xl',
48      full: 'max-w-full',
49    },
50  },
51  defaultVariants: {
52    size: 'xl',
53  },
54})
55
56interface ContainerProps
57  extends React.HTMLAttributes<HTMLDivElement>,
58    VariantProps<typeof containerVariants> {}
59
60export function Container({ className, size, ...props }: ContainerProps) {
61  return (
62    <div className={cn(containerVariants({ size, className }))} {...props} />
63  )
64}
65
66// Usage
67<Container>
68  <Grid cols={4} gap="lg">
69    {products.map((product) => (
70      <ProductCard key={product.id} product={product} />
71    ))}
72  </Grid>
73</Container>

Pattern 5: Animation Utilities

 1// lib/animations.ts - Tailwind CSS Animate utilities
 2import { cn } from './utils'
 3
 4export const fadeIn = 'animate-in fade-in duration-300'
 5export const fadeOut = 'animate-out fade-out duration-300'
 6export const slideInFromTop = 'animate-in slide-in-from-top duration-300'
 7export const slideInFromBottom = 'animate-in slide-in-from-bottom duration-300'
 8export const slideInFromLeft = 'animate-in slide-in-from-left duration-300'
 9export const slideInFromRight = 'animate-in slide-in-from-right duration-300'
10export const zoomIn = 'animate-in zoom-in-95 duration-300'
11export const zoomOut = 'animate-out zoom-out-95 duration-300'
12
13// Compound animations
14export const modalEnter = cn(fadeIn, zoomIn, 'duration-200')
15export const modalExit = cn(fadeOut, zoomOut, 'duration-200')
16export const dropdownEnter = cn(fadeIn, slideInFromTop, 'duration-150')
17export const dropdownExit = cn(fadeOut, 'slide-out-to-top', 'duration-150')
18
19// components/ui/dialog.tsx
20import * as DialogPrimitive from '@radix-ui/react-dialog'
21
22const DialogOverlay = forwardRef<
23  React.ElementRef<typeof DialogPrimitive.Overlay>,
24  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
25>(({ className, ...props }, ref) => (
26  <DialogPrimitive.Overlay
27    ref={ref}
28    className={cn(
29      'fixed inset-0 z-50 bg-black/80',
30      'data-[state=open]:animate-in data-[state=closed]:animate-out',
31      'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
32      className
33    )}
34    {...props}
35  />
36))
37
38const DialogContent = forwardRef<
39  React.ElementRef<typeof DialogPrimitive.Content>,
40  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
41>(({ className, children, ...props }, ref) => (
42  <DialogPortal>
43    <DialogOverlay />
44    <DialogPrimitive.Content
45      ref={ref}
46      className={cn(
47        'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg',
48        'data-[state=open]:animate-in data-[state=closed]:animate-out',
49        'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
50        'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
51        'data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]',
52        'data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]',
53        'sm:rounded-lg',
54        className
55      )}
56      {...props}
57    >
58      {children}
59    </DialogPrimitive.Content>
60  </DialogPortal>
61))

Pattern 6: Dark Mode Implementation

 1// providers/ThemeProvider.tsx
 2'use client'
 3
 4import { createContext, useContext, useEffect, useState } from 'react'
 5
 6type Theme = 'dark' | 'light' | 'system'
 7
 8interface ThemeProviderProps {
 9  children: React.ReactNode
10  defaultTheme?: Theme
11  storageKey?: string
12}
13
14interface ThemeContextType {
15  theme: Theme
16  setTheme: (theme: Theme) => void
17  resolvedTheme: 'dark' | 'light'
18}
19
20const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
21
22export function ThemeProvider({
23  children,
24  defaultTheme = 'system',
25  storageKey = 'theme',
26}: ThemeProviderProps) {
27  const [theme, setTheme] = useState<Theme>(defaultTheme)
28  const [resolvedTheme, setResolvedTheme] = useState<'dark' | 'light'>('light')
29
30  useEffect(() => {
31    const stored = localStorage.getItem(storageKey) as Theme | null
32    if (stored) setTheme(stored)
33  }, [storageKey])
34
35  useEffect(() => {
36    const root = window.document.documentElement
37    root.classList.remove('light', 'dark')
38
39    let resolved: 'dark' | 'light'
40
41    if (theme === 'system') {
42      resolved = window.matchMedia('(prefers-color-scheme: dark)').matches
43        ? 'dark'
44        : 'light'
45    } else {
46      resolved = theme
47    }
48
49    root.classList.add(resolved)
50    setResolvedTheme(resolved)
51  }, [theme])
52
53  const value = {
54    theme,
55    setTheme: (newTheme: Theme) => {
56      localStorage.setItem(storageKey, newTheme)
57      setTheme(newTheme)
58    },
59    resolvedTheme,
60  }
61
62  return (
63    <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
64  )
65}
66
67export const useTheme = () => {
68  const context = useContext(ThemeContext)
69  if (!context) throw new Error('useTheme must be used within ThemeProvider')
70  return context
71}
72
73// components/ThemeToggle.tsx
74import { Moon, Sun } from 'lucide-react'
75import { useTheme } from '@/providers/ThemeProvider'
76
77export function ThemeToggle() {
78  const { resolvedTheme, setTheme } = useTheme()
79
80  return (
81    <Button
82      variant="ghost"
83      size="icon"
84      onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
85    >
86      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
87      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
88      <span className="sr-only">Toggle theme</span>
89    </Button>
90  )
91}

Utility Functions

 1// lib/utils.ts
 2import { type ClassValue, clsx } from "clsx";
 3import { twMerge } from "tailwind-merge";
 4
 5export function cn(...inputs: ClassValue[]) {
 6  return twMerge(clsx(inputs));
 7}
 8
 9// Focus ring utility
10export const focusRing = cn(
11  "focus-visible:outline-none focus-visible:ring-2",
12  "focus-visible:ring-ring focus-visible:ring-offset-2",
13);
14
15// Disabled utility
16export const disabled = "disabled:pointer-events-none disabled:opacity-50";

Best Practices

Do’s

  • Use CSS variables - Enable runtime theming
  • Compose with CVA - Type-safe variants
  • Use semantic colors - primary not blue-500
  • Forward refs - Enable composition
  • Add accessibility - ARIA attributes, focus states

Don’ts

  • Don’t use arbitrary values - Extend theme instead
  • Don’t nest @apply - Hurts readability
  • Don’t skip focus states - Keyboard users need them
  • Don’t hardcode colors - Use semantic tokens
  • Don’t forget dark mode - Test both themes

Resources

What Users Are Saying

Real feedback from the community

Environment Matrix

Dependencies

Tailwind CSS 3.0+
class-variance-authority (CVA)
clsx
tailwind-merge
tailwindcss-animate (optional)

Framework Support

React ✓ (recommended) Next.js ✓ (recommended) TypeScript ✓ Radix UI ✓ React Hook Form ✓

Context Window

Token Usage ~3K-8K tokens for complex design system implementations

Security & Privacy

Information

Author
wshobson
Updated
2026-01-30
Category
cms-platforms