Claude Agent Skill · by Wshobson

Tailwind Design System

Builds component libraries with Tailwind v4's new CSS-first configuration, swapping out the old JavaScript config for @theme blocks and native CSS variables. Ge

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill tailwind-design-system
Works with Paperclip

How Tailwind Design System fits into a Paperclip company.

Tailwind Design System drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md565 lines
Expand
---name: tailwind-design-systemdescription: Build scalable design systems with Tailwind CSS v4, design tokens, component libraries, and responsive patterns. Use when creating component libraries, implementing design systems, or standardizing UI patterns.--- # Tailwind Design System (v4) Build production-ready design systems with Tailwind CSS v4, including CSS-first configuration, design tokens, component variants, responsive patterns, and accessibility. > **Note**: This skill targets Tailwind CSS v4 (2024+). For v3 projects, refer to the [upgrade guide](https://tailwindcss.com/docs/upgrade-guide). ## When to Use This Skill - Creating a component library with Tailwind v4- Implementing design tokens and theming with CSS-first configuration- Building responsive and accessible components- Standardizing UI patterns across a codebase- Migrating from Tailwind v3 to v4- Setting up dark mode with native CSS features ## Key v4 Changes | v3 Pattern                            | v4 Pattern                                                            || ------------------------------------- | --------------------------------------------------------------------- || `tailwind.config.ts`                  | `@theme` in CSS                                                       || `@tailwind base/components/utilities` | `@import "tailwindcss"`                                               || `darkMode: "class"`                   | `@custom-variant dark (&:where(.dark, .dark *))`                      || `theme.extend.colors`                 | `@theme { --color-*: value }`                                         || `require("tailwindcss-animate")`      | CSS `@keyframes` in `@theme` + `@starting-style` for entry animations | ## Quick Start ```css/* app.css - Tailwind v4 CSS-first configuration */@import "tailwindcss"; /* Define your theme with @theme */@theme {  /* Semantic color tokens using OKLCH for better color perception */  --color-background: oklch(100% 0 0);  --color-foreground: oklch(14.5% 0.025 264);   --color-primary: oklch(14.5% 0.025 264);  --color-primary-foreground: oklch(98% 0.01 264);   --color-secondary: oklch(96% 0.01 264);  --color-secondary-foreground: oklch(14.5% 0.025 264);   --color-muted: oklch(96% 0.01 264);  --color-muted-foreground: oklch(46% 0.02 264);   --color-accent: oklch(96% 0.01 264);  --color-accent-foreground: oklch(14.5% 0.025 264);   --color-destructive: oklch(53% 0.22 27);  --color-destructive-foreground: oklch(98% 0.01 264);   --color-border: oklch(91% 0.01 264);  --color-ring: oklch(14.5% 0.025 264);   --color-card: oklch(100% 0 0);  --color-card-foreground: oklch(14.5% 0.025 264);   /* Ring offset for focus states */  --color-ring-offset: oklch(100% 0 0);   /* Radius tokens */  --radius-sm: 0.25rem;  --radius-md: 0.375rem;  --radius-lg: 0.5rem;  --radius-xl: 0.75rem;   /* Animation tokens - keyframes inside @theme are output when referenced by --animate-* variables */  --animate-fade-in: fade-in 0.2s ease-out;  --animate-fade-out: fade-out 0.2s ease-in;  --animate-slide-in: slide-in 0.3s ease-out;  --animate-slide-out: slide-out 0.3s ease-in;   @keyframes fade-in {    from {      opacity: 0;    }    to {      opacity: 1;    }  }   @keyframes fade-out {    from {      opacity: 1;    }    to {      opacity: 0;    }  }   @keyframes slide-in {    from {      transform: translateY(-0.5rem);      opacity: 0;    }    to {      transform: translateY(0);      opacity: 1;    }  }   @keyframes slide-out {    from {      transform: translateY(0);      opacity: 1;    }    to {      transform: translateY(-0.5rem);      opacity: 0;    }  }} /* Dark mode variant - use @custom-variant for class-based dark mode */@custom-variant dark (&:where(.dark, .dark *)); /* Dark mode theme overrides */.dark {  --color-background: oklch(14.5% 0.025 264);  --color-foreground: oklch(98% 0.01 264);   --color-primary: oklch(98% 0.01 264);  --color-primary-foreground: oklch(14.5% 0.025 264);   --color-secondary: oklch(22% 0.02 264);  --color-secondary-foreground: oklch(98% 0.01 264);   --color-muted: oklch(22% 0.02 264);  --color-muted-foreground: oklch(65% 0.02 264);   --color-accent: oklch(22% 0.02 264);  --color-accent-foreground: oklch(98% 0.01 264);   --color-destructive: oklch(42% 0.15 27);  --color-destructive-foreground: oklch(98% 0.01 264);   --color-border: oklch(22% 0.02 264);  --color-ring: oklch(83% 0.02 264);   --color-card: oklch(14.5% 0.025 264);  --color-card-foreground: oklch(98% 0.01 264);   --color-ring-offset: oklch(14.5% 0.025 264);} /* Base styles */@layer base {  * {    @apply border-border;  }   body {    @apply bg-background text-foreground antialiased;  }}``` ## Core Concepts ### 1. Design Token Hierarchy ```Brand Tokens (abstract)    └── Semantic Tokens (purpose)        └── Component Tokens (specific) Example:    oklch(45% 0.2 260) → --color-primary → bg-primary``` ### 2. Component Architecture ```Base styles → Variants → Sizes → States → Overrides``` ## Patterns ### Pattern 1: CVA (Class Variance Authority) Components ```typescript// components/ui/button.tsximport { Slot } from '@radix-ui/react-slot'import { cva, type VariantProps } from 'class-variance-authority'import { cn } from '@/lib/utils' const buttonVariants = cva(  // Base styles - v4 uses native CSS variables  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium 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',  {    variants: {      variant: {        default: 'bg-primary text-primary-foreground hover:bg-primary/90',        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',        outline: 'border border-border bg-background hover:bg-accent hover:text-accent-foreground',        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',        ghost: 'hover:bg-accent hover:text-accent-foreground',        link: 'text-primary underline-offset-4 hover:underline',      },      size: {        default: 'h-10 px-4 py-2',        sm: 'h-9 rounded-md px-3',        lg: 'h-11 rounded-md px-8',        icon: 'size-10',      },    },    defaultVariants: {      variant: 'default',      size: 'default',    },  }) export interface ButtonProps  extends React.ButtonHTMLAttributes<HTMLButtonElement>,    VariantProps<typeof buttonVariants> {  asChild?: boolean} // React 19: No forwardRef neededexport function Button({  className,  variant,  size,  asChild = false,  ref,  ...props}: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) {  const Comp = asChild ? Slot : 'button'  return (    <Comp      className={cn(buttonVariants({ variant, size, className }))}      ref={ref}      {...props}    />  )} // Usage<Button variant="destructive" size="lg">Delete</Button><Button variant="outline">Cancel</Button><Button asChild><Link href="/home">Home</Link></Button>``` ### Pattern 2: Compound Components (React 19) ```typescript// components/ui/card.tsximport { cn } from '@/lib/utils' // React 19: ref is a regular prop, no forwardRefexport function Card({  className,  ref,  ...props}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {  return (    <div      ref={ref}      className={cn(        'rounded-lg border border-border bg-card text-card-foreground shadow-sm',        className      )}      {...props}    />  )} export function CardHeader({  className,  ref,  ...props}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {  return (    <div      ref={ref}      className={cn('flex flex-col space-y-1.5 p-6', className)}      {...props}    />  )} export function CardTitle({  className,  ref,  ...props}: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLHeadingElement> }) {  return (    <h3      ref={ref}      className={cn('text-2xl font-semibold leading-none tracking-tight', className)}      {...props}    />  )} export function CardDescription({  className,  ref,  ...props}: React.HTMLAttributes<HTMLParagraphElement> & { ref?: React.Ref<HTMLParagraphElement> }) {  return (    <p      ref={ref}      className={cn('text-sm text-muted-foreground', className)}      {...props}    />  )} export function CardContent({  className,  ref,  ...props}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {  return (    <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />  )} export function CardFooter({  className,  ref,  ...props}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {  return (    <div      ref={ref}      className={cn('flex items-center p-6 pt-0', className)}      {...props}    />  )} // Usage<Card>  <CardHeader>    <CardTitle>Account</CardTitle>    <CardDescription>Manage your account settings</CardDescription>  </CardHeader>  <CardContent>    <form>...</form>  </CardContent>  <CardFooter>    <Button>Save</Button>  </CardFooter></Card>``` ### Pattern 3: Form Components ```typescript// components/ui/input.tsximport { cn } from '@/lib/utils' export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {  error?: string  ref?: React.Ref<HTMLInputElement>} export function Input({ className, type, error, ref, ...props }: InputProps) {  return (    <div className="relative">      <input        type={type}        className={cn(          'flex h-10 w-full rounded-md border border-border 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',          error && 'border-destructive focus-visible:ring-destructive',          className        )}        ref={ref}        aria-invalid={!!error}        aria-describedby={error ? `${props.id}-error` : undefined}        {...props}      />      {error && (        <p          id={`${props.id}-error`}          className="mt-1 text-sm text-destructive"          role="alert"        >          {error}        </p>      )}    </div>  )} // components/ui/label.tsximport { cva, type VariantProps } from 'class-variance-authority' const labelVariants = cva(  'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70') export function Label({  className,  ref,  ...props}: React.LabelHTMLAttributes<HTMLLabelElement> & { ref?: React.Ref<HTMLLabelElement> }) {  return (    <label ref={ref} className={cn(labelVariants(), className)} {...props} />  )} // Usage with React Hook Form + Zodimport { useForm } from 'react-hook-form'import { zodResolver } from '@hookform/resolvers/zod'import { z } from 'zod' const schema = z.object({  email: z.string().email('Invalid email address'),  password: z.string().min(8, 'Password must be at least 8 characters'),}) function LoginForm() {  const { register, handleSubmit, formState: { errors } } = useForm({    resolver: zodResolver(schema),  })   return (    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">      <div className="space-y-2">        <Label htmlFor="email">Email</Label>        <Input          id="email"          type="email"          {...register('email')}          error={errors.email?.message}        />      </div>      <div className="space-y-2">        <Label htmlFor="password">Password</Label>        <Input          id="password"          type="password"          {...register('password')}          error={errors.password?.message}        />      </div>      <Button type="submit" className="w-full">Sign In</Button>    </form>  )}``` ### Pattern 4: Responsive Grid System ```typescript// components/ui/grid.tsximport { cn } from '@/lib/utils'import { cva, type VariantProps } from 'class-variance-authority' const gridVariants = cva('grid', {  variants: {    cols: {      1: 'grid-cols-1',      2: 'grid-cols-1 sm:grid-cols-2',      3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',      4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',      5: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5',      6: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',    },    gap: {      none: 'gap-0',      sm: 'gap-2',      md: 'gap-4',      lg: 'gap-6',      xl: 'gap-8',    },  },  defaultVariants: {    cols: 3,    gap: 'md',  },}) interface GridProps  extends React.HTMLAttributes<HTMLDivElement>,    VariantProps<typeof gridVariants> {} export function Grid({ className, cols, gap, ...props }: GridProps) {  return (    <div className={cn(gridVariants({ cols, gap, className }))} {...props} />  )} // Container componentconst containerVariants = cva('mx-auto w-full px-4 sm:px-6 lg:px-8', {  variants: {    size: {      sm: 'max-w-screen-sm',      md: 'max-w-screen-md',      lg: 'max-w-screen-lg',      xl: 'max-w-screen-xl',      '2xl': 'max-w-screen-2xl',      full: 'max-w-full',    },  },  defaultVariants: {    size: 'xl',  },}) interface ContainerProps  extends React.HTMLAttributes<HTMLDivElement>,    VariantProps<typeof containerVariants> {} export function Container({ className, size, ...props }: ContainerProps) {  return (    <div className={cn(containerVariants({ size, className }))} {...props} />  )} // Usage<Container>  <Grid cols={4} gap="lg">    {products.map((product) => (      <ProductCard key={product.id} product={product} />    ))}  </Grid></Container>``` For advanced animation and dark mode patterns, see [references/advanced-patterns.md](references/advanced-patterns.md): - **Pattern 5: Native CSS Animations** — dialog `@keyframes`, native popover API with `@starting-style`, `allow-discrete` transitions, and a full `DialogContent`/`DialogOverlay` implementation using Radix UI- **Pattern 6: Dark Mode** — `ThemeProvider` context with `localStorage` persistence, `prefers-color-scheme` detection, meta `theme-color` update, and a `ThemeToggle` button component ## Utility Functions ```typescript// lib/utils.tsimport { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs));} // Focus ring utilityexport const focusRing = cn(  "focus-visible:outline-none focus-visible:ring-2",  "focus-visible:ring-ring focus-visible:ring-offset-2",); // Disabled utilityexport const disabled = "disabled:pointer-events-none disabled:opacity-50";``` For advanced v4 CSS patterns, the full v3-to-v4 migration checklist, and complete best practices, see [references/advanced-patterns.md](references/advanced-patterns.md): - **Custom `@utility`** — reusable CSS utilities for decorative lines and text gradients- **Theme modifiers** — `@theme inline` (reference other CSS vars), `@theme static` (always output), `@import "tailwindcss" theme(static)`- **Namespace overrides** — clearing default Tailwind color scales with `--color-*: initial`- **Semi-transparent variants** — `color-mix()` for alpha scale generation- **Container queries** — `--container-*` token definitions- **v3→v4 migration checklist** — 10-item checklist covering config, directives, colors, dark mode, animations, React 19 ref changes- **Best practices** — full Do's and Don'ts list