Tailwind CSS Advanced Component Patterns Component Variants with CVA Class Variance Authority Integration npm install class-variance-authority
// components/Button.tsx import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils';
const buttonVariants = cva( // Base styles 'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'bg-brand-500 text-white hover:bg-brand-600 focus-visible:ring-brand-500', secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500', outline: 'border border-gray-300 bg-transparent hover:bg-gray-100 focus-visible:ring-gray-500', ghost: 'hover:bg-gray-100 focus-visible:ring-gray-500', destructive: 'bg-red-500 text-white hover:bg-red-600 focus-visible:ring-red-500', link: 'text-brand-500 underline-offset-4 hover:underline', }, size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4 text-sm', lg: 'h-12 px-6 text-base', xl: 'h-14 px-8 text-lg', icon: 'h-10 w-10', }, fullWidth: { true: 'w-full', }, }, compoundVariants: [ { variant: 'outline', size: 'sm', className: 'border', }, { variant: 'outline', size: ['md', 'lg', 'xl'], className: 'border-2', }, ], defaultVariants: { variant: 'default', size: 'md', }, } );
export interface ButtonProps
extends React.ButtonHTMLAttributes
export function Button({ className, variant, size, fullWidth, asChild = false, ...props }: ButtonProps) { return ( ); }
Usage
Compound Components Pattern Context-Based Component System // components/Card/index.tsx import { createContext, useContext, forwardRef } from 'react'; import { cn } from '@/lib/utils';
// Context for shared state const CardContext = createContext<{ variant?: 'default' | 'elevated' | 'outline' }>({});
// Root component
interface CardProps extends React.HTMLAttributes
const Card = forwardRef
return (
<CardContext.Provider value={{ variant }}>
<div
ref={ref}
className={cn(
'rounded-xl',
variants[variant],
className
)}
{...props}
>
{children}
</div>
</CardContext.Provider>
);
} );
// Sub-components
const CardHeader = forwardRef
const CardTitle = forwardRef
const CardDescription = forwardRef
const CardContent = forwardRef
const CardFooter = forwardRef
// Named exports Card.displayName = 'Card'; CardHeader.displayName = 'CardHeader'; CardTitle.displayName = 'CardTitle'; CardDescription.displayName = 'CardDescription'; CardContent.displayName = 'CardContent'; CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
Usage
Data Attribute Variants CSS-Based State Management / Define data attribute variants / @custom-variant data-state-open (&[data-state="open"]); @custom-variant data-state-closed (&[data-state="closed"]); @custom-variant data-side-top (&[data-side="top"]); @custom-variant data-side-bottom (&[data-side="bottom"]); @custom-variant data-side-left (&[data-side="left"]); @custom-variant data-side-right (&[data-side="right"]); @custom-variant data-highlighted (&[data-highlighted]); @custom-variant data-disabled (&[data-disabled]);
// Dropdown component using data attributes function DropdownContent({ children, ...props }) { return ( <div data-state={isOpen ? 'open' : 'closed'} data-side={side} className=" absolute z-50 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-white p-1 shadow-lg
data-state-open:animate-in
data-state-open:fade-in-0
data-state-open:zoom-in-95
data-state-closed:animate-out
data-state-closed:fade-out-0
data-state-closed:zoom-out-95
data-side-top:slide-in-from-bottom-2
data-side-bottom:slide-in-from-top-2
data-side-left:slide-in-from-right-2
data-side-right:slide-in-from-left-2
"
{...props}
>
{children}
</div>
); }
function DropdownItem({ children, disabled, ...props }) { return ( <div data-highlighted={isHighlighted || undefined} data-disabled={disabled || undefined} className=" relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none
data-highlighted:bg-gray-100
data-disabled:pointer-events-none
data-disabled:opacity-50
"
{...props}
>
{children}
</div>
); }
Group and Peer Patterns Complex State Propagation
Title
Description
Please enter a valid email
Slot Pattern with @apply Reusable Component Slots @layer components { / Base dialog structure / .dialog { @apply fixed inset-0 z-50 flex items-center justify-center; }
.dialog-overlay { @apply fixed inset-0 bg-black/50 backdrop-blur-sm; @apply data-state-open:animate-in data-state-open:fade-in-0; @apply data-state-closed:animate-out data-state-closed:fade-out-0; }
.dialog-content { @apply relative z-50 w-full max-w-lg rounded-xl bg-white p-6 shadow-xl; @apply data-state-open:animate-in data-state-open:fade-in-0 data-state-open:zoom-in-95; @apply data-state-closed:animate-out data-state-closed:fade-out-0 data-state-closed:zoom-out-95; }
.dialog-header { @apply flex flex-col gap-1.5 text-center sm:text-left; }
.dialog-title { @apply text-lg font-semibold leading-none tracking-tight; }
.dialog-description { @apply text-sm text-gray-500; }
.dialog-footer { @apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end; }
.dialog-close { @apply absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100; @apply focus:outline-none focus:ring-2 focus:ring-offset-2; } }
Polymorphic Components "as" Prop Pattern import { forwardRef, ElementType, ComponentPropsWithoutRef } from 'react'; import { cn } from '@/lib/utils';
type PolymorphicRef
type PolymorphicComponentProps
// Text component that can be any element interface TextProps { size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl'; weight?: 'normal' | 'medium' | 'semibold' | 'bold'; color?: 'default' | 'muted' | 'accent'; }
type TextComponent =
export const Text: TextComponent = forwardRef(
const sizes = {
xs: 'text-xs',
sm: 'text-sm',
base: 'text-base',
lg: 'text-lg',
xl: 'text-xl',
'2xl': 'text-2xl',
};
const weights = {
normal: 'font-normal',
medium: 'font-medium',
semibold: 'font-semibold',
bold: 'font-bold',
};
const colors = {
default: 'text-gray-900',
muted: 'text-gray-500',
accent: 'text-brand-500',
};
return (
<Component
ref={ref}
className={cn(sizes[size], weights[weight], colors[color], className)}
{...props}
>
{children}
</Component>
);
} );
Usage
Headless Component Integration Headless UI with Tailwind import { Dialog, Transition } from '@headlessui/react'; import { Fragment } from 'react';
function Modal({ isOpen, onClose, title, children }) {
return (
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="
w-full max-w-md transform overflow-hidden rounded-2xl
bg-white p-6 text-left align-middle shadow-xl transition-all
">
<Dialog.Title className="text-lg font-medium leading-6 text-gray-900">
{title}
</Dialog.Title>
<div className="mt-2">
{children}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
); }
Radix UI with Tailwind import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
function Dropdown() {
return (
<DropdownMenu.Portal>
<DropdownMenu.Content
className="
min-w-[200px] rounded-md bg-white p-1 shadow-lg
border border-gray-200
animate-in fade-in-0 zoom-in-95
data-[side=bottom]:slide-in-from-top-2
data-[side=top]:slide-in-from-bottom-2
"
sideOffset={5}
>
<DropdownMenu.Item className="
relative flex cursor-pointer select-none items-center
rounded-sm px-2 py-2 text-sm outline-none
data-[highlighted]:bg-gray-100
">
Profile
</DropdownMenu.Item>
<DropdownMenu.Separator className="my-1 h-px bg-gray-200" />
<DropdownMenu.Item className="
relative flex cursor-pointer select-none items-center
rounded-sm px-2 py-2 text-sm text-red-600 outline-none
data-[highlighted]:bg-red-50
">
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
); }
Animation Patterns Staggered Animations function StaggeredList({ items }) { return (
-
{items.map((item, index) => (
- ${index * 100}ms }} > {item.content} ))}
Skeleton Loading function Skeleton({ className, ...props }) { return (
); }function CardSkeleton() { return (
Best Practices 1. Use cn() Utility for Class Merging // lib/utils.ts import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
- Extract Common Patterns @layer components { / Consistent focus ring / .focus-ring { @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-2; }
/ Consistent disabled state / .disabled-state { @apply disabled:pointer-events-none disabled:opacity-50; }
/ Truncate text / .truncate-lines-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } }
- Document Component APIs /**
- Button component with multiple variants *
- @example
- *
- @example
-
*/ export function Button({ ... }) { ... }
-
Test Component Variants // Button.test.tsx describe('Button', () => { it('renders all variants correctly', () => { const variants = ['default', 'secondary', 'outline', 'ghost', 'destructive'];
variants.forEach(variant => { render(); // Assert classes are applied correctly }); }); });