tailwind2-8 Exercise 2: Finish by Hand to Zero Pixels, and Test the Safelist
===============================================================================
Start from the project after the upgrade tool (exercise 1). Delete tailwind.config.js, replace src/input.css, edit three lines of index.html, rebuild, screenshot and compare with the v3 baseline.
--- src/input.css ---
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
@source inline("bg-{emerald,pink}-500");
@custom-variant dark (&:is(.dark *));
@theme {
--color-brand-500: #4f46e5;
--color-brand-600: #4338ca;
--color-surface: #f1f5f9;
--font-display: Georgia, serif;
--breakpoint-3xl: 1920px;
--radius-card: 0.75rem;
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button { border-color: var(--color-gray-200, currentcolor); }
h1 { @apply text-2xl font-bold; }
}
@utility btn {
@apply inline-flex items-center rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-white hover:bg-brand-600 focus:outline-hidden focus:ring-2;
}
@utility panel {
background: var(--color-surface);
border-radius: var(--radius-card);
padding: --spacing(4);
}
--- index.html changes ---
safelisted + transform
node node_modules/@tailwindcss/cli/dist/index.mjs -i src/input.css -o dist/out.css Done in 330ms, exit 0
node pix.mjs v3.png up2.png diff2.png -> v3.png vs up2.png differing pixels 0 of 490000
--- dark.mjs ---
import { withPage } from './../c4/cdp.mjs';
const d=process.argv[2];
await withPage(`file:///C:/lwdj/tw2/c8/${d}/index.html`, async (p) => {
console.log(d,'body bg light:', await p.css('body','background-color'));
await p.ev(`document.documentElement.classList.add('dark')`);
console.log(d,'body bg with :', await p.css('body','background-color'));
});
node dark.mjs proj -> body bg light rgb(255,255,255) ; with rgb(15,23,42)
node dark.mjs up2 -> body bg light rgb(255,255,255) ; with oklch(0.208 0.042 265.755) (the same slate-900)
grep -c '\.bg-pink-500' dist/out.css -> 1 (from @source inline)
Safelist test, v3 config with safelist: ['bg-emerald-500','bg-pink-500'] (bg-pink-500 appears nowhere in the markup):
v4 build with @config still loading the file: bg-pink-500 rule present (1) LOOKS like safelist works
rewrite the entry as 'bg-pink-' + '500' v4: 0 rules v3 (same config): 1 rule
The scanner had found the literal 'bg-pink-500' in tailwind.config.js itself; the safelist option was never read.
WHY THIS WORKS AS AN ANSWER
---------------------------
Each setting in the JavaScript config has a CSS home, and once all are moved the page is pixel-identical (0 of 490,000 pixels), including dark mode (@custom-variant dark with the .dark class) and the typography plugin
(@plugin). The three markup edits are the ones the tool could not decide for you: bg-black/50 replaces the removed opacity utility, ring-3 ring-blue-500/50 restores the old ring colour, and scale-105 no longer needs transform. The safelist test
shows why a "works" result can be a coincidence: in v4 the file scanner sees ANY text file, including the config, so a literal class name written in a safelist is generated as if it were used. Concatenating the string hides it from the scanner and
shows the truth: safelist is ignored, matching Tailwind's guide ("corePlugins, safelist, and separator ... are not supported in v4.0"). Use @source inline("bg-{emerald,pink}-500").