tailwindcss-advanced-components

tailwindcss-advanced-components

Tailwind CSS advanced component patterns with CVA integration and variant management

7estrellas
1forks
Actualizado 1/17/2026
SKILL.md
readonlyread-only
name
tailwindcss-advanced-components
description

Tailwind CSS advanced component patterns with CVA integration and variant management

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<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

export function Button({
  className,
  variant,
  size,
  fullWidth,
  asChild = false,
  ...props
}: ButtonProps) {
  return (
    <button
      className={cn(buttonVariants({ variant, size, fullWidth, className }))}
      {...props}
    />
  );
}

Usage

<Button>Default</Button>
<Button variant="secondary" size="lg">Large Secondary</Button>
<Button variant="destructive" fullWidth>Delete</Button>
<Button variant="ghost" size="icon"><IconMenu /></Button>

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<HTMLDivElement> {
  variant?: 'default' | 'elevated' | 'outline';
}

const Card = forwardRef<HTMLDivElement, CardProps>(
  ({ className, variant = 'default', children, ...props }, ref) => {
    const variants = {
      default: 'bg-white border border-gray-200',
      elevated: 'bg-white shadow-lg',
      outline: 'bg-transparent border-2 border-gray-300',
    };

    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<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('flex flex-col gap-1.5 p-6 pb-0', className)}
      {...props}
    />
  )
);

const CardTitle = forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
  ({ className, ...props }, ref) => (
    <h3
      ref={ref}
      className={cn('text-xl font-semibold leading-none tracking-tight', className)}
      {...props}
    />
  )
);

const CardDescription = forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
  ({ className, ...props }, ref) => (
    <p
      ref={ref}
      className={cn('text-sm text-gray-500', className)}
      {...props}
    />
  )
);

const CardContent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div ref={ref} className={cn('p-6', className)} {...props} />
  )
);

const CardFooter = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('flex items-center p-6 pt-0', className)}
      {...props}
    />
  )
);

// 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

<Card variant="elevated">
  <CardHeader>
    <CardTitle>Account Settings</CardTitle>
    <CardDescription>Manage your account preferences</CardDescription>
  </CardHeader>
  <CardContent>
    <form>...</form>
  </CardContent>
  <CardFooter className="justify-between">
    <Button variant="ghost">Cancel</Button>
    <Button>Save Changes</Button>
  </CardFooter>
</Card>

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

<!-- Group pattern: Parent hover affects children -->
<div class="group relative overflow-hidden rounded-xl">
  <img
    src="image.jpg"
    class="transition-transform duration-300 group-hover:scale-110"
  />
  <div class="
    absolute inset-0 bg-gradient-to-t from-black/80 to-transparent
    opacity-0 transition-opacity group-hover:opacity-100
  ">
    <div class="
      absolute bottom-0 left-0 right-0 p-6
      translate-y-4 transition-transform group-hover:translate-y-0
    ">
      <h3 class="text-xl font-bold text-white">Title</h3>
      <p class="text-gray-200">Description</p>
    </div>
  </div>
</div>

<!-- Named groups for nested components -->
<div class="group/card">
  <div class="group/header">
    <button class="group-hover/header:text-blue-500">
      Header Action
    </button>
  </div>
  <div class="group-hover/card:bg-gray-50">
    Card content
  </div>
</div>

<!-- Peer pattern: Sibling state affects elements -->
<div class="relative">
  <input
    type="email"
    class="peer w-full border rounded-lg px-4 py-2 placeholder-transparent"
    placeholder="Email"
  />
  <label class="
    absolute left-4 top-2 text-gray-500
    transition-all
    peer-placeholder-shown:top-2 peer-placeholder-shown:text-base
    peer-focus:-top-6 peer-focus:text-sm peer-focus:text-blue-500
    peer-[:not(:placeholder-shown)]:-top-6 peer-[:not(:placeholder-shown)]:text-sm
  ">
    Email address
  </label>
</div>

<!-- Peer for form validation -->
<div>
  <input
    type="email"
    class="peer"
    required
  />
  <p class="hidden text-red-500 peer-invalid:block">
    Please enter a valid email
  </p>
</div>

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<C extends ElementType> = ComponentPropsWithoutRef<C>['ref'];

type PolymorphicComponentProps<C extends ElementType, Props = {}> = Props & {
  as?: C;
  className?: string;
  children?: React.ReactNode;
} & Omit<ComponentPropsWithoutRef<C>, 'as' | 'className' | keyof Props>;

// 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 = <C extends ElementType = 'span'>(
  props: PolymorphicComponentProps<C, TextProps> & { ref?: PolymorphicRef<C> }
) => React.ReactElement | null;

export const Text: TextComponent = forwardRef(
  <C extends ElementType = 'span'>(
    {
      as,
      size = 'base',
      weight = 'normal',
      color = 'default',
      className,
      children,
      ...props
    }: PolymorphicComponentProps<C, TextProps>,
    ref?: PolymorphicRef<C>
  ) => {
    const Component = as || 'span';

    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

<Text>Default span</Text>
<Text as="p" size="lg" color="muted">Large muted paragraph</Text>
<Text as="h1" size="2xl" weight="bold">Bold heading</Text>
<Text as="a" href="/link" color="accent">Accent link</Text>

Headless Component Integration

Headless UI with Tailwind

import { Dialog, Transition } from '@headlessui/react';
import { Fragment } from 'react';

function Modal({ isOpen, onClose, title, children }) {
  return (
    <Transition appear show={isOpen} as={Fragment}>
      <Dialog as="div" className="relative z-50" onClose={onClose}>
        {/* Backdrop */}
        <Transition.Child
          as={Fragment}
          enter="ease-out duration-300"
          enterFrom="opacity-0"
          enterTo="opacity-100"
          leave="ease-in duration-200"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <div className="fixed inset-0 bg-black/50 backdrop-blur-sm" />
        </Transition.Child>

        <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.Root>
      <DropdownMenu.Trigger className="
        inline-flex items-center justify-center rounded-md
        bg-white px-4 py-2 text-sm font-medium
        border border-gray-300 hover:bg-gray-50
        focus:outline-none focus:ring-2 focus:ring-brand-500
      ">
        Options
        <ChevronDownIcon className="ml-2 h-4 w-4" />
      </DropdownMenu.Trigger>

      <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 (
    <ul className="space-y-2">
      {items.map((item, index) => (
        <li
          key={item.id}
          className="animate-in fade-in-0 slide-in-from-left-4"
          style={{ animationDelay: `${index * 100}ms` }}
        >
          {item.content}
        </li>
      ))}
    </ul>
  );
}

Skeleton Loading

function Skeleton({ className, ...props }) {
  return (
    <div
      className={cn(
        'animate-pulse rounded-md bg-gray-200',
        className
      )}
      {...props}
    />
  );
}

function CardSkeleton() {
  return (
    <div className="rounded-xl border border-gray-200 p-6">
      <div className="flex items-center gap-4">
        <Skeleton className="h-12 w-12 rounded-full" />
        <div className="space-y-2">
          <Skeleton className="h-4 w-[200px]" />
          <Skeleton className="h-3 w-[150px]" />
        </div>
      </div>
      <div className="mt-4 space-y-2">
        <Skeleton className="h-3 w-full" />
        <Skeleton className="h-3 w-full" />
        <Skeleton className="h-3 w-3/4" />
      </div>
    </div>
  );
}

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));
}

2. 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;
  }
}

3. Document Component APIs

/**
 * Button component with multiple variants
 *
 * @example
 * <Button variant="primary" size="lg">Click me</Button>
 *
 * @example
 * <Button variant="ghost" size="icon">
 *   <IconMenu />
 * </Button>
 */
export function Button({ ... }) { ... }

4. Test Component Variants

// Button.test.tsx
describe('Button', () => {
  it('renders all variants correctly', () => {
    const variants = ['default', 'secondary', 'outline', 'ghost', 'destructive'];

    variants.forEach(variant => {
      render(<Button variant={variant}>Test</Button>);
      // Assert classes are applied correctly
    });
  });
});

You Might Also Like

Related Skills

cache-components

cache-components

137Kdev-frontend

Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.

vercel avatarvercel
Obtener
component-refactoring

component-refactoring

128Kdev-frontend

Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component --json` shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction, or when `pnpm analyze-component` warns to refactor before testing; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.

langgenius avatarlanggenius
Obtener
web-artifacts-builder

web-artifacts-builder

47Kdev-frontend

Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.

anthropics avataranthropics
Obtener
frontend-design

frontend-design

47Kdev-frontend

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.

anthropics avataranthropics
Obtener
react-modernization

react-modernization

28Kdev-frontend

Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to latest React versions.

wshobson avatarwshobson
Obtener
tailwind-design-system

tailwind-design-system

28Kdev-frontend

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.

wshobson avatarwshobson
Obtener