Tailwind CSS Intermediate: Theme, Variants and Reuse
Arbitrary Values and Class Detection
In this lesson you will
- Write arbitrary values and properties
- Explain how Tailwind detects classes in source files
- Fix missing styles caused by dynamic class names
Introduction
The theme covers most needs, but sometimes you need a value that is not in it: a 137px width, a specific hex colour from a design file, a precise grid template. Tailwind lets you write arbitrary values with square brackets. A related and very common problem, classes that mysteriously have no effect, comes from how Tailwind detects classes. Both are covered here.
Arbitrary values
Put the value in square brackets:
HTML
<div class="w-[137px] text-[#bada55] top-[117px] grid-cols-[1fr_2fr]">
...
</div>Results from compiling these:
| Class | Generated CSS |
|---|---|
w-[137px] | width: 137px |
text-[#bada55] | color: #bada55 |
grid-cols-[1fr_2fr] | grid-template-columns: 1fr 2fr |
bg-[url('/img.png')] | background-image: url('/img.png') |
Rules:
- Underscores become spaces:
grid-cols-[1fr_2fr]is1fr 2fr. In v4, commas in grid and object-position values are no longer turned into spaces, so use underscores for spaces explicitly. - The utility prefix decides the property, so
text-[#bada55]is a colour andtext-[20px]a size. When this is ambiguous, add a type hint:text-[length:var(--my-size)]. - They work with variants:
md:w-[137px]andhover:bg-[#bada55].
Arbitrary properties
For a CSS property without a utility, write the whole declaration in brackets:
HTML
<div class="[mask-type:luminance]">...</div>I compiled it to mask-type: luminance.
CSS variables
Use parentheses for CSS variables in v4:
HTML
<div class="p-(--my-pad) bg-(--card-bg)">...</div>p-(--my-pad) compiled to padding: var(--my-pad). The old v3 form p-[--my-pad] is no longer how you do it.
Opacity and important
bg-blue-500/50makes a colour 50 percent opaque, andbg-blue-500/[0.37]accepts an arbitrary opacity.font-bold!marks a utility!important, putting the exclamation mark at the end in v4. I confirmed it generatesfont-weight: var(--font-weight-bold) !important, andmd:p-4!works too. The old leading form!font-boldis deprecated, though I found it still compiles.
Quick quiz
In grid-cols-[1fr_2fr], what do the underscores mean?
Use arbitrary values with care
They are an escape hatch. If you find yourself writing text-[#5b4bf5] in twenty places, add it to the theme as --color-brand instead. A good rule: once is fine, three times means promote it to the theme.
How Tailwind finds classes
Tailwind does not execute your code. It scans your source files as plain text looking for strings that look like class names, and generates CSS for those it finds. By default it scans every file in your project except those in your .gitignore, binary files and CSS files.
This has an important consequence: Tailwind only knows about complete class names that appear literally in your files.
I tested this by compiling with two different candidate lists. When text-red-500 appeared, its CSS was generated. When only text-red-600 appeared, text-red-500 was not generated.
The dynamic class trap
React (JSX)
// Broken: "text-red-500" never appears as a full string
const colour = "red";
<p className={`text-${colour}-500`}>Warning</p>Tailwind never sees text-red-500 anywhere, so no CSS is created and the text is unstyled. Always use complete strings:
React (JSX)
// Works: complete class names appear in the source
const colours = {
red: "text-red-500",
green: "text-green-500",
};
<p className={colours[colour]}>Warning</p>This applies to anything that builds class names from variables, including server data or a CMS. If class names come from data at run time, they are invisible to Tailwind.
Telling Tailwind where to look
Add extra sources, for example a component library inside node_modules:
CSS
@import "tailwindcss";
@source "../node_modules/@my-company/ui-lib";Exclude a path with @source not "./legacy". I compiled this and it was accepted.
Safelisting classes
If classes must exist but never appear in your files, such as classes from a database, force them with @source inline():
CSS
@source inline("underline hover:underline");I compiled this, and both underline and hover:underline were generated, with the hover rule inside the @media (hover: hover) block. In v3 this was the safelist option in tailwind.config.js, which v4 does not support. You can also use brace ranges to safelist many classes, as the official docs show.
Debugging missing styles
When a class has no effect:
- Is the class name spelled correctly and written in full in a source file?
- Is that file within the scanned paths (not git-ignored, and not in a third-party package)?
- Is the dev server rebuilding?
- Is another rule overriding it? Use the browser developer tools to see.
- Is it a v3 class that no longer exists, such as
bg-opacity-50?
Try it yourself
Create a component with a colour prop that builds bg-${colour}-500, and confirm it fails. Fix it with a lookup object. Then add an arbitrary value, promote it to the theme and replace it.
Summary
- Square brackets give one-off values (
w-[137px]), underscores mean spaces, and parentheses use CSS variables (p-(--my-pad)). - Tailwind scans files as text and only generates CSS for complete class names it can find, so dynamic names like
text-${colour}-500fail. - Use lookup objects for variants,
@sourceto add paths and@source inline()to safelist classes.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading