1. Tailwind's Matching Engine in a Nutshell
Tailwind 3+ ships with a JIT compiler. Instead of prebuilding a giant stylesheet, it watches your templates, scans for class-like strings, and emits CSS rules for every match.
- You author markup containing potential utility classes.
- Tailwind parses class names against utility patterns defined in its core and config.
- When the engine finds a match, it generates a rule, caches it, and adds it to the output build.
Arbitrary values extend step 2 by allowing literal values inside a pattern instead of fixed suffixes.
2. Anatomy of an Arbitary Value Class
The syntax follows a utility-[value] pattern. The utility portion must match a known generator (such as bg, text, w, grid-cols, translate-x, or any plugin-defined key). The value inside square brackets is passed to that generator.
<div class="bg-[radial-gradient(circle_at_top,_#f97316,_transparent)]"></div>bg-tells Tailwind to use the background utility generator.- The bracketed string is returned as the final CSS value with minimal normalization.
If the utility supports modifiers (like responsive prefixes or hover: variants) they wrap around the entire token:
<button class="md:hover:bg-[rgba(2,132,199,0.85)]">Reserve seat</button>3. How Tailwind Parses Values
When Tailwind encounters an arbitrary class, it:
- Splits the candidate around modifiers (
sm:,dark:,hover:) and the important flag (!). - Looks up the base utility (
bg,text,border, etc.) in its internal registry. - Calls the generator function with the value string from the brackets.
- Verifies that the generator accepts arbitrary values. If not, it ignores the token.
Generators are smart enough to add necessary vendor prefixes, convert underscores to spaces, and escape characters. The underscores in circle_at_top become spaces because Tailwind replaces _ with whitespace inside brackets.
4. Dynamic Lengths, Colors and Functions
Arbitrary values unlock any CSS that the browser supports:
<div class="w-[clamp(16rem,_40vw,_32rem)]"></div>
<div class="text-[length:calc(1rem+1vw)]"></div>
<div class="shadow-[0_25px_50px_-12px_rgba(30,41,59,0.45)]"></div>You can even reference CSS variables:
<div class="bg-[color:var(--card-bg)] text-[color:var(--card-fg)]"></div>Tailwind does minimal interpretation — it simply injects the value into the generated rule, ensuring characters are escaped so the final CSS remains valid.
5. Arbitary Properties
What if the utility key doesn’t exist? Tailwind supports arbitrary properties through the [property:value] syntax:
<div class="[mask-image:linear-gradient(to_top,_transparent,_black)]"></div>Behind the scenes, Tailwind treats everything before the colon as the property name and everything after as the value. It wraps the token in a rule like:
.arbitrary-property-xyz {
mask-image: linear-gradient(to top, transparent, black);
}This approach keeps the expressive power of inline styles while preserving Tailwind’s variant system and purge-friendly scanning.
6. The Role of the Config and Theme Tokens
Tailwind still prefers named design tokens defined in theme. Arbitrary values fall back when you need one-off styling. You can combine both:
<!-- Use theme spacing with a custom fallback -->
<div class="p-[theme(spacing.7)] md:p-[30px]"></div>Inside the brackets you can call helper functions such as theme() or colors(). Tailwind resolves these at build time using your configuration, allowing dynamic yet consistent values.
7. Safelisting and Content Scanning
Because the JIT engine compiles only what it sees, arbitrary values embedded in strings or generated at runtime might be missed. Options include:
- Safelist entries in
tailwind.config.js:
module.exports = {
safelist: [
'bg-[rgba(2,132,199,0.85)]',
{
pattern: /border-[0-9]+px/, // matches border-[1px], border-[2px], etc.
},
],
};
- Compute classes server-side during render rather than on the client.
- Use full strings instead of concatenating pieces so the scanner picks them up.
8. Performance and Caching
Each unique arbitrary value produces a unique CSS rule. Repeating the same class keeps the bundle small because Tailwind caches the generated rule. Excessive unique values can bloat the output, so prefer tokens for repeated patterns and reserve arbitrary values for edge cases.
9. Debugging Arbitrary Classes
When a class doesn’t work:
- Check your build logs. Tailwind warns when it encounters invalid arbitrary syntax.
- Inspect the final CSS output to confirm the rule exists.
- Remember that underscores become spaces and escaped characters must remain inside the brackets.
- Ensure variants like
hover:ordark:are supported for that utility.
If nothing appears in the CSS, Tailwind likely did not recognize the base utility or the class was stripped during tree-shaking.
10. Best Practices
- Default to design tokens for repeatable styles; reach for arbitrary values as exceptions.
- Leverage CSS variables to keep dynamic values manageable.
- Document any safelisted patterns so teammates know why they exist.
- Avoid generating arbitrary classes entirely on the client — precompute them where Tailwind can see them.
- Keep class strings legible; Tailwind ignores whitespace inside brackets, so format long values with underscores or short CSS variables.
Closing Thoughts
Tailwind’s arbitrary value syntax and JIT compiler provide a sweet spot between utility-first consistency and the expressive power of plain CSS. Understanding how the framework parses and emits these classes helps you debug confidently, balance flexibility with maintainability, and ship polished interfaces without cracks in the design system.
If you enjoyed this post or have any feedback, feel free to connect with me:
- 💼 LinkedIn — Let’s network and share ideas!
- 👨💻 GitHub — Check out more of my projects and code examples.
- If you enjoyed my content or want to support my writing journey, please take a moment to follow me on Medium. I regularly share insights on JavaScript, modern frontend development, and practical dev tricks you can use in real projects.
🔗 Read the original article on Medium:
❤️ Follow me on Medium for more:
