Tailwind Design System
Build production-ready design systems with Tailwind CSS and type-safe variants
✨ The solution you've been looking for
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.
See It In Action
Interactive preview & real-world examples
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
Install
claude-code skill install tailwind-design-system
claude-code skill install tailwind-design-systemConfig
First Trigger
@tailwind-design-system helpCommands
| Command | Description | Required Args |
|---|---|---|
| @tailwind-design-system building-a-component-library | Create reusable UI components with consistent styling and type-safe variants | None |
| @tailwind-design-system implementing-design-tokens | Set up semantic color tokens and theming system for consistent brand application | None |
| @tailwind-design-system creating-responsive-grid-systems | Build flexible, responsive layouts with consistent spacing and breakpoints | None |
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 -
primarynotblue-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
Framework Support
Context Window
Security & Privacy
Information
- Author
- wshobson
- Updated
- 2026-01-30
- Category
- cms-platforms
Related Skills
Tailwind Design System
Build scalable design systems with Tailwind CSS, design tokens, component libraries, and responsive …
View Details →Tailwind 4
Tailwind CSS 4 patterns and best practices. Trigger: When styling with Tailwind (className, …
View Details →Tailwind 4
Tailwind CSS 4 patterns and best practices. Trigger: When styling with Tailwind (className, …
View Details →