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 Type | Tailwind v3 | Tailwind v4 | Extraction Method |
|---|---|---|---|
| HTML Static | โ Full | โ Full | Built-in extractors |
| React / Next.js | โ Full | โ Full | Built-in JSX extractors |
| Vue 3 | โ Full | โ Full | Built-in :class binding support |
| Svelte 4 + 5 / SvelteKit | โ Full | โ Full | Built-in class: directive support |
| Astro | โ Full | โ Full | Built-in class:list support |
| Qwik | โ Full | โ Full | Built-in class$ support |
| Solid.js | โ Full | โ Full | Built-in JSX extractors |
| React Router (ex-Remix) | โ Full | โ Full | Built-in JSX extractors |
| TanStack Start / Router | โ Full | โ Full | Built-in JSX extractors |
Class Pattern Support โ
HTML Patterns โ
| Pattern | Example | Extraction | Obfuscation |
|---|---|---|---|
| Double quotes | class="bg-blue-500" | โ | โ |
| Single quotes | class='bg-blue-500' | โ | โ |
| Multi-line | class="bg-blue-500\n text-white" | โ | โ |
| No quotes | class=bg-blue-500 | โ (since v2.0.1) | โ (since v2.0.1) |
JSX/React Patterns โ
| Pattern | Example | Extraction | Obfuscation |
|---|---|---|---|
| String attribute | className="bg-blue-500" | โ | โ |
| Single quotes | className='bg-blue-500' | โ | โ |
| Braces with string | className={"bg-blue-500"} | โ | โ ๏ธ |
| Template literal (static) | className={`bg-blue-500`} | โ | โ ๏ธ |
| Template literal (dynamic) | className={`bg-${color}-500`} | โ | โ |
| Ternary | className={active ? "bg-blue" : "bg-gray"} | โ * | โ ๏ธ |
| Variable | className={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 โ
| Pattern | Example | Extraction | Obfuscation |
|---|---|---|---|
| @apply | @apply bg-blue-500 p-4; | โ | โ |
| @layer components | @layer components { .btn { @apply ... } } | โ | โ |
| @theme (v4) | @theme { --color-primary: #3b82f6; } | N/A | N/A |
Tailwind Feature Support โ
Modifiers and Variants โ
| Feature | Example | v3 | v4 |
|---|---|---|---|
| Responsive | sm:bg-blue-500 | โ | โ |
| Dark mode | dark:bg-gray-800 | โ | โ |
| Hover/Focus | hover:bg-blue-700 | โ | โ |
| Active | active:bg-blue-800 | โ | โ |
| Group hover | group-hover:opacity-100 | โ | โ |
| Peer states | peer-checked:block | โ | โ |
| First/Last | first:rounded-t-lg | โ | โ |
| Odd/Even | odd:bg-gray-50 | โ | โ |
| Before/After | before:content-[''] | โ | โ |
| Important | !bg-green-500 | โ | โ |
| Negative | -ml-4 | โ | โ |
Tailwind v4 Specific Features โ
| Feature | Example | Extraction | Obfuscation |
|---|---|---|---|
| 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 attributes | data-[state=open]:bg-green | โ | โ |
| ARIA states | aria-disabled:opacity-50 | โ | โ |
| ARIA arbitrary | aria-[current=page]:font-bold | โ | โ |
| Supports queries | supports-[display:grid]:grid | โ | โ |
| Has variant | has-[:checked]:bg-blue-500 | โ | โ |
| Not variant | not-first:mt-4 | โ | โ |
| Min/Max breakpoints | min-[320px]:flex | โ | โ |
| CSS variable shorthand | bg-(--my-color) | โ | โ |
| CSS var with type hint | bg-(color:--my-bg) | โ | โ |
| CSS var with opacity | bg-(--primary)/50 | โ | โ |
| Group-data variants | group-data-[state=open]:block | โ | โ |
| Group-aria variants | group-aria-expanded:rotate-180 | โ | โ |
| Peer-data variants | peer-data-[checked]:bg-blue | โ | โ |
| Named group variants | group/sidebar:hover:bg-gray | โ | โ |
| Named peer variants | peer/input:focus:ring-2 | โ | โ |
| Wildcard selector | *:flex | โ | โ |
| Deep wildcard selector | **:text-sm | โ | โ |
| In variants | in-hover:bg-blue-500 | โ | โ |
| In with data attr | in-data-[state=open]:block | โ | โ |
| In with arbitrary | in-[.sidebar]:bg-gray-100 | โ | โ |
| nth variants | nth-2:bg-gray-100 | โ | โ |
| nth arbitrary | nth-[2n+1]:bg-gray-50 | โ | โ |
| nth-last variants | nth-last-2:mb-0 | โ | โ |
| nth-of-type variants | nth-of-type-2:text-lg | โ | โ |
| Starting style | starting:opacity-0 | โ | โ |
| Forced colors | forced-colors:outline | โ | โ |
| Not forced colors | not-forced-colors:shadow-lg | โ | โ |
| Trailing important | flex! | โ | โ |
| Arbitrary variants | [&_p]:text-blue-500 | โ | โ |
| Arbitrary media | [@media(min-width:640px)]:flex | โ | โ |
| Arbitrary supports | [@supports(display:grid)]:grid | โ | โ |
| Extended breakpoints | 3xl:flex to 7xl:hidden | โ | โ |
| User interaction states | user-valid:border-green-500 | โ | โ |
| Inert state | inert:opacity-50 | โ | โ |
| Pointer device queries | pointer-fine:cursor-pointer | โ | โ |
| Optional form state | optional:border-gray-300 | โ | โ |
| Small container sizes | @3xs:flex, @2xs:grid | โ | โ |
Arbitrary Values โ
| Feature | Example | Extraction | Obfuscation |
|---|---|---|---|
| Arbitrary color | bg-[#1da1f2] | โ | โ |
| Arbitrary spacing | p-[13px] | โ | โ |
| Arbitrary property | [color:red] | โ | โ |
| CSS functions | bg-[url('/img.png')] | โ | โ |
| calc() | w-[calc(100%-20px)] | โ | โ |
| CSS variables | bg-[var(--my-color)] | โ | โ |
Opacity Modifiers โ
| Feature | Example | v3 | v4 |
|---|---|---|---|
| Standard opacity | bg-blue-500/50 | โ | โ |
| Arbitrary opacity | bg-blue-500/[.25] | โ | โ ๏ธ |
Gradients โ
| Feature | Example | v3 | v4 |
|---|---|---|---|
| from-* | from-blue-500 | โ | โ |
| via-* | via-pink-500 | โ | โ |
| to-* | to-purple-500 | โ | โ |
| Percentage stops | from-10% | โ | โ |
Utility Library Support โ
| Library | Example | Extraction | Obfuscation |
|---|---|---|---|
| clsx | clsx('bg-blue', {'text-white': true}) | โ | โ |
| classnames | classnames('a', 'b') | โ | โ |
| tailwind-merge | twMerge('p-4', 'p-2') | โ | โ |
| cn (shadcn/ui) | cn('bg-blue', className) | โ | โ |
| CVA | cva('base', { variants }) | โ | โ |
| tailwind-variants | tv({ 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 โ
| Pattern | Example | Extraction | Obfuscation |
|---|---|---|---|
| classList.add | el.classList.add('bg-blue-500') | โ ๏ธ | โ ๏ธ |
| classList.toggle | el.classList.toggle('active') | โ ๏ธ | โ ๏ธ |
| className = | el.className = 'bg-blue-500' | โ ๏ธ | โ ๏ธ |
| setAttribute | el.setAttribute('class', '...') | โ ๏ธ | โ ๏ธ |
Legend โ
| Symbol | Meaning |
|---|---|
| โ | Fully supported |
| โ ๏ธ | Partial support / requires care |
| โ | Not supported |
| N/A | Not applicable |
Recommendations โ
For Maximum Compatibility โ
- Use complete static class strings - Avoid dynamic class construction
- Prefer ternary operators -
isActive ? "bg-blue" : "bg-gray"over template interpolation - Use object mapping - Map state to complete class strings
- Avoid utility libraries with dynamic logic - Or ensure all classes are also used statically somewhere
Class Pattern Examples โ
// โ
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 โ
| Configuration | Tailwind | Status | Notes |
|---|---|---|---|
| HTML Static | v3 | โ Works | Use tailwindcss-patch@^3.0.1 |
| Vite React | v3 | โ Works | Use tailwindcss-patch@^3.0.1 |
| Next.js | v3 | โ Works | Use webpack plugin config |
| HTML Static | v4 | โ Fails | No tailwind.config.js |
| Vite React | v4 | โ Fails | No tailwind.config.js |
| Next.js | v4 | โ Fails | No 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 Type | Tested Version | Tailwind | Bundler | Test App |
|---|---|---|---|---|
| Next.js (App Router) | 16.2.4 | v4 | Webpack / Turbopack | apps/test-nextjs |
| Next.js (legacy v3) | 16.2.4 | v3 | Webpack | apps/tailwind_v3_react_nextjs |
| Next.js + shadcn/ui | 16.2.4 | v4 | Webpack | apps/test-shadcn-ui |
| Nuxt | 4.4.2 | v4 | Vite | apps/test-nuxt |
| SvelteKit + Svelte 5 | 2.58.0 (Svelte 5.55.5) | v4 | Vite | apps/test-sveltekit |
| Astro | 6.1.9 | v4 | Vite | apps/test-astro |
| Solid.js | 1.9.12 | v4 | Vite | apps/test-solidjs |
| Qwik | 1.19.2 | v4 | Vite | apps/test-qwik |
| React Router (ex-Remix) | 7.14.2 | v4 | Vite | apps/test-react-router |
| TanStack Start | 1.168.25 | v4 | Vite | apps/test-tanstack-start |
| React + Vite | 19.1.0 | v4 | Vite 8.x | apps/test-vite-react |
| React + Vite (TW v3) | 19.0.0 | v3 | Vite 8.x | apps/test-tailwind-v3 |
| React + Vite (TW v4) | 19.0.0 | v4 | Vite 8.x | apps/test-tailwind-v4 |
| Vue 3 + Vite | 3.5.14 | v4 | Vite 8.x | apps/test-vite-vue |
| Static HTML (TW v3) | n/a | v3 | Vite | apps/tailwind_v3_html_static |
| Static HTML (TW v4) | n/a | v4 | Vite | apps/tailwind_v4_html_static |
| Static HTML + esbuild | n/a | v4 | esbuild โฅ 0.28 | apps/test-static-html |
| Rollup standalone | n/a | v4 | Rollup ^4.60 | apps/test-rollup-standalone |
| Webpack standalone | n/a | v4 | Webpack ^5.106 | apps/test-webpack-standalone |
| Rspack standalone | n/a | v4 | @rspack/core โฅ1.7.11 | no test app โ adapter exercised by tarball-smoke |
| Farm standalone | n/a | v4 | @farmfe/core โฅ1.7.11 | no 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.
| App | Validates |
|---|---|
apps/test-safelist | The exclude option (a.k.a. safelist) โ listed classes stay unchanged |
apps/test-tailwind-variants | The tv() extractor โ base / variants / compoundVariants all rewrite |
apps/tailwind_v4_react_nextjs | Next.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 claim | Actually tested in CI | Untested versions |
|---|---|---|
| Next.js v13 โ v16 | v16.2.4 only | v13, v14, v15 (App Router + Pages Router) |
| Nuxt v3 + v4 | v4.4.2 only | v3.x |
| SvelteKit v2 (Svelte v4+v5) | v2.58.0 + Svelte 5.55.5 only | Svelte v4.x (pre-runes) |
| Astro v4 โ v6 | v6.1.9 only | v4.x, v5.x |
| Vite v4 โ v8 | v8.x only | v4, v5, v6, v7 |
| Vue v3.5+ | v3.5.14 only | other v3.5 / v3.6 patches as they ship |
| Webpack v5 | v5.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.