Tailwind v4 @source inline(): replace the v3 safelist
Trace an absent grid utility through a production build, replace the legacy safelist, and include shared UI classes without generating every combination.
TL;DR#
Tailwind scans source text for complete utility tokens; it does not evaluate JavaScript that assembles a class name. Replace interpolation with a map of complete strings when those choices belong in your code. For classes supplied outside the scanned files, use @source inline() on Tailwind 4.1 or later, or register the source directory. Loading an old config with @config does not restore its unsupported safelist option. These are the distinctions in Tailwind's source detection documentation and compatibility reference.
The Stack Overflow safelist question asks how to replace a legacy pattern for responsive grid columns. Its important symptom is missing generated CSS. There need not be a compiler exception: a successful build can omit a class that only exists after JavaScript runs.
Recognise a missing selector before changing configuration#
This source contains an ordinary grid class and an interpolated column class:
import './style.css';
const columns = 3;
document.querySelector('#app').innerHTML = `
<div class="grid grid-cols-${columns}">
<span>One</span><span>Two</span><span>Three</span>
</div>
`;The browser receives a complete column class when that script runs. The scanner sees the source expression instead. If the complete token appears nowhere else in a scanned file or inline source, its CSS is absent. A different component containing the same literal can conceal this mistake, so test with a deliberately small source tree.
The reproduction for this article ran on 2 November 2026 with Tailwind CSS 4.3.3, Vite 8.3.5 and Node 24.22.2 in an isolated Alpine container. It inspected production CSS; it did not claim a browser rendering test. With source detection limited to the application directory, the first build succeeded and reported:
dynamic token: .grid{ = true
dynamic token: .grid-cols-3{ = falseThose lines are selector checks from the reproduction script, not Tailwind error messages. They establish that the pipeline generates utilities, while the column selector is missing. If your built CSS already contains the selector but the browser has no stylesheet, follow the separate Vite build CSS loading diagnosis. A safelist cannot repair an asset that was generated correctly but never loaded.
Why it happens#
The scanner collects possible utility tokens from text. It does not execute your component or infer all results of a prop, conditional expression or template literal. This explains why an intact string selected at runtime works, while assembling the middle of a class name is unreliable. Tailwind's dynamic class guidance recommends mapping props to complete strings.
Source coverage is a separate question. A complete token in an ignored dependency still contributes nothing until that dependency is registered. The documented source exclusions include ignored files, dependency directories, CSS, binary files and lockfiles. Putting class names in an arbitrary CSS comment is therefore a poor replacement for declaring an inline source.
Legacy configuration introduces another trap. The official directive reference documents loading JavaScript configuration through @config, while identifying its safelist option as unsupported. The reproduction loaded a config containing the desired class, and that class remained absent. Loading the file and supporting each historical option are different behaviours.
Fix#
1. Keep application choices as complete tokens#
For a finite component API, replace the earlier file with a map. The runtime selects a whole class string, leaving each supported utility visible to the scanner:
import './style.css';
const columns = 3;
const columnClasses = {
1: 'grid-cols-1',
2: 'grid-cols-2',
3: 'grid-cols-3',
};
const columnClass = columnClasses[columns] ?? columnClasses[1];
document.querySelector('#app').innerHTML = `
<div class="grid ${columnClass}">
<span>One</span><span>Two</span><span>Three</span>
</div>
`;Keep your existing working Tailwind integration. For the isolated example, this stylesheet explicitly restricts scanning to its own directory, making the input set reproducible:
@import "tailwindcss" source(none);
@source "./";Automatic detection remains a valid choice in a normal application; disabling it here prevents unrelated files from accidentally supplying a missing token. The source path is relative to this stylesheet. The explicit source documentation explains that path convention.
2. Declare an inline source for externally selected classes#
When complete tokens are supplied by a CMS or another input unavailable at build time, declare the supported set in the stylesheet. As an alternative to changing the original JavaScript, this single addition generated the missing selector:
@import "tailwindcss" source(none);
@source "./";
@source inline("grid-cols-3");This is a build-time declaration, not a runtime instruction. New values arriving after deployment still need corresponding generated CSS. Tailwind introduced this feature in its 4.1 release. If the installed packages predate that feature, update the Tailwind integration together or keep complete tokens in a scanned source file.
For the responsive choices in the original question, use a bounded brace expression instead of a broad pattern. This example supports three column counts, with base, small-screen and medium-screen versions:
@import "tailwindcss" source(none);
@source "./";
@source inline("{sm:,md:,}grid-cols-{1..3}");The empty item after the last comma includes the unprefixed utility. Without that empty choice, only the listed responsive prefixes are requested. The range supplies the supported counts; it does not mean every possible grid size. This follows the documented brace expansion syntax, and both forms were checked in the production reproduction.
3. Register a shared UI source instead of duplicating its classes#
When a dependency ships complete utility strings, register its directory. For an application whose stylesheet is in the source directory, a package at the application root's dependency directory has this relative path:
@import "tailwindcss";
@source "../node_modules/@acmecorp/ui-lib";Replace that illustrative package path with the installed package that owns your components. This is a source scan, not a CSS import. If the library instead ships its own compiled stylesheet, follow its instructions for loading that CSS. If its implementation interpolates class fragments, adding its directory does not resolve those expressions; it still needs complete strings or an explicit supported set.
The production control used a small local fixture under a dependency directory containing a complete background utility. It was absent with ordinary application sources and present after adding that fixture's directory. Shared workspace components follow the same principle: register the actual source directory using a path from the stylesheet. For builds launched at a monorepo root, Tailwind also documents setting an explicit source base path.
Verify the fix#
Rebuild using the application's production command. For the Vite fixture, the command was:
npm run buildOpen the emitted CSS and search for the exact selector, rather than checking only that the build exited successfully. Minification can remove spaces before braces, and responsive selectors escape their colon in CSS. The reproduction's checks after the single inline addition and after the bounded expression were:
single inline source: .grid-cols-3{ = true
bounded brace range: .grid-cols-3{ = true
bounded brace range: .sm\:grid-cols-3{ = true
bounded brace range: .md\:grid-cols-3{ = true
bounded brace range: .grid-cols-4{ = falseThe last line checks the boundary: the fix included supported choices without silently extending the set. The static mapping independently produced the base column selector. Removing the empty prefix option independently removed the base selector while retaining a responsive one. These controls distinguish token generation from a coincidental match in another file.
Once the selector exists, inspect the loaded stylesheet and the selected element in browser developer tools. A responsive utility needs its media condition to match. A present declaration can also lose to another rule; for spacing that exists but is overridden, the Tailwind reset and cascade diagnosis explains the separate layer conflict. Generating additional copies does not change which declaration wins.
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.
Bound the runtime vocabulary and check explicit exclusions#
Avoid multiplying every colour, size, breakpoint and state into one expression. Brace expansion makes a short declaration capable of requesting a large output. Generate the combinations the interface offers, then inspect the resulting CSS. A CMS that accepts unrestricted utility text has no finite supported vocabulary for the build to discover; decide which choices the application actually supports before expanding them.
An explicit exclusion can defeat an inclusion. The source exclusion reference documents @source not inline(). The reproduction included and excluded the same column utility in separate declarations, and the selector remained absent. Check for such exclusions before widening a safelist or blaming a stale build.
Finally, requesting a token does not define a missing theme value. If the next failure is Cannot apply unknown utility class border-border, follow that page's theme-registration or reference-context fix. This article's controls use utilities Tailwind already understands. The successful outcome is a production stylesheet containing the supported selectors, sourced from complete strings, a registered directory or an intentionally bounded inline declaration.
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 v4 padding and margin not working
Prove that the utility exists but loses the CSS cascade, restore spacing with one layer change, and inspect the child margins behind space-x.
Fix Tailwind v4: dark class changes no background colour
Keep one bg-bgContrast class while changing its inherited colour, with real browser checks for the wrong theme syntax and the inline-token trap.
Tailwind CSS not working after Vite build: dev vs dist
Find out in two commands whether the built CSS really lacks your classes or the browser never loaded it, then apply the one change that fixes each case.


