Tailwind Arbitrary Values: The [Bracket] Syntax Guide
Tailwind arbitrary values let you drop any raw CSS value into a utility class using square brackets, like w-[237px] or bg-[#1da1f2]. Reach for them for genuine one-offs (an exact brand color, a pixel-perfect width, a calc() expression) that do not belong in your theme. For anything repeated across the codebase, define a token and use the named utility instead.
What arbitrary values actually do
An arbitrary value tells Tailwind to generate a utility on the fly instead of looking one up in your theme. The syntax is the utility namespace, then your value in square brackets. The generated CSS is identical to what a theme utility would produce, but the value is whatever you wrote rather than a design token.
<div class="top-[117px] bg-[#bada55] text-[22px]">...</div>
<div class="before:content-['Festivus']">...</div>
They compose with every modifier you already use, so responsive prefixes, state variants, and pseudo-elements all work the same way. This is what makes them feel native rather than like an escape hatch:
<div class="top-[117px] lg:top-[344px] hover:bg-[#1da1f2]">...</div>
When to use them vs. theme utilities
The deciding question is repetition. If a value appears once (a hero image height tuned to a specific design comp, a one-time brand accent), an arbitrary value keeps it local and visible right in the markup. If the same hex or measurement shows up three or more times, that is a token waiting to happen. Put it in your theme so a single edit propagates everywhere and your class names stay self-documenting.
- Good fit: a single embed sized to a third-party widget, a magic-number offset, a brand color used in exactly one badge, a
grid-templatethat exists on one layout. - Bad fit: your primary blue, your standard card radius, body font size, the spacing scale. These belong in the theme so they are consistent and renameable.
A useful rule of thumb: arbitrary values are for values the design system does not own. The moment the design system should own a value, promote it. If you are converting an existing stylesheet and want to see what the equivalent utilities look like, the Tailwind to CSS converter shows exactly what each class compiles to, which makes the trade-off concrete.
Underscores, calc(), and whitespace
CSS values often contain spaces, but spaces are not allowed inside a class name. Tailwind solves this by treating the underscore as a space at build time. So grid-cols-[1fr_500px_2fr] compiles to grid-template-columns: 1fr 500px 2fr. This applies anywhere a value needs internal whitespace.
<div class="grid grid-cols-[1fr_500px_2fr]">...</div>
<div class="h-[calc(100vh_-_10px)]">...</div>
There is one important exception. In contexts where underscores are legitimate characters and spaces would be invalid, such as a URL, Tailwind leaves the underscore alone. So bg-[url('/what_a_rush.png')] keeps its underscores. When you genuinely need a literal underscore in an ambiguous spot, escape it with a backslash, as in before:content-['hello\_world']. In JSX you must wrap that in String.raw so the backslash survives, otherwise the template literal eats it.
Type hints: avoiding namespace collisions
Some Tailwind namespaces serve multiple CSS properties. The text- prefix handles both font size (text-2xl) and color (text-gray-900); the bg- prefix handles color and other background properties. With a literal value Tailwind can usually guess: a hex looks like a color, a unit looks like a length. But when the value is a CSS variable, Tailwind cannot inspect it and does not know which property you mean.
Type hints resolve this by prefixing a CSS data type before the value. You add length: to force a font size, or color: to force a color. This is the canonical fix for the "my variable is being treated as the wrong property" bug.
<!-- v3 style with var() -->
<div class="text-[length:var(--my-size)]">...</div>
<div class="text-[color:var(--my-color)]">...</div>
If you are picking the underlying hex or HSL value for one of these variables, a quick way to lock it in is the color converter to normalize formats, or the image color picker to sample an exact brand color straight from a logo before you paste it into bg-[#...].
Arbitrary variants and arbitrary properties
The bracket syntax is not limited to values. You can write an arbitrary variant to target a selector Tailwind has no built-in name for, putting the selector inside brackets with & as the placeholder for the element. You can also write an arbitrary property for a CSS property Tailwind does not ship a utility for at all.
<!-- arbitrary variant: complex nth-child selector -->
<li class="lg:[&:nth-child(-n+3)]:hover:underline">...</li>
<!-- arbitrary property: no utility exists for mask-type -->
<div class="[mask-type:luminance] hover:[mask-type:alpha]">...</div>
<!-- set a CSS variable inline, responsively -->
<div class="[--scroll-offset:56px] lg:[--scroll-offset:44px]">...</div>
Tailwind v4 notes
Tailwind v4 changed how you reference CSS variables in arbitrary contexts. Instead of writing var(--my-var) inside brackets, you can use a parentheses shorthand and Tailwind adds the var() for you. So fill-[var(--my-brand-color)] becomes fill-(--my-brand-color), and the type-hint form becomes text-(length:--my-var) or text-(color:--my-var).
v4 also reduced how often you need arbitrary values in the first place. Many utilities now accept bare numeric arguments out of the box because the spacing scale is generated from a single CSS variable, so things like arbitrary grid column counts often work without brackets. The flip side is the build-size and maintainability cost: every unique arbitrary class is a distinct rule the compiler must parse and emit, so a codebase littered with w-[173px], w-[174px], and w-[175px] produces more CSS and is harder to keep consistent than one that leans on a small, named scale.
| Need | Syntax |
|---|---|
| One-off value | w-[237px], bg-[#1da1f2] |
| Value with spaces | grid-cols-[1fr_500px_2fr] |
| Calc expression | h-[calc(100vh_-_10px)] |
| Type hint (v3) | text-[length:var(--s)] |
| CSS var shorthand (v4) | fill-(--brand) |
| Arbitrary property | [mask-type:luminance] |
| Arbitrary variant | [&:nth-child(3)]:underline |
The practical takeaway: arbitrary values are a precision tool, not a default. Use them where the design system has no opinion, prefer the parentheses shorthand on v4, add a type hint whenever a CSS variable hits an ambiguous namespace, and promote any value you write twice into your theme.
Frequently Asked Questions
Use an arbitrary value for genuine one-offs that the design system does not own, such as a magic-number offset or a single brand color used in one place. If a value appears three or more times, define a theme token and use the named utility so edits propagate everywhere.
Class names cannot contain spaces, so Tailwind converts underscores to spaces at build time, letting values like grid-cols-[1fr_500px_2fr] compile correctly. The exception is contexts like URLs where underscores are valid characters, where Tailwind preserves them instead.
The text- namespace serves both font size and color, so when you pass a CSS variable Tailwind cannot tell which you mean. Prefixing length: forces a font size and color: forces a color, resolving the namespace collision.
Tailwind v4 adds a parentheses shorthand for CSS variables, so fill-[var(--brand)] becomes fill-(--brand) with var() added automatically. Many utilities also accept bare numeric arguments out of the box, reducing how often you need bracket syntax at all.