Setting up Tailwind Vue 3 comes down to installing Tailwind as a dev dependency, wiring it into your build tool, and pointing its content scanner at your .vue files so unused classes get stripped from the production bundle. The mechanics are quick. The parts people skip are the ones that bite later: pinning your toolchain, understanding what runs during your build, and keeping the generated CSS small.
This guide covers a clean Tailwind CSS Vue 3 install and the security hygiene that should ride along with it.
Installing Tailwind in a Vue 3 project
Most Vue 3 apps are scaffolded with Vite. Starting from a Vite + Vue project, add Tailwind and its peer tooling as development dependencies:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
That second command generates tailwind.config.js and postcss.config.js. The -p flag adds the PostCSS config so Vite picks Tailwind up automatically. Note the -D: Tailwind is a build-time tool. It has no business in your production dependencies, and keeping it in devDependencies shrinks what an attacker or an audit has to reason about at runtime.
If you are on a newer Tailwind release, always check the current install steps in the official docs rather than copying a two-year-old blog, because the PostCSS plugin packaging has changed between major versions.
Configuring content paths
The single most important line in a tailwind vue setup is the content array. Tailwind scans these files, sees which classes you actually use, and emits only those. Get the globs wrong and you either ship every utility class (a huge bundle) or lose styles in production that worked in dev.
// tailwind.config.js
export default {
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
Then create your entry stylesheet and import it once in main.js:
/* src/style.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
For a vue tailwind project this is the whole runtime surface. There is no client-side Tailwind engine shipping to browsers; the utility CSS is generated at build time. That is a genuine security win over CSS-in-JS approaches that ship a runtime.
Using utilities in single-file components
With the pipeline in place, classes work directly in your templates. A basic Vue 3 tailwind component:
<template>
<button class="rounded-lg bg-indigo-600 px-4 py-2 font-medium text-white hover:bg-indigo-700">
Save changes
</button>
</template>
Dynamic classes need care. Tailwind's scanner reads source as plain text, so it cannot see classes you assemble from string fragments at runtime. Write complete class names and toggle them with bindings instead:
<template>
<span :class="active ? 'text-green-600' : 'text-gray-400'">Status</span>
</template>
Building class names like `text-${color}-600` will not be detected and the styles will be missing in production. This is the number one "it works locally but not when deployed" bug in tailwind css vue projects.
The supply-chain angle
A Tailwind install pulls in PostCSS, Autoprefixer, and a tree of transitive build dependencies. All of it runs on your machine and in CI with full filesystem access during the build. That is the real attack surface of a frontend toolchain, and it is why frontend build dependencies show up in supply-chain incidents more than the framework code itself.
A few habits keep it tight:
- Commit your lockfile and treat unexpected lockfile changes in a PR as a review blocker.
- Pin versions and let a bot propose upgrades you can review, rather than floating ranges that resolve to whatever is newest at install time.
- Run
npm auditin CI and fail the build on high-severity findings in the build path. For deeper transitive visibility, an SCA tool such as Safeguard can surface issues several layers down thatnpm auditcollapses. Our SCA product page explains what that looks like.
The point is not paranoia about Tailwind specifically, which is a mature and well-maintained project. The point is that your CSS framework arrives with dozens of dependencies, and each is code you are choosing to execute.
Keeping the production bundle honest
Once content scanning is set up correctly, production CSS is typically small because unused utilities are dropped. Verify it. Build the app and inspect the emitted CSS size:
npm run build
If your CSS file is hundreds of kilobytes, your content globs are almost certainly not matching your source, and Tailwind is emitting everything as a fallback. Fix the globs before you fix anything else. A correctly configured vue 3 tailwind build usually lands in the low tens of kilobytes before gzip for a typical app.
FAQ
Do I need a Vue-specific Tailwind plugin?
No. Tailwind CSS Vue 3 works through PostCSS and Vite with no Vue-specific plugin. You just make sure your content paths include .vue files so the scanner can see the classes in your single-file components.
Why are my Tailwind classes missing in production but fine in dev?
Almost always because the classes are built dynamically from string fragments, so Tailwind's content scanner cannot detect them, or because your content globs do not match where your components live. Write full class names and double-check the globs.
Should Tailwind be a dependency or a devDependency?
A devDependency. Tailwind generates CSS at build time and ships nothing to the browser at runtime, so it belongs in devDependencies. Keeping it there also keeps your production dependency tree, and its audit surface, smaller.
Is Tailwind safe to use in a security-sensitive app?
Tailwind itself generates static CSS with no client runtime, which is a low-risk profile. The risk lives in its build-time dependency tree, shared with the rest of your toolchain. Pin versions, commit lockfiles, and scan the build dependencies like you would any other code.