Overview

Tailwind v4 replaced the JavaScript plugin API with a CSS-native @plugin directive. First-party plugins (typography, forms, container-queries) load with one line. Custom utilities and variants live in plain CSS. The parent reference is tailwind.

Load first-party plugins with @plugin

Add a first-party plugin the same way you would add any CSS import.

@import "tailwindcss";
 
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/container-queries";

Install the package first: npm install @tailwindcss/typography. No JavaScript config file needed.

  • @tailwindcss/typography adds the prose class for rich text bodies: <article class="prose">.
  • @tailwindcss/forms resets form elements to a consistent baseline without killing customization.
  • @tailwindcss/container-queries adds @lg:, @md:, and other container-size variants. For the underlying CSS mechanism, see css-container-queries.

Customize first-party plugins with @theme overrides

Typography and forms both read from your @theme tokens. Override them by setting the relevant custom properties after the @plugin line.

@plugin "@tailwindcss/typography";
 
@theme {
  --tw-prose-body: var(--color-ink);
  --tw-prose-headings: var(--color-ink);
  --tw-prose-links: var(--color-brand-500);
  --tw-prose-code: var(--color-brand-700);
}

This keeps prose colors in sync with the design system instead of hard-coding a parallel set of values.

Write custom utilities as CSS in @layer utilities

For utilities that no plugin covers, write them directly in the utilities layer. They become first-class Tailwind utilities: they participate in the scanner, the Prettier sort, and the variant system.

@layer utilities {
  .text-balance {
    text-wrap: balance;
  }
  .scrollbar-none {
    scrollbar-width: none;
  }
  .scrollbar-none::-webkit-scrollbar {
    display: none;
  }
}

Use this for single-property utilities that would otherwise be arbitrary values you type repeatedly. Three or more uses of [text-wrap:balance] in source means it belongs here.

Write custom variants with @custom-variant

Define a custom variant for any CSS selector pattern you repeat.

@custom-variant hocus (&:hover, &:focus-visible);
@custom-variant rtl ([dir="rtl"] &);
@custom-variant print (@media print { & });

After this definition, hocus:text-brand applies text-brand on hover or keyboard focus. The rtl: variant flips layout for right-to-left languages. print:hidden hides elements in print media.

Publish reusable plugin packages as CSS files

A v4 plugin is a plain CSS file that follows the @layer and @theme conventions. Publish it to npm; consumers load it with @plugin.

/* my-plugin/index.css */
@layer utilities {
  .truncate-2 {
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
}
/* consumer */
@plugin "my-plugin";

No build step, no JavaScript file, no plugin function to call. The CSS file is the plugin.

Check v4 before reaching for a v3 plugin

Several popular v3 plugins became built-in or first-class in v4:

  • @tailwindcss/line-clamp: the line-clamp-* utilities are built into v4.
  • @tailwindcss/aspect-ratio: aspect-* utilities are built in.
  • @tailwindcss/typography: still separate; see above.
  • Custom theme() calls in v3: replaced by var(--token-name) in v4 since tokens are CSS custom properties.

Checking the v4 changelog before installing a plugin avoids pulling in a package that does nothing.

Avoid composing utilities with @apply inside plugins

@apply inside a plugin adds hidden coupling between the plugin and the host project’s utility set. If the host project renames a token, the plugin silently breaks.

Write plugins using direct CSS declarations and var(--token) references. This makes the dependency on the design system explicit and auditable.