Skip to main content

How it works

How the tailess plugin enumerates every class your ss() calls can produce and hands the list to Tailwind through its own @source inline safelist.

What Tailwind can see

Tailwind v4 generates CSS by scanning your source for literal class strings. It never runs your code, so a prefix joined to a utility at runtime is something it cannot know about.

Invisible to the scanner
// Tailwind scans for literal class strings and never runs your code,// so this concatenation is invisible to it."md:" + "text-2xl"

The class lands on the element and there is no rule behind it. Your element is simply unstyled, with nothing in the console and nothing in the build log — which is where every hand-rolled version of this idea quietly fails.

The pipeline

The plugin closes that gap without asking Tailwind to change how it works. It reads your source, enumerates every class your ss() calls can produce, and hands the list to Tailwind through its own @source inline(...) safelist.

Source to CSS
your source                  tailess plugin                    Tailwind───────────                  ──────────────                    ────────ss({ md: "text-2xl" })  ──▶  scan + enumerate                             "md:text-2xl"                                   │                                   ▼                             node_modules/.cache/…/tailess.css                             @source inline("md:text-2xl")  ──▶  .md\:text-2xl { … }                                   │       your app.css  ──▶  @import "…/tailess.css"  (injected)

Nothing in that path depends on your code being executed, only on it being read. What the list contains is therefore bounded by what the scanner can see at your call sites — the literals it reads and the ones it cannot.

Why this is reliable

Three details make this reliable rather than merely clever.

The list goes in a separate file, not inline

Tailwind re-reads @source inline(...) only when it rebuilds its compiler, and it only rebuilds when one of its own build dependencies changes. Your .tsx files are not dependencies — but an @imported stylesheet is.

Writing the list to a sidecar file makes every change a guaranteed rebuild trigger, which is what lets a brand-new class work without restarting the dev server. A class you delete loses its CSS too, instead of lingering forever.

Injection is scoped to real Tailwind entries

The plugin only touches a stylesheet that Tailwind actually emits utilities into — directly, or through a chain of relative @imports. Every other CSS file in your build comes out byte-identical.

A marker proves it is wired up

The plugin declares a custom property on the root element. In dev, the first time the runtime builds a prefixed class it checks for that marker and, if it is missing, prints exactly which line of config you have not added.

The marker
:root { --tailess: 1; }

Dev only

No marker check runs in production. If something else already supplies the CSS — your own safelist, say — declare the marker yourself and the warning goes quiet. Troubleshooting has that case, and the rest.