Skip to content

Tailwind CSS Class Obfuscation - Compatibility Matrix โ€‹

Overview โ€‹

This document provides a comprehensive compatibility matrix for Tailwind CSS class obfuscation across different project types, frameworks, and class patterns.

Project Type Support โ€‹

This summary indicates whether a project type is conceptually supported by the package's extractors and transformers. For the actual list of versions exercised in CI (the source of truth), jump to tailwindcss-obfuscator (our package) below.

Project TypeTailwind v3Tailwind v4Extraction Method
HTML Staticโœ… Fullโœ… FullBuilt-in extractors
React / Next.jsโœ… Fullโœ… FullBuilt-in JSX extractors
Vue 3โœ… Fullโœ… FullBuilt-in :class binding support
Svelte 4 + 5 / SvelteKitโœ… Fullโœ… FullBuilt-in class: directive support
Astroโœ… Fullโœ… FullBuilt-in class:list support
Qwikโœ… Fullโœ… FullBuilt-in class$ support
Solid.jsโœ… Fullโœ… FullBuilt-in JSX extractors
React Router (ex-Remix)โœ… Fullโœ… FullBuilt-in JSX extractors
TanStack Start / Routerโœ… Fullโœ… FullBuilt-in JSX extractors

Class Pattern Support โ€‹

HTML Patterns โ€‹

PatternExampleExtractionObfuscation
Double quotesclass="bg-blue-500"โœ…โœ…
Single quotesclass='bg-blue-500'โœ…โœ…
Multi-lineclass="bg-blue-500\n text-white"โœ…โœ…
No quotesclass=bg-blue-500โœ… (since v2.0.1)โœ… (since v2.0.1)

JSX/React Patterns โ€‹

PatternExampleExtractionObfuscation
String attributeclassName="bg-blue-500"โœ…โœ…
Single quotesclassName='bg-blue-500'โœ…โœ…
Braces with stringclassName={"bg-blue-500"}โœ…โš ๏ธ
Template literal (static)className={`bg-blue-500`}โœ…โš ๏ธ
Template literal (dynamic)className={`bg-${color}-500`}โŒโŒ
TernaryclassName={active ? "bg-blue" : "bg-gray"}โœ…*โš ๏ธ
VariableclassName={styles}โŒโŒ

*Ternary works if both options are complete static strings

Why dynamic patterns (bg-${color}-500, className={styles}) cannot be extracted

The obfuscator extracts class names by statically analysing source code at build time. Anything resolved at runtime (template-literal interpolation, prop-bound variables) is invisible to it. Full explanation + workarounds in Known Limitations ยง Dynamic template literals.

CSS Patterns โ€‹

PatternExampleExtractionObfuscation
@apply@apply bg-blue-500 p-4;โœ…โœ…
@layer components@layer components { .btn { @apply ... } }โœ…โœ…
@theme (v4)@theme { --color-primary: #3b82f6; }N/AN/A

Tailwind Feature Support โ€‹

Modifiers and Variants โ€‹

FeatureExamplev3v4
Responsivesm:bg-blue-500โœ…โœ…
Dark modedark:bg-gray-800โœ…โœ…
Hover/Focushover:bg-blue-700โœ…โœ…
Activeactive:bg-blue-800โœ…โœ…
Group hovergroup-hover:opacity-100โœ…โœ…
Peer statespeer-checked:blockโœ…โœ…
First/Lastfirst:rounded-t-lgโœ…โœ…
Odd/Evenodd:bg-gray-50โœ…โœ…
Before/Afterbefore:content-['']โœ…โœ…
Important!bg-green-500โœ…โœ…
Negative-ml-4โœ…โœ…

Tailwind v4 Specific Features โ€‹

FeatureExampleExtractionObfuscation
Container queries@containerโœ…โœ…
Named containers@container/cardโœ…โœ…
Container size variants@lg:blockโœ…โœ…
Named container + size@lg/card:text-2xlโœ…โœ…
Arbitrary container@[500px]:flexโœ…โœ…
Container min/max@min-[400px]:gridโœ…โœ…
Extended container sizes@3xl:text-xl to @7xl:p-12โœ…โœ…
Data attributesdata-[state=open]:bg-greenโœ…โœ…
ARIA statesaria-disabled:opacity-50โœ…โœ…
ARIA arbitraryaria-[current=page]:font-boldโœ…โœ…
Supports queriessupports-[display:grid]:gridโœ…โœ…
Has varianthas-[:checked]:bg-blue-500โœ…โœ…
Not variantnot-first:mt-4โœ…โœ…
Min/Max breakpointsmin-[320px]:flexโœ…โœ…
CSS variable shorthandbg-(--my-color)โœ…โœ…
CSS var with type hintbg-(color:--my-bg)โœ…โœ…
CSS var with opacitybg-(--primary)/50โœ…โœ…
Group-data variantsgroup-data-[state=open]:blockโœ…โœ…
Group-aria variantsgroup-aria-expanded:rotate-180โœ…โœ…
Peer-data variantspeer-data-[checked]:bg-blueโœ…โœ…
Named group variantsgroup/sidebar:hover:bg-grayโœ…โœ…
Named peer variantspeer/input:focus:ring-2โœ…โœ…
Wildcard selector*:flexโœ…โœ…
Deep wildcard selector**:text-smโœ…โœ…
In variantsin-hover:bg-blue-500โœ…โœ…
In with data attrin-data-[state=open]:blockโœ…โœ…
In with arbitraryin-[.sidebar]:bg-gray-100โœ…โœ…
nth variantsnth-2:bg-gray-100โœ…โœ…
nth arbitrarynth-[2n+1]:bg-gray-50โœ…โœ…
nth-last variantsnth-last-2:mb-0โœ…โœ…
nth-of-type variantsnth-of-type-2:text-lgโœ…โœ…
Starting stylestarting:opacity-0โœ…โœ…
Forced colorsforced-colors:outlineโœ…โœ…
Not forced colorsnot-forced-colors:shadow-lgโœ…โœ…
Trailing importantflex!โœ…โœ…
Arbitrary variants[&_p]:text-blue-500โœ…โœ…
Arbitrary media[@media(min-width:640px)]:flexโœ…โœ…
Arbitrary supports[@supports(display:grid)]:gridโœ…โœ…
Extended breakpoints3xl:flex to 7xl:hiddenโœ…โœ…
User interaction statesuser-valid:border-green-500โœ…โœ…
Inert stateinert:opacity-50โœ…โœ…
Pointer device queriespointer-fine:cursor-pointerโœ…โœ…
Optional form stateoptional:border-gray-300โœ…โœ…
Small container sizes@3xs:flex, @2xs:gridโœ…โœ…

Arbitrary Values โ€‹

FeatureExampleExtractionObfuscation
Arbitrary colorbg-[#1da1f2]โœ…โœ…
Arbitrary spacingp-[13px]โœ…โœ…
Arbitrary property[color:red]โœ…โœ…
CSS functionsbg-[url('/img.png')]โœ…โœ…
calc()w-[calc(100%-20px)]โœ…โœ…
CSS variablesbg-[var(--my-color)]โœ…โœ…

Opacity Modifiers โ€‹

FeatureExamplev3v4
Standard opacitybg-blue-500/50โœ…โœ…
Arbitrary opacitybg-blue-500/[.25]โœ…โš ๏ธ

Gradients โ€‹

FeatureExamplev3v4
from-*from-blue-500โœ…โœ…
via-*via-pink-500โœ…โœ…
to-*to-purple-500โœ…โœ…
Percentage stopsfrom-10%โœ…โœ…

Utility Library Support โ€‹

LibraryExampleExtractionObfuscation
clsxclsx('bg-blue', {'text-white': true})โœ…โœ…
classnamesclassnames('a', 'b')โœ…โœ…
tailwind-mergetwMerge('p-4', 'p-2')โœ…โœ…
cn (shadcn/ui)cn('bg-blue', className)โœ…โœ…
CVAcva('base', { variants })โœ…โœ…
tailwind-variantstv({ base: 'px-4' })โœ…โœ…

Note: All static string arguments in these libraries are fully extractable, including:

  • Base classes: cva("flex items-center")
  • Variant classes: variants: { size: { sm: "text-sm" } }
  • Compound variants: compoundVariants: [{ class: "font-bold" }]
  • Slots (tailwind-variants): slots: { base: "rounded-lg" }

Dynamic class construction (e.g., bg-${color}-500) is not supported.

DOM Manipulation โ€‹

PatternExampleExtractionObfuscation
classList.addel.classList.add('bg-blue-500')โš ๏ธโš ๏ธ
classList.toggleel.classList.toggle('active')โš ๏ธโš ๏ธ
className =el.className = 'bg-blue-500'โš ๏ธโš ๏ธ
setAttributeel.setAttribute('class', '...')โš ๏ธโš ๏ธ

Legend โ€‹

SymbolMeaning
โœ…Fully supported
โš ๏ธPartial support / requires care
โŒNot supported
N/ANot applicable

Recommendations โ€‹

For Maximum Compatibility โ€‹

  1. Use complete static class strings - Avoid dynamic class construction
  2. Prefer ternary operators - isActive ? "bg-blue" : "bg-gray" over template interpolation
  3. Use object mapping - Map state to complete class strings
  4. Avoid utility libraries with dynamic logic - Or ensure all classes are also used statically somewhere

Class Pattern Examples โ€‹

tsx
// โœ… GOOD - All classes are static and extractable
const variants = {
  primary: "bg-blue-500 text-white hover:bg-blue-600",
  secondary: "bg-gray-500 text-white hover:bg-gray-600",
};

<button className={variants[variant]}>Click</button>

// โŒ BAD - Dynamic class construction
<button className={`bg-${color}-500 hover:bg-${color}-600`}>Click</button>

Package Compatibility โ€‹

tailwindcss-patch + unplugin-tailwindcss-mangle โ€‹

ConfigurationTailwindStatusNotes
HTML Staticv3โœ… WorksUse tailwindcss-patch@^3.0.1
Vite Reactv3โœ… WorksUse tailwindcss-patch@^3.0.1
Next.jsv3โœ… WorksUse webpack plugin config
HTML Staticv4โŒ FailsNo tailwind.config.js
Vite Reactv4โŒ FailsNo tailwind.config.js
Next.jsv4โŒ FailsNo tailwind.config.js

Critical: tailwindcss-patch requires tailwind.config.js which doesn't exist in Tailwind v4's CSS-first architecture.

See docs/lab_tailwindcss_patch_analysis.md for detailed analysis.

tailwindcss-obfuscator (our package) โ€‹

This is the live, source-of-truth matrix for the package. Every row corresponds to a real, executed test app under apps/ โ€” built and verified at every release via scripts/verify-obfuscation.mjs (which the CI runs).

Framework / Project TypeTested VersionTailwindBundlerTest App
Next.js (App Router)16.2.4v4Webpack / Turbopackapps/test-nextjs
Next.js (legacy v3)16.2.4v3Webpackapps/tailwind_v3_react_nextjs
Next.js + shadcn/ui16.2.4v4Webpackapps/test-shadcn-ui
Nuxt4.4.2v4Viteapps/test-nuxt
SvelteKit + Svelte 52.58.0 (Svelte 5.55.5)v4Viteapps/test-sveltekit
Astro6.1.9v4Viteapps/test-astro
Solid.js1.9.12v4Viteapps/test-solidjs
Qwik1.19.2v4Viteapps/test-qwik
React Router (ex-Remix)7.14.2v4Viteapps/test-react-router
TanStack Start1.168.25v4Viteapps/test-tanstack-start
React + Vite19.1.0v4Vite 8.xapps/test-vite-react
React + Vite (TW v3)19.0.0v3Vite 8.xapps/test-tailwind-v3
React + Vite (TW v4)19.0.0v4Vite 8.xapps/test-tailwind-v4
Vue 3 + Vite3.5.14v4Vite 8.xapps/test-vite-vue
Static HTML (TW v3)n/av3Viteapps/tailwind_v3_html_static
Static HTML (TW v4)n/av4Viteapps/tailwind_v4_html_static
Static HTML + esbuildn/av4esbuild โ‰ฅ 0.28apps/test-static-html
Rollup standalonen/av4Rollup ^4.60apps/test-rollup-standalone
Webpack standalonen/av4Webpack ^5.106apps/test-webpack-standalone
Rspack standalonen/av4@rspack/core โ‰ฅ1.7.11no test app โ€” adapter exercised by tarball-smoke
Farm standalonen/av4@farmfe/core โ‰ฅ1.7.11no test app โ€” adapter exercised by tarball-smoke

How to read this table: the "Tested Version" column shows the exact upper-bound version exercised on the CI. Lower versions of the same major typically work but are not in the test matrix and may regress without us catching it. If you need a guarantee, open an issue and we'll add a test app for your specific version.

Specialised regression apps โ€‹

These apps exist alongside the framework ร— bundler matrix above to lock in specific package behaviours rather than ยซ does framework X work end-to-end ยป. They run on every CI build via scripts/verify-obfuscation.mjs.

AppValidates
apps/test-safelistThe exclude option (a.k.a. safelist) โ€” listed classes stay unchanged
apps/test-tailwind-variantsThe tv() extractor โ€” base / variants / compoundVariants all rewrite
apps/tailwind_v4_react_nextjsNext.js + Tailwind v4 + Webpack adapter end-to-end (alternate to test-nextjs for legacy paths)

Version-range claims vs. tested baselines โ€‹

Some README cells advertise a wider range than the actual test matrix below. The README cells reflect the package's design intent (the plugin core is bundler-agnostic and most reasonable versions should work) ; the matrix above reflects only what we actually exercise. If a wider range matters to you, here is the gap :

README claimActually tested in CIUntested versions
Next.js v13 โ†’ v16v16.2.4 onlyv13, v14, v15 (App Router + Pages Router)
Nuxt v3 + v4v4.4.2 onlyv3.x
SvelteKit v2 (Svelte v4+v5)v2.58.0 + Svelte 5.55.5 onlySvelte v4.x (pre-runes)
Astro v4 โ†’ v6v6.1.9 onlyv4.x, v5.x
Vite v4 โ†’ v8v8.x onlyv4, v5, v6, v7
Vue v3.5+v3.5.14 onlyother v3.5 / v3.6 patches as they ship
Webpack v5v5.106.x only (in test-webpack-standalone)older v5 minors (rare in greenfield projects)

We track these gaps in issue: phase-2 test apps coverage โ€” contributions welcome.