Plugins and Ecosystem

Extending Tailwind: Plugins and the Ecosystem

Tailwind 5,202 stops at utilities. Above them sit three layers: plugins that add utilities, component libraries that supply buttons and dialogs, and JavaScript helpers that assemble class strings.

Official plugins

In v4 you add your own utilities in CSS with @utility and @variant (Adding Custom Styles), so plugins are needed less often than in v3. JavaScript plugins still load with @plugin (a package name or file path), and @config loads a v3 tailwind.config.js during a migration. Tailwind Labs maintains two MIT-licensed favorites, each installed with npm 2,036 install -D and loaded with a line such as @plugin "@tailwindcss/forms"; after @import "tailwindcss";. @tailwindcss/typography (0.5.20) styles rendered Markdown with one prose class (Typography). @tailwindcss/forms (0.5.11) resets inputs, selects and checkboxes so that utilities such as rounded-lg border-slate-300 apply cleanly; pass strategy: "class" to get opt-in form-input classes instead of global styles. The browser build cannot load npm plugins, so use them in a real build (Installation Options).

Component libraries
Component libraries built on Tailwind CSS
Library What you get Licence Repository
daisyUI 5.7 193,893 CSS component classes (btn, card) and themes MIT github.com/saadeghi/daisyui
Flowbite 4.0 192,814 Utility markup plus JS driven by data attributes MIT github.com/themesberg/flowbite
Preline 5.0 870,069 Utility markup plus 27 headless JS plugins MIT + Fair Use github.com/htmlstreamofficial/preline
Headless UI 2.2 663,593 Unstyled, accessible React 7,897 and Vue 5,482 components MIT github.com/tailwindlabs/headlessui
shadcn/ui 77,293 A CLI that copies component source into your app MIT github.com/shadcn-ui/ui

daisyUI is closest to Bootstrap 2,007 : install it with npm i -D daisyui and @plugin "daisyui";, write btn btn-primary, and override any detail with utilities. Flowbite and Preline publish copy-and-paste markup made of plain utilities, with a script that wires up modals and dropdowns; Preline's Fair Use License forbids building a competing UI kit with it. Headless UI, from Tailwind Labs, supplies behavior only: focus management, keyboard handling and ARIA attributes for menus, dialogs and comboboxes, which you style. shadcn/ui runs npx shadcn@latest add button to write a React component, built on Radix 205,870 or Base UI primitives and styled with Tailwind, into your repository, so there is no dependency to upgrade and nothing you cannot edit.

daisyUI components from the CDNHTMLLive
<link href="https://cdn.jsdelivr.net/npm/daisyui@5.7.38" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
<div data-theme="light" class="flex flex-wrap gap-4 bg-base-200 p-4">
  <div class="card w-72 bg-base-100 shadow-md">
    <div class="card-body">
      <h2 class="card-title">Pro plan <span class="badge badge-secondary">Popular</span></h2>
      <p>Unlimited projects and automations.</p>
      <div class="card-actions justify-end">
        <button class="btn btn-primary">Start trial</button></div>
    </div>
  </div>
  <div class="flex flex-col gap-3">
    <div role="alert" class="alert alert-success">Invoice paid.</div>
    <label class="flex items-center gap-2">
      <input type="checkbox" class="toggle toggle-primary" checked> Email alerts</label>
    <progress class="progress progress-accent w-48" value="70" max="100"></progress>
  </div>
</div>
Browser output of Listing 5.31
Browser output of 31
Building class strings: clsx, tailwind-merge and cva

Components need classes that change with props, and callers want to override them. shadcn/ui combines the first two of these tiny libraries into a cn() helper.

Conditional classes, conflict resolution and variantsJavaScript
import { clsx } from 'clsx'                          // npm install clsx tailwind-merge
import { twMerge } from 'tailwind-merge'
import { cva } from 'class-variance-authority'       // npm install class-variance-authority
const cn = (...inputs) => twMerge(clsx(inputs))
const button = cva('inline-flex items-center rounded-md font-medium', {
  variants: {
    intent: { primary: 'bg-sky-600 text-white', outline: 'border border-sky-600 text-sky-700' },
    size: { sm: 'px-2 py-1 text-sm', md: 'px-4 py-2' },
  },
  defaultVariants: { intent: 'primary', size: 'md' },
})
const isLoading = true
console.log(clsx('btn', { 'opacity-50': isLoading, hidden: false }, ['px-2', null]))
console.log(twMerge('px-2 py-1 bg-red-500', 'p-3 bg-sky-600'))
console.log(button({ intent: 'outline', size: 'sm' }))
console.log(cn(button(), isLoading && 'opacity-50', 'px-8 bg-emerald-600'))
Output
btn opacity-50 px-2
p-3 bg-sky-600
inline-flex items-center rounded-md font-medium border border-sky-600 text-sky-700 px-2 py-1
  text-sm
inline-flex items-center rounded-md font-medium text-white py-2 opacity-50 px-8 bg-emerald-600

clsx 9,844 (2.1.1, MIT, github.com/lukeed/clsx (https://github.com/lukeed/clsx 9,844 )) joins strings, arrays and object keys whose values are truthy. cva (class-variance-authority 0.7.1, Apache-2.0, github.com/joe-bell/cva (https://github.com/joe-bell/cva 6,902 )) turns a variant table into a function. Plain concatenation fails at overrides: bg-sky-600 bg-emerald-600 puts both rules in the stylesheet, where Tailwind emits .bg-emerald-600 first, so sky blue wins although you wrote emerald last. tailwind-merge 5,692 (3.7.0, MIT, github.com/dcastil/tailwind-merge (https://github.com/dcastil/tailwind-merge 5,692 )) fixes this, as the last line shows: px-8 removed px-4 but kept py-2.