~/
Building Components
Quiz
...

Building Components

intermediate · updated Tue Sep 22 2026Contribute

Reuse markup with components and @apply for shared styles.

Building Components

Long class lists repeat. Tailwind gives you two ways to reuse: extract the component, or extract a class with @apply.

Prefer Components

In React, Vue, Svelte, or Astro, wrap the markup in a component:

export function Button({ children, ...props }) {
  return (
    <button
      className="rounded-lg bg-blue-600 px-4 py-2 font-medium text-white hover:bg-blue-700"
      {...props}
    >
      {children}
    </button>
  );
}

Use it everywhere:

<Button>Save</Button>
<Button type="button">Cancel</Button>

The classes live in one place. This is the idiomatic Tailwind answer.

Variants with cva

For components with multiple looks, class-variance-authority keeps it tidy:

import { cva } from "class-variance-authority";

const button = cva("rounded-lg px-4 py-2 font-medium", {
  variants: {
    intent: {
      primary: "bg-blue-600 text-white hover:bg-blue-700",
      ghost: "text-blue-600 hover:bg-blue-50",
    },
    size: {
      sm: "text-sm",
      lg: "text-lg",
    },
  },
  defaultVariants: { intent: "primary", size: "sm" },
});

button({ intent: "ghost", size: "lg" });

Conditional Classes with clsx

Merge classes based on state:

import clsx from "clsx";

<div className={clsx("p-4 rounded", isActive && "bg-blue-100", disabled && "opacity-50")}>
  ...
</div>

tailwind-merge resolves conflicts (p-2 vs p-4) when combining.

@apply for Shared Styles

When you genuinely need a named class — for prose, third-party markup, or a design token — use @apply:

@import "tailwindcss";

.btn {
  @apply rounded-lg bg-blue-600 px-4 py-2 font-medium text-white;
}
.btn:hover {
  @apply bg-blue-700;
}

Now class="btn" works in plain HTML.

When to Use Which

  • Component — Most cases. Reuse structure and behavior.
  • @apply — Markdown content, generated HTML, or a stable public class.
  • cva/clsx — Components with variants or conditional styling.

Best Practices

  1. Components first@apply is a tool, not the default.
  2. Keep variants declarative — cva over nested ternaries.
  3. Use tailwind-merge — When composing classes from props.
  4. Name by intentButton intent="danger", not Button red.

Common Mistakes

  1. @apply everywhere — You rebuild a CSS framework you left.
  2. Concatenating classes — The scanner misses dynamic strings.
  3. Conflicting classesp-2 p-4; use tailwind-merge.
  4. One giant component — Split variants instead of many booleans.