The theme covers the vast majority of designs, but every project eventually needs a one-off value: a 117px offset to line up with a third-party widget, a brand color that exists nowhere else, or a selector that no built-in variant expresses. Tailwind's square-bracket syntax handles all of these without leaving the HTML. After this lesson you will know the three kinds of arbitrary syntax, how to reference CSS variables, and when to reach for @theme instead.
Put any valid CSS value in square brackets after a utility name:
<div class="mt-[13px] w-[37.5%] max-w-[65ch] rotate-[17deg]"></div>
<div class="bg-[#bada55] text-[oklch(0.7_0.15_200)] shadow-[0_4px_20px_rgb(0_0_0/0.15)]"></div>
<div class="grid grid-cols-[200px_minmax(0,1fr)_200px] gap-[clamp(1rem,3vw,2rem)]"></div>
<p class="before:content-['Note:_'] font-[Inter,sans-serif]"></p>Two rules make this work. Spaces are written as underscores (_) because a space would end the class name; to output a literal underscore, escape it as \_. And the generated utility still supports every variant, so md:w-[37.5%] and hover:bg-[#bada55] are valid.
Tailwind usually infers what the value means from its form. When a value is ambiguous — text-[…] can be a font size or a color — add a type hint: text-[length:var(--size)] or text-[color:var(--brand)].
Reading a custom property is so common that v4 gives it a shorter form with parentheses:
<div class="bg-(--brand) border-(--brand) text-(--fg)"></div>
<div class="p-(--gutter) md:p-(--gutter-lg)"></div>bg-(--brand) compiles to background-color: var(--brand). The v3 form bg-[--brand] is no longer supported; bg-[var(--brand)] still works but is longer. Type hints use the same syntax: text-(length:--size).
You can also set a variable from a class, which is a neat way to pass values into child components or into an arbitrary property:
<section class="[--card-radius:1rem] md:[--card-radius:1.5rem]">
<div class="rounded-(--card-radius) border p-4">Radius follows the section</div>
</section>When Tailwind has no utility for a property at all, write the whole declaration in brackets:
<div class="[mask-image:linear-gradient(to_bottom,black,transparent)]"></div>
<input class="[field-sizing:content] [caret-shape:block]" />
<div class="[scrollbar-width:thin] [scrollbar-color:gray_transparent]"></div>These behave like normal utilities, so hover: and md: prefixes work. Arbitrary properties are the right tool for new or rarely used CSS features; if you find the same one repeated across a project, promote it to a custom utility with @utility (covered in a later lesson).
Variants can also be written from scratch. The selector goes in brackets and & stands for the element the class is on:
<li class="[&:nth-child(3)]:font-bold">Third item is bold</li>
<div class="[&_p]:mt-4 [&>h2]:text-xl">Descendant and child selectors</div>
<div class="[&.is-dragging]:cursor-grabbing">Reacts to a class set by JS</div>
<div class="[@supports(display:grid)]:grid">At-rule variants also work</div>Arbitrary variants stack with built-in ones (md:[&>h2]:text-2xl) and with group-[…]: and peer-[…]: for custom relationships, such as group-[.is-published]:block.
| Syntax | What goes in the brackets | Example |
|---|---|---|
| utility-[value] | A CSS value | w-[37.5%] |
| utility-(--var) | A custom property name | bg-(--brand) |
| [property:value] | A full declaration | [mask-type:luminance] |
| [selector]:utility | A selector with & | [&>li]:pl-4 |
Arbitrary syntax is an escape hatch; every bracket is a value the design system does not know about:
text-[#1e293b] on many elements): add it to @theme as --color-ink and use text-ink.mt-[13px]): check whether mt-3 plus a flex/grid alignment fixes the real problem.w-[${size}px]); Tailwind scans source text and will not generate a class it cannot see.How are spaces written inside an arbitrary value such as a grid template?
utility-[value] accepts any CSS value; use _ for spaces and add a type hint when the value is ambiguous.utility-(--var) reads a CSS variable, and [--var:value] sets one from a class.[property:value] writes a full declaration for properties Tailwind does not cover.[selector]:utility creates a one-off variant with & as the element.@theme or @utility; never assemble class names at runtime.Next lesson: Transitions, Transforms and Animations — add motion with transition-*, scale-*, rotate-* and keyframe animations.