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
| 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.
<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>
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.
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'))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.