Reusable Component Patterns

Tame Long Class Lists the Right Way

Reusable Component Patterns

The cure for class soup is component extraction, not premature @apply. Build one source of truth for buttons and cards.

4 min read Level 2/5 #tailwind#components#patterns
What you'll learn
  • Extract repeated markup into a component or partial
  • Keep one source of truth for a button or card
  • Know the @apply versus component trade-off

When the same twelve-class button appears in forty files, the fix is not to hide the classes — it is to stop duplicating the markup. Component extraction beats premature @apply.

The Class Soup Problem

Copy-pasting a styled button means every visual tweak becomes a forty-file find-and-replace. Tailwind’s answer is the same as any DRY problem: extract a component.

function Button({ children }) {
  return (
    <button className="inline-flex items-center justify-center
      rounded-md bg-blue-600 px-4 py-2 text-sm font-medium
      text-white hover:bg-blue-700">
      {children}
    </button>
  );
}

Now the class list lives in exactly one place. Astro, Vue, and Svelte all support the same idea with their own component or partial syntax.

Variants via Props

Map a variant and size prop to full class strings. Use a lookup object so the strings stay statically analyzable:

const variants = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700',
  ghost: 'bg-transparent text-blue-600 hover:bg-blue-50',
};
const sizes = { sm: 'px-3 py-1 text-sm', md: 'px-4 py-2 text-base' };

function Button({ variant = 'primary', size = 'md', ...props }) {
  return (
    <button
      className={`rounded-md font-medium ${variants[variant]} ${sizes[size]}`}
      {...props}
    />
  );
}

When @apply Is the Right Tool

@apply shines for markup you do not control — third-party HTML, Markdown output, or a CMS body. For your own components, prefer a real component so props, accessibility, and behavior travel with the styles.

/* You cannot add classes to rendered Markdown, so apply here */
.prose a {
  @apply text-blue-600 underline underline-offset-2;
}
Tailwind with React →