Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Implement and maintain Tailwind CSS design systems within a multi-agent Claude Code plugin ecosystem
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: tailwind-design-system3description: 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.4---56# Tailwind Design System (v4)78Build production-ready design systems with Tailwind CSS v4, including CSS-first configuration, design tokens, component variants, responsive patterns, and accessibility.910> **Note**: This skill targets Tailwind CSS v4 (2024+). For v3 projects, refer to the [upgrade guide](https://tailwindcss.com/docs/upgrade-guide).1112## When to Use This Skill1314- Creating a component library with Tailwind v415- Implementing design tokens and theming with CSS-first configuration16- Building responsive and accessible components17- Standardizing UI patterns across a codebase18- Migrating from Tailwind v3 to v419- Setting up dark mode with native CSS features2021## Key v4 Changes2223| v3 Pattern | v4 Pattern |24| ------------------------------------- | --------------------------------------------------------------------- |25| `tailwind.config.ts` | `@theme` in CSS |26| `@tailwind base/components/utilities` | `@import "tailwindcss"` |27| `darkMode: "class"` | `@custom-variant dark (&:where(.dark, .dark *))` |28| `theme.extend.colors` | `@theme { --color-*: value }` |29| `require("tailwindcss-animate")` | CSS `@keyframes` in `@theme` + `@starting-style` for entry animations |3031## Quick Start3233```css34/* app.css - Tailwind v4 CSS-first configuration */35@import "tailwindcss";3637/* Define your theme with @theme */38@theme {39/* Semantic color tokens using OKLCH for better color perception */40--color-background: oklch(100% 0 0);41--color-foreground: oklch(14.5% 0.025 264);4243--color-primary: oklch(14.5% 0.025 264);44--color-primary-foreground: oklch(98% 0.01 264);4546--color-secondary: oklch(96% 0.01 264);47--color-secondary-foreground: oklch(14.5% 0.025 264);4849--color-muted: oklch(96% 0.01 264);50--color-muted-foreground: oklch(46% 0.02 264);5152--color-accent: oklch(96% 0.01 264);53--color-accent-foreground: oklch(14.5% 0.025 264);5455--color-destructive: oklch(53% 0.22 27);56--color-destructive-foreground: oklch(98% 0.01 264);5758--color-border: oklch(91% 0.01 264);59--color-ring: oklch(14.5% 0.025 264);6061--color-card: oklch(100% 0 0);62--color-card-foreground: oklch(14.5% 0.025 264);6364/* Ring offset for focus states */65--color-ring-offset: oklch(100% 0 0);6667/* Radius tokens */68--radius-sm: 0.25rem;69--radius-md: 0.375rem;70--radius-lg: 0.5rem;71--radius-xl: 0.75rem;7273/* Animation tokens - keyframes inside @theme are output when referenced by --animate-* variables */74--animate-fade-in: fade-in 0.2s ease-out;75--animate-fade-out: fade-out 0.2s ease-in;76--animate-slide-in: slide-in 0.3s ease-out;77--animate-slide-out: slide-out 0.3s ease-in;7879@keyframes fade-in {80from {81opacity: 0;82}83to {84opacity: 1;85}86}8788@keyframes fade-out {89from {90opacity: 1;91}92to {93opacity: 0;94}95}9697@keyframes slide-in {98from {99transform: translateY(-0.5rem);100opacity: 0;101}102to {103transform: translateY(0);104opacity: 1;105}106}107108@keyframes slide-out {109from {110transform: translateY(0);111opacity: 1;112}113to {114transform: translateY(-0.5rem);115opacity: 0;116}117}118}119120/* Dark mode variant - use @custom-variant for class-based dark mode */121@custom-variant dark (&:where(.dark, .dark *));122123/* Dark mode theme overrides */124.dark {125--color-background: oklch(14.5% 0.025 264);126--color-foreground: oklch(98% 0.01 264);127128--color-primary: oklch(98% 0.01 264);129--color-primary-foreground: oklch(14.5% 0.025 264);130131--color-secondary: oklch(22% 0.02 264);132--color-secondary-foreground: oklch(98% 0.01 264);133134--color-muted: oklch(22% 0.02 264);135--color-muted-foreground: oklch(65% 0.02 264);136137--color-accent: oklch(22% 0.02 264);138--color-accent-foreground: oklch(98% 0.01 264);139140--color-destructive: oklch(42% 0.15 27);141--color-destructive-foreground: oklch(98% 0.01 264);142143--color-border: oklch(22% 0.02 264);144--color-ring: oklch(83% 0.02 264);145146--color-card: oklch(14.5% 0.025 264);147--color-card-foreground: oklch(98% 0.01 264);148149--color-ring-offset: oklch(14.5% 0.025 264);150}151152/* Base styles */153@layer base {154* {155@apply border-border;156}157158body {159@apply bg-background text-foreground antialiased;160}161}162```163164## Core Concepts165166### 1. Design Token Hierarchy167168```169Brand Tokens (abstract)170└── Semantic Tokens (purpose)171└── Component Tokens (specific)172173Example:174oklch(45% 0.2 260) → --color-primary → bg-primary175```176177### 2. Component Architecture178179```180Base styles → Variants → Sizes → States → Overrides181```182183## Patterns184185### Pattern 1: CVA (Class Variance Authority) Components186187```typescript188// components/ui/button.tsx189import { Slot } from '@radix-ui/react-slot'190import { cva, type VariantProps } from 'class-variance-authority'191import { cn } from '@/lib/utils'192193const buttonVariants = cva(194// Base styles - v4 uses native CSS variables195'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',196{197variants: {198variant: {199default: 'bg-primary text-primary-foreground hover:bg-primary/90',200destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',201outline: 'border border-border bg-background hover:bg-accent hover:text-accent-foreground',202secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',203ghost: 'hover:bg-accent hover:text-accent-foreground',204link: 'text-primary underline-offset-4 hover:underline',205},206size: {207default: 'h-10 px-4 py-2',208sm: 'h-9 rounded-md px-3',209lg: 'h-11 rounded-md px-8',210icon: 'size-10',211},212},213defaultVariants: {214variant: 'default',215size: 'default',216},217}218)219220export interface ButtonProps221extends React.ButtonHTMLAttributes<HTMLButtonElement>,222VariantProps<typeof buttonVariants> {223asChild?: boolean224}225226// React 19: No forwardRef needed227export function Button({228className,229variant,230size,231asChild = false,232ref,233...props234}: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) {235const Comp = asChild ? Slot : 'button'236return (237<Comp238className={cn(buttonVariants({ variant, size, className }))}239ref={ref}240{...props}241/>242)243}244245// Usage246<Button variant="destructive" size="lg">Delete</Button>247<Button variant="outline">Cancel</Button>248<Button asChild><Link href="/home">Home</Link></Button>249```250251### Pattern 2: Compound Components (React 19)252253```typescript254// components/ui/card.tsx255import { cn } from '@/lib/utils'256257// React 19: ref is a regular prop, no forwardRef258export function Card({259className,260ref,261...props262}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {263return (264<div265ref={ref}266className={cn(267'rounded-lg border border-border bg-card text-card-foreground shadow-sm',268className269)}270{...props}271/>272)273}274275export function CardHeader({276className,277ref,278...props279}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {280return (281<div282ref={ref}283className={cn('flex flex-col space-y-1.5 p-6', className)}284{...props}285/>286)287}288289export function CardTitle({290className,291ref,292...props293}: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLHeadingElement> }) {294return (295<h3296ref={ref}297className={cn('text-2xl font-semibold leading-none tracking-tight', className)}298{...props}299/>300)301}302303export function CardDescription({304className,305ref,306...props307}: React.HTMLAttributes<HTMLParagraphElement> & { ref?: React.Ref<HTMLParagraphElement> }) {308return (309<p310ref={ref}311className={cn('text-sm text-muted-foreground', className)}312{...props}313/>314)315}316317export function CardContent({318className,319ref,320...props321}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {322return (323<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />324)325}326327export function CardFooter({328className,329ref,330...props331}: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) {332return (333<div334ref={ref}335className={cn('flex items-center p-6 pt-0', className)}336{...props}337/>338)339}340341// Usage342<Card>343<CardHeader>344<CardTitle>Account</CardTitle>345<CardDescription>Manage your account settings</CardDescription>346</CardHeader>347<CardContent>348<form>...</form>349</CardContent>350<CardFooter>351<Button>Save</Button>352</CardFooter>353</Card>354```355356### Pattern 3: Form Components357358```typescript359// components/ui/input.tsx360import { cn } from '@/lib/utils'361362export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {363error?: string364ref?: React.Ref<HTMLInputElement>365}366367export function Input({ className, type, error, ref, ...props }: InputProps) {368return (369<div className="relative">370<input371type={type}372className={cn(373'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',374error && 'border-destructive focus-visible:ring-destructive',375className376)}377ref={ref}378aria-invalid={!!error}379aria-describedby={error ? `${props.id}-error` : undefined}380{...props}381/>382{error && (383<p384id={`${props.id}-error`}385className="mt-1 text-sm text-destructive"386role="alert"387>388{error}389</p>390)}391</div>392)393}394395// components/ui/label.tsx396import { cva, type VariantProps } from 'class-variance-authority'397398const labelVariants = cva(399'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'400)401402export function Label({403className,404ref,405...props406}: React.LabelHTMLAttributes<HTMLLabelElement> & { ref?: React.Ref<HTMLLabelElement> }) {407return (408<label ref={ref} className={cn(labelVariants(), className)} {...props} />409)410}411412// Usage with React Hook Form + Zod413import { useForm } from 'react-hook-form'414import { zodResolver } from '@hookform/resolvers/zod'415import { z } from 'zod'416417const schema = z.object({418email: z.string().email('Invalid email address'),419password: z.string().min(8, 'Password must be at least 8 characters'),420})421422function LoginForm() {423const { register, handleSubmit, formState: { errors } } = useForm({424resolver: zodResolver(schema),425})426427return (428<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">429<div className="space-y-2">430<Label htmlFor="email">Email</Label>431<Input432id="email"433type="email"434{...register('email')}435error={errors.email?.message}436/>437</div>438<div className="space-y-2">439<Label htmlFor="password">Password</Label>440<Input441id="password"442type="password"443{...register('password')}444error={errors.password?.message}445/>446</div>447<Button type="submit" className="w-full">Sign In</Button>448</form>449)450}451```452453### Pattern 4: Responsive Grid System454455```typescript456// components/ui/grid.tsx457import { cn } from '@/lib/utils'458import { cva, type VariantProps } from 'class-variance-authority'459460const gridVariants = cva('grid', {461variants: {462cols: {4631: 'grid-cols-1',4642: 'grid-cols-1 sm:grid-cols-2',4653: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',4664: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',4675: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5',4686: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',469},470gap: {471none: 'gap-0',472sm: 'gap-2',473md: 'gap-4',474lg: 'gap-6',475xl: 'gap-8',476},477},478defaultVariants: {479cols: 3,480gap: 'md',481},482})483484interface GridProps485extends React.HTMLAttributes<HTMLDivElement>,486VariantProps<typeof gridVariants> {}487488export function Grid({ className, cols, gap, ...props }: GridProps) {489return (490<div className={cn(gridVariants({ cols, gap, className }))} {...props} />491)492}493494// Container component495const containerVariants = cva('mx-auto w-full px-4 sm:px-6 lg:px-8', {496variants: {497size: {498sm: 'max-w-screen-sm',499md: 'max-w-screen-md',500lg: 'max-w-screen-lg',501xl: 'max-w-screen-xl',502'2xl': 'max-w-screen-2xl',503full: 'max-w-full',504},505},506defaultVariants: {507size: 'xl',508},509})510511interface ContainerProps512extends React.HTMLAttributes<HTMLDivElement>,513VariantProps<typeof containerVariants> {}514515export function Container({ className, size, ...props }: ContainerProps) {516return (517<div className={cn(containerVariants({ size, className }))} {...props} />518)519}520521// Usage522<Container>523<Grid cols={4} gap="lg">524{products.map((product) => (525<ProductCard key={product.id} product={product} />526))}527</Grid>528</Container>529```530531For advanced animation and dark mode patterns, see [references/advanced-patterns.md](references/advanced-patterns.md):532533- **Pattern 5: Native CSS Animations** — dialog `@keyframes`, native popover API with `@starting-style`, `allow-discrete` transitions, and a full `DialogContent`/`DialogOverlay` implementation using Radix UI534- **Pattern 6: Dark Mode** — `ThemeProvider` context with `localStorage` persistence, `prefers-color-scheme` detection, meta `theme-color` update, and a `ThemeToggle` button component535536## Utility Functions537538```typescript539// lib/utils.ts540import { type ClassValue, clsx } from "clsx";541import { twMerge } from "tailwind-merge";542543export function cn(...inputs: ClassValue[]) {544return twMerge(clsx(inputs));545}546547// Focus ring utility548export const focusRing = cn(549"focus-visible:outline-none focus-visible:ring-2",550"focus-visible:ring-ring focus-visible:ring-offset-2",551);552553// Disabled utility554export const disabled = "disabled:pointer-events-none disabled:opacity-50";555```556557For advanced v4 CSS patterns, the full v3-to-v4 migration checklist, and complete best practices, see [references/advanced-patterns.md](references/advanced-patterns.md):558559- **Custom `@utility`** — reusable CSS utilities for decorative lines and text gradients560- **Theme modifiers** — `@theme inline` (reference other CSS vars), `@theme static` (always output), `@import "tailwindcss" theme(static)`561- **Namespace overrides** — clearing default Tailwind color scales with `--color-*: initial`562- **Semi-transparent variants** — `color-mix()` for alpha scale generation563- **Container queries** — `--container-*` token definitions564- **v3→v4 migration checklist** — 10-item checklist covering config, directives, colors, dark mode, animations, React 19 ref changes565- **Best practices** — full Do's and Don'ts list566