Tailwind CSS Advanced: Systems, Plugins and Production
Tailwind in Frameworks
In this lesson you will
- Choose the right Tailwind integration for a build tool
- Compose and merge class names safely in components
- Build variant-based components with cva
Introduction
Tailwind does not care which framework renders your HTML. It only needs to see class names in your files and to run as part of your build. This lesson shows how it fits into common set-ups, and how to build robust components on top of it. The packages named here were installed and exercised with Tailwind 4.3.3, clsx 2.1.1, tailwind-merge 3.7.0 and class-variance-authority 0.7.1.
Three ways to plug in
Tailwind v4 ships separate packages, and you pick the one that matches your tooling:
| Package | Use it when |
|---|---|
@tailwindcss/vite | your project uses Vite (React, Vue, Svelte, SolidJS, Astro and others) |
@tailwindcss/postcss | your framework already uses PostCSS, such as Next.js |
@tailwindcss/cli | you have plain HTML or want a standalone build |
The CSS stays the same in all cases:
CSS
@import "tailwindcss";Vite
Terminal
npm install tailwindcss @tailwindcss/viteNext.js and other PostCSS tools
This site is a Next.js app, and its set-up is the PostCSS route. The whole PostCSS config is:
and the global stylesheet begins with @import "tailwindcss";, followed by an @theme inline block for the brand colours. In v4 you can remove postcss-import and autoprefixer, because Tailwind handles imports and vendor prefixes itself.
Always check the framework-specific page in the official installation docs, since integrations are updated often.
Quick quiz
Which package do you use for Tailwind v4 in a Next.js project that uses PostCSS?
Styling in component frameworks
Because Tailwind scans files as text, class names work in JSX, Vue and Svelte templates, Astro files, Markdown, and even JavaScript strings. The golden rule from earlier still applies: use complete class names.
React (TSX)
type Props = { tone: "info" | "error"; children: React.ReactNode };
const tones = {
info: "bg-blue-50 text-blue-900",
error: "bg-red-50 text-red-900",
};
export function Alert({ tone, children }: Props) {
return <div className={`rounded-lg p-4 ${tones[tone]}`}>{children}</div>;
}CSS in Vue, Svelte and CSS modules
Style blocks that are bundled separately cannot see your theme. If you use @apply there, import your main file for reference only:
CSS
@reference "../../app.css";Or use theme variables directly, such as var(--color-brand). Sass, Less and Stylus are not supported by Tailwind v4.
Composing class names with clsx
Conditional classes by hand are messy. clsx combines values and drops falsy ones. When I ran it:
The conflict problem and tailwind-merge
A reusable component takes a className so callers can adjust it. But if the component has p-4 and the caller passes p-8, both are applied, and the winner depends on the order of rules in the generated CSS, not on which you wrote last. tailwind-merge solves this by understanding Tailwind classes and keeping the last conflicting one. I ran these:
| Input | Result |
|---|---|
twMerge("p-4 p-8") | p-8 |
twMerge("px-4 py-2 p-8") | p-8 (the later p-8 replaces both) |
twMerge("p-8 px-4") | p-8 px-4 (kept, since px-4 is more specific and later) |
twMerge("bg-blue-600 hover:bg-blue-700", "bg-red-600") | hover:bg-blue-700 bg-red-600 (variants are separate) |
twMerge("rounded-lg", "rounded-full") | rounded-full |
A tiny helper combines both libraries and is used in many projects:
If you have a custom theme, for example a custom font size or colour name, tell tailwind-merge about it through its configuration, or it may not recognise those classes. Check its documentation for your version.
Variants with class-variance-authority
Buttons have intents and sizes. cva maps options to class strings and handles defaults:
Results from running it:
button()
rounded-lg px-4 py-2 font-semibold bg-blue-600 text-white hover:bg-blue-700 text-sm
button({ intent: "danger", size: "lg" })
rounded-lg px-4 py-2 font-semibold bg-red-600 text-white hover:bg-red-700 text-lg px-6Notice the large size contains both px-4 and px-6. Passing the result through twMerge removed the duplicate and kept px-6. Complete class names appear as literal strings, so Tailwind detects them all.
Other approaches
Some teams prefer a component library built on Tailwind, or a tool such as Tailwind Variants. The principle is the same: variants map to complete strings, and conflicts are resolved in one place.
Try it yourself
Create a cn helper and a Button component with cva that accepts className. Pass className="px-10" and confirm the merged class list has no duplicate padding. Add a unit test for the merged output.
Summary
- Pick
@tailwindcss/vite,@tailwindcss/postcssor@tailwindcss/clifor your build tool, and use the same@import "tailwindcss"CSS. - Combine classes with
clsx, resolve conflicts withtailwind-merge, and wrap them in acnhelper. - Use
cvato map variants to complete class strings, and@referencewhen separately bundled styles need@apply.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading