Fix slow Tailwind v3 in Vite: remove catch-all safelists
Separate oversized generated CSS from plugin cost, with a single-change compiler reproduction and an Autoprefixer negative control.
TL;DR#
If a Tailwind v3 project has a catch-all safelist, remove that pattern before changing unrelated Vite settings. It requests utilities that your source files never use. Keep genuinely external class choices as a short explicit list or a bounded pattern, then compare generated CSS and the actual update delay. Retain Autoprefixer for the first comparison so the experiment changes only one input.
The Vite and React slow-compilation report describes slow updates and an unresponsive browser Styles pane. Its Tailwind configuration includes:
module.exports = {
content: ['./app/**/*.{html,js,jsx,ts,tsx}'],
safelist: [{ pattern: /./ }],
darkMode: 'class',
theme: { extend: {} },
plugins: [require('@tailwindcss/forms')],
}The answers suggest removing Autoprefixer or narrowing the content paths. Neither answer proves which operation consumed the original project's time. The broad safelist is a separate, directly testable source of excess generated CSS. This article reproduces that generation behaviour; it does not claim to have run the author's application or measured its browser delay.
Why it happens#
The Tailwind v3 safelist documentation describes safelisting as a last resort for content unavailable to the scanner. Source scanning and safelisting supply candidates through different routes. A small content list does not place a ceiling on an independent safelist.
The tested compiler implementation walks its configured utility candidates, tests each against a safelist pattern, and adds matching names for generation. A pattern matching any character accepts those non-empty names without asking whether the application used them. The effect is not restricted to the folder named in content.
That does not mean every imaginable CSS value exists in the output. The candidate set comes from configured utilities and theme values. Arbitrary values are not an infinite catalogue for the regex to enumerate. Similarly, state and responsive variants are requested separately; a broad base pattern is not a precise description of the combinations your interface supports.
Fix#
1. Confirm the Tailwind generation before editing its configuration#
Run this in the application workspace:
npm ls tailwindcss autoprefixerThis diagnosis concerns the JavaScript safelist supported by Tailwind v3. If the project now runs v4, use the v4 safelist migration diagnosis instead. Loading a legacy config and supporting its safelist are separate questions. Do not attempt to repair missing v4 classes by increasing an old v3 regex.
Find the config the application actually uses. Check whether the catch-all pattern is present directly or supplied through a shared configuration. Preserve the existing theme, plugins and PostCSS setup during the comparison: replacing the whole config could remove the excess output while also breaking the application's design system.
2. Remove the catch-all request, keeping the rest of the pipeline#
For the minimal configuration above, the corresponding change is:
module.exports = {
content: ['./app/**/*.{html,js,jsx,ts,tsx}'],
safelist: [],
darkMode: 'class',
theme: { extend: {} },
plugins: [require('@tailwindcss/forms')],
}Use the source paths matching your own application; this example follows the question's folder. A Vite project keeping components elsewhere needs those files scanned as well. The goal is not to exclude real component files to obtain a smaller number, but to stop requesting utilities independently of any real use.
Keep the v3 PostCSS integration while testing this change:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}Tailwind's official v3 Vite installation includes both plugins. Removing one can change the pipeline's cost, but it does not remove the broad request made inside Tailwind. Establish whether the stylesheet was oversized before deciding that a dependency must go.
3. Restore only classes that genuinely arrive from outside the source#
An administrative interface might accept a bounded choice from stored content rather than contain the complete token in a scanned component. For that situation, an explicit class list is easier to inspect than a catch-all. An alternative minimal configuration is:
module.exports = {
content: ['./app/**/*.{html,js,jsx,ts,tsx}'],
safelist: ['bg-green-500'],
darkMode: 'class',
theme: { extend: {} },
plugins: [require('@tailwindcss/forms')],
}Use the actual supported external choices. If component code already selects among complete literal strings, let content scanning find those strings instead. If it assembles a class from fragments, replace that construction with a mapping of complete tokens rather than generating a whole design system to hide the scanning gap.
When a regex is appropriate, constrain its utility family and permitted values. The v3 documentation also explains that patterns target base utilities and that requested variants belong in the variants option. A pattern matching everything plus an expanding variant list makes the requested output broader again; it does not make the scanner understand runtime JavaScript.
Verify the fix#
The reproduction ran on 3 November 2026 in an isolated Node 24.22.2 container with Tailwind CSS 3.4.19, PostCSS 8.4.49, Autoprefixer 10.4.21 and the forms plugin. To keep it small, its configuration used a fixed palette and a fixed subset of core utilities throughout. Its source contained only flex, padding and text-colour utilities. It tested compilation and selector presence, without running a Vite server or browser rendering benchmark.
Each scenario ran in a fresh process. The results were:
| Safelist and pipeline | CSS bytes | Rule count |
|---|---|---|
| Catch-all, Autoprefixer retained | 22013 | 468 |
| Empty safelist, Autoprefixer retained | 92 | 3 |
| Explicit external background class, Autoprefixer retained | 140 | 4 |
| Catch-all, only Autoprefixer removed | 22013 | 468 |
Those figures belong to the deliberately small fixture, not a forecast for your application. The important controls are what changed and what remained: removing only the safelist retained the literal padding utility and removed an unrelated red background utility. The bounded list restored the intended green background without restoring the red one. Removing only Autoprefixer left the broad output unchanged in this fixture.
In your project, compare the emitted or served stylesheet before and after the safelist edit. Inspect representative selectors your components need, including externally supplied choices. Then measure the same development update again. A smaller stylesheet demonstrates less generated output; it does not by itself prove which part of the original HMR path was slow.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Distinguish a smaller stylesheet from a solved browser stall#
If the stylesheet is now compact but updates remain slow, investigate the remaining stage. Vite's performance guide documents plugin-transform diagnostics and CPU profiling. Use that evidence to identify expensive plugin work instead of attributing every delay to the first plugin listed in a config. Repeat comparable operations when measuring; a first startup and an already warm update answer different questions.
A different symptom needs its own investigation. If built selectors exist but production looks unstyled, check whether Vite's built CSS is actually being loaded. If Chrome on Linux stalls with many module requests pending while another browser loads the app, the Vite browser file-descriptor case concerns request handling rather than a safelist.
Keep the supported class vocabulary explicit and check both output coverage and the measured delay. That preserves the utilities the application actually needs while giving the next performance investigation a smaller, understood stylesheet to work with.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix: Tailwind PostCSS Plugin Has Moved (Vite + React)
The exact steps to resolve the Tailwind v4 PostCSS plugin relocation error in a Vite + React + TypeScript build.
Fix shadcn "No Tailwind CSS configuration found" in Vite
What shadcn init actually looks for before it says Tailwind is missing, reproduced on Tailwind 3 and 4 with the one line that fixes each.
Fix Tailwind v4 IntelliSense not working in VS Code
Find which layer has stopped working: Tailwind project discovery, document mapping, or automatic suggestions inside JSX strings.


