|
| 1 | +--- |
| 2 | +name: tailwind-syntax-arbitrary-values |
| 3 | +description: > |
| 4 | + Use when needing a one-off value that is not in the theme: specific hex colors, |
| 5 | + arbitrary pixel sizes, calc() expressions, custom grid templates, unusual font sizes, |
| 6 | + specific transforms, custom selectors, runtime CSS-variable values. Use also when |
| 7 | + reading existing Tailwind markup and encountering `bg-[#1da1f2]`, `w-[calc(100%-2rem)]`, |
| 8 | + `data-[state=open]:`, `[mask-type:luminance]`, `bg-(--brand)`, `[&_p]:`, or any other |
| 9 | + square-bracket / parens-bracket utility, and needing to know what it compiles to. |
| 10 | + Use also when an arbitrary value is being misinterpreted (background-size vs |
| 11 | + background-position, font-size vs color) and a type hint is required. |
| 12 | + Prevents (a) reaching for arbitrary syntax when a theme token already exists (lazy |
| 13 | + arbitrary values fragment design systems), (b) dynamic class-string concatenation |
| 14 | + like `bg-[${color}]` that never appears as a literal token and silently generates |
| 15 | + no CSS, (c) using v4 parens CSS-var syntax `bg-(--brand)` in a v3 project where it |
| 16 | + is parsed incorrectly, (d) using literal spaces inside `[...]` which terminates the |
| 17 | + class-name token, (e) mistaking a type-hint prefix as the property name itself |
| 18 | + (`text-[color:var(--c)]` is a `color` utility, not a property assignment), and (f) |
| 19 | + reaching for `text-[14.5px]` when `text-base/[1.4]` size-plus-line-height syntax |
| 20 | + fits, or `mt-17` in v4 where dynamic integer spacing avoids arbitrary syntax entirely. |
| 21 | + Covers the complete arbitrary-value system: value-arbitrary `bg-[#hex]`, `w-[calc()]`, |
| 22 | + `text-[Npx]`, `top-[-Npx]`, modifier-arbitrary `[&:nth-child(3)]:`, `[&>*]:`, `[&_p]:`, |
| 23 | + variant-arbitrary `data-[state=open]:`, `aria-[expanded=true]:`, `supports-[display:grid]:`, |
| 24 | + arbitrary-property `[mask-type:luminance]`, `[scroll-snap-stop:always]`, type-hint |
| 25 | + disambiguation `bg-[length:200px_100px]`, `bg-[image:url(...)]`, `text-[color:var(--c)]`, |
| 26 | + `text-[length:var(--c)]`, CSS-variable shorthand `bg-(--brand)` (v4 parens) vs |
| 27 | + `bg-[var(--brand)]` (v3 brackets), space encoding with `_`, comma encoding with `_` |
| 28 | + in v4 (`grid-cols-[max-content_1fr]`), source-detection rules (classes must be |
| 29 | + literal whole tokens), JIT performance model (v3 JIT vs v4 Oxide), and the v4 |
| 30 | + dynamic-integer fallback for spacing that obviates many arbitrary use cases. |
| 31 | + Keywords: arbitrary value, arbitrary class, brackets syntax, bracket notation, |
| 32 | + one-off color, bg-[hex], w-[calc], text-[px], custom value, custom selector, |
| 33 | + arbitrary variant, arbitrary modifier, arbitrary property, [mask-type], data-[state], |
| 34 | + aria-[expanded], type hint, length color image, bg-[length:], text-[color:], |
| 35 | + CSS variable Tailwind, bg-(--var), v4 parens syntax, bg-[var()], v3 brackets, |
| 36 | + underscore space, why does my arbitrary class not work, dynamic class not generated, |
| 37 | + JIT compiler, just in time, Oxide engine, performance, literal token, class detection, |
| 38 | + source scanning, mt-17, dynamic spacing. |
| 39 | +license: MIT |
| 40 | +compatibility: "Designed for Claude Code. Requires Tailwind CSS v3.4 or v4.0+." |
| 41 | +metadata: |
| 42 | + author: OpenAEC-Foundation |
| 43 | + version: "1.0" |
| 44 | +--- |
| 45 | + |
| 46 | +# Tailwind CSS Syntax: Arbitrary Values |
| 47 | + |
| 48 | +Arbitrary values are Tailwind's escape hatch: any utility can accept a one-off value via square-bracket syntax (or parens for CSS variables in v4). The syntax is identical in v3 and v4 ; the only difference is the v4 parens shorthand for CSS variables and the v4 Oxide engine that generates the CSS roughly 5x faster. Mastering arbitrary values means knowing WHEN to reach for them (rarely) and WHEN to use a theme token (almost always). |
| 49 | + |
| 50 | +## Quick Reference |
| 51 | + |
| 52 | +### Five arbitrary-value patterns |
| 53 | + |
| 54 | +| Pattern : example : compiles to | |
| 55 | +|--| |
| 56 | +| Value-arbitrary : `bg-[#1da1f2]` : `background-color: #1da1f2` | |
| 57 | +| Modifier-arbitrary : `[&:nth-child(3)]:underline` : custom selector wraps the utility | |
| 58 | +| Variant-arbitrary : `data-[state=open]:rotate-180` : `[data-state="open"] { rotate: 180deg }` | |
| 59 | +| Arbitrary property : `[mask-type:luminance]` : `mask-type: luminance` (no utility namespace at all) | |
| 60 | +| Type-hinted arbitrary : `bg-[length:200px_100px]` : disambiguates ambiguous values | |
| 61 | + |
| 62 | +### Top 3 most-used patterns |
| 63 | + |
| 64 | +```html |
| 65 | +<!-- 1. One-off color or size (works v3 + v4) --> |
| 66 | +<button class="bg-[#1da1f2] text-[14.5px] w-[calc(100%-2rem)]">Custom values</button> |
| 67 | + |
| 68 | +<!-- 2. CSS variable for runtime theming --> |
| 69 | +<!-- v4 parens shorthand --> |
| 70 | +<div class="bg-(--brand-bg) text-(--brand-fg)">v4</div> |
| 71 | +<!-- v3 brackets equivalent --> |
| 72 | +<div class="bg-[var(--brand-bg)] text-[var(--brand-fg)]">v3</div> |
| 73 | + |
| 74 | +<!-- 3. Arbitrary variant for one-off selector --> |
| 75 | +<ul class="[&_li]:list-disc [&>li:first-child]:font-bold"> |
| 76 | + <li>First (bold)</li> |
| 77 | + <li>Second</li> |
| 78 | +</ul> |
| 79 | +``` |
| 80 | + |
| 81 | +### Type hints when ambiguous |
| 82 | + |
| 83 | +| Hint | When needed | Example | |
| 84 | +|------|-------------|---------| |
| 85 | +| `length:` | distinguish from color/keyword on `text-*`, `bg-*` with CSS var | `text-[length:var(--my-size)]` | |
| 86 | +| `color:` | distinguish from length on `text-*`, `bg-*` with CSS var | `text-[color:var(--my-color)]` | |
| 87 | +| `image:` | distinguish from length/color on `bg-*` | `bg-[image:url('/hero.png')]` | |
| 88 | +| `family-name:` | distinguish on `font-*` with CSS var | `font-[family-name:var(--my-font)]` | |
| 89 | +| `position:` / `size:` | distinguish on `bg-*` | `bg-[position:50%_50%]`, `bg-[size:cover]` | |
| 90 | + |
| 91 | +## Decision Trees |
| 92 | + |
| 93 | +### Should I use arbitrary syntax at all? |
| 94 | + |
| 95 | +``` |
| 96 | +need a value outside the default theme? |
| 97 | +├── value used in 2+ places? |
| 98 | +│ └── ALWAYS extend the theme (v4 @theme or v3 tailwind.config.js) ; NEVER duplicate arbitrary values |
| 99 | +├── value used exactly once + unlikely to repeat? |
| 100 | +│ └── arbitrary value is fine |
| 101 | +├── value comes from runtime data (user picks color, server sends size)? |
| 102 | +│ └── ALWAYS use inline style `style={{...}}` for fully dynamic values, NEVER arbitrary |
| 103 | +├── value is an integer multiple of base spacing (Tailwind v4)? |
| 104 | +│ └── ALWAYS use dynamic integer utility `mt-17`, `gap-43` ; NEVER `mt-[68px]` when on v4 |
| 105 | +└── value is a CSS variable from your theme? |
| 106 | + ├── v4 : ALWAYS use parens shorthand `bg-(--brand)` (shorter) |
| 107 | + └── v3 : ALWAYS use brackets with `var()` `bg-[var(--brand)]` |
| 108 | +``` |
| 109 | + |
| 110 | +### Picking the right arbitrary-value flavour |
| 111 | + |
| 112 | +``` |
| 113 | +what am I customising? |
| 114 | +├── the value of an existing utility (color, size, calc()) : VALUE-arbitrary |
| 115 | +│ └── `bg-[#hex]`, `w-[calc()]`, `text-[Npx]`, `rotate-[7.5deg]` |
| 116 | +├── a CSS property Tailwind does not ship a utility for : ARBITRARY PROPERTY |
| 117 | +│ └── `[mask-type:luminance]`, `[scroll-snap-stop:always]`, `[contain:layout]` |
| 118 | +├── the selector the utility applies to : MODIFIER-arbitrary |
| 119 | +│ └── `[&_p]:text-red-500`, `[&>*]:p-4`, `[&.dragging]:cursor-grabbing` |
| 120 | +├── a custom attribute or media match : VARIANT-arbitrary |
| 121 | +│ └── `data-[state=open]:rotate-180`, `aria-[sort=ascending]:bg-blue-50`, `supports-[display:grid]:grid` |
| 122 | +└── a value that is ambiguous (string could parse multiple ways) : TYPE-HINTED arbitrary |
| 123 | + └── `bg-[length:200px_100px]`, `text-[color:var(--c)]`, `text-[length:var(--c)]` |
| 124 | +``` |
| 125 | + |
| 126 | +### Why is my arbitrary class not generating CSS? |
| 127 | + |
| 128 | +``` |
| 129 | +class is in the DOM but no CSS rule exists? |
| 130 | +├── built via string concatenation (`bg-[${color}]` or `bg-[#${hex}]`)? |
| 131 | +│ └── ROOT CAUSE : Tailwind scans source as literal tokens. Concatenation NEVER produces |
| 132 | +│ a complete literal class. Fix : static map or inline `style`. |
| 133 | +├── value contains an unencoded space (`grid-cols-[1fr 2fr]`)? |
| 134 | +│ └── ROOT CAUSE : space terminates the class-name token. Fix : `grid-cols-[1fr_2fr]`. |
| 135 | +├── value contains a CSS variable without proper syntax? |
| 136 | +│ ├── v3 : MUST be `bg-[var(--c)]` (full var() form inside brackets) |
| 137 | +│ └── v4 : either `bg-[var(--c)]` (still works) or `bg-(--c)` (shorthand) |
| 138 | +├── using v4 parens syntax `bg-(--c)` in v3 project? |
| 139 | +│ └── ROOT CAUSE : v3 does NOT parse parens-arbitrary. Fix : use `bg-[var(--c)]` in v3. |
| 140 | +├── ambiguous value (length vs color from CSS var)? |
| 141 | +│ └── ROOT CAUSE : Tailwind cannot disambiguate at compile time. Fix : type hint |
| 142 | +│ `text-[color:var(--c)]` or `text-(color:--c)` (v4). |
| 143 | +└── class only appears in production after a build? |
| 144 | + └── ROOT CAUSE : content scanning missed the source file. Fix : adjust content glob |
| 145 | + (v3) or add `@source "./path"` (v4). |
| 146 | +``` |
| 147 | + |
| 148 | +## Patterns |
| 149 | + |
| 150 | +### Pattern 1: Value-arbitrary |
| 151 | + |
| 152 | +The single most common form. Square brackets wrap a CSS value the utility would otherwise refuse. |
| 153 | + |
| 154 | +```html |
| 155 | +<!-- Colors --> |
| 156 | +<div class="bg-[#1da1f2]">Twitter blue</div> |
| 157 | +<div class="text-[rgb(20,30,40)]">Custom rgb</div> |
| 158 | +<div class="border-[oklch(0.65_0.196_254)]">Custom oklch</div> |
| 159 | + |
| 160 | +<!-- Sizes --> |
| 161 | +<div class="w-[calc(100%-2rem)]">Width via calc</div> |
| 162 | +<div class="h-[80vh]">Viewport-relative</div> |
| 163 | +<div class="text-[14.5px]">Sub-pixel font size</div> |
| 164 | +<div class="text-[14.5px]/[1.3]">Size + line-height in one</div> |
| 165 | +<div class="top-[-113px]">Negative top (auto-detected as negative)</div> |
| 166 | + |
| 167 | +<!-- Grids --> |
| 168 | +<div class="grid grid-cols-[24rem_2.5rem_minmax(0,1fr)]">Custom 3-column layout</div> |
| 169 | + |
| 170 | +<!-- Transforms / filters --> |
| 171 | +<div class="rotate-[7.5deg] blur-[2.5px] hue-rotate-[15deg]">Custom rotations</div> |
| 172 | +``` |
| 173 | + |
| 174 | +### Pattern 2: Modifier-arbitrary (custom selector) |
| 175 | + |
| 176 | +The `[&...]:utility` form gives the utility a custom selector. `&` is the styled element ; `_` represents a space in the selector. |
| 177 | + |
| 178 | +```html |
| 179 | +<!-- Direct child selector --> |
| 180 | +<ul class="[&>li]:list-disc [&>li]:ml-4"> |
| 181 | + <li>One</li> |
| 182 | + <li>Two</li> |
| 183 | +</ul> |
| 184 | + |
| 185 | +<!-- Descendant selector (use _ for the space) --> |
| 186 | +<article class="[&_p]:text-slate-600 [&_h2]:text-2xl [&_a]:text-blue-600"> |
| 187 | + <h2>Heading</h2> |
| 188 | + <p>Body</p> |
| 189 | + <a>Link</a> |
| 190 | +</article> |
| 191 | + |
| 192 | +<!-- Sibling and attribute selectors --> |
| 193 | +<input class="peer [&~p]:hidden focus:[&~p]:block" /> |
| 194 | + |
| 195 | +<!-- Pseudo-class shortcut for things not in the built-in list --> |
| 196 | +<li class="[&:nth-child(-n+3)]:font-bold">First 3 items bold</li> |
| 197 | +<li class="[&::-webkit-scrollbar]:hidden">Hide webkit scrollbar</li> |
| 198 | + |
| 199 | +<!-- Complex selector combining multiple parts --> |
| 200 | +<div class="[&>[data-active]+span]:text-blue-600"> |
| 201 | + <button data-active>...</button> |
| 202 | + <span>Sibling of active gets blue</span> |
| 203 | +</div> |
| 204 | +``` |
| 205 | + |
| 206 | +### Pattern 3: Variant-arbitrary (custom attribute or query) |
| 207 | + |
| 208 | +Built-in variants like `aria-checked:` and `data-active:` are shortcuts. Arbitrary variants accept any value or any selector. |
| 209 | + |
| 210 | +```html |
| 211 | +<!-- Custom data attribute value --> |
| 212 | +<div data-state="open" class="data-[state=open]:rotate-180 data-[state=closed]:rotate-0"> |
| 213 | + Chevron |
| 214 | +</div> |
| 215 | + |
| 216 | +<!-- Custom aria attribute value --> |
| 217 | +<th aria-sort="ascending" class="aria-[sort=ascending]:bg-blue-50"> |
| 218 | + Name |
| 219 | +</th> |
| 220 | + |
| 221 | +<!-- Browser feature detection --> |
| 222 | +<div class="grid-cols-3 grid supports-[display:grid]:gap-4 not-supports-[backdrop-filter]:bg-white"> |
| 223 | + Grid + fallback |
| 224 | +</div> |
| 225 | + |
| 226 | +<!-- Custom at-rule --> |
| 227 | +<div class="[@media(prefers-reduced-data:reduce)]:bg-none"> |
| 228 | + Saves bandwidth |
| 229 | +</div> |
| 230 | +<div class="[@container(width>=600px)]:grid"> |
| 231 | + Container-query escape hatch |
| 232 | +</div> |
| 233 | +``` |
| 234 | + |
| 235 | +### Pattern 4: Arbitrary property (no utility namespace) |
| 236 | + |
| 237 | +When Tailwind ships no utility for a CSS property at all, use `[property:value]`. |
| 238 | + |
| 239 | +```html |
| 240 | +<!-- CSS properties without Tailwind utilities --> |
| 241 | +<div class="[mask-type:luminance] hover:[mask-type:alpha]">SVG mask switching</div> |
| 242 | +<section class="[scroll-snap-stop:always] [scroll-snap-align:center]">Snap target</section> |
| 243 | +<div class="[contain:layout] [content-visibility:auto]">Optimisation hints</div> |
| 244 | +<div class="[view-transition-name:hero]">View Transitions API</div> |
| 245 | +<input class="[appearance:textfield] [-moz-appearance:textfield]">Strip number arrows</input> |
| 246 | +``` |
| 247 | + |
| 248 | +These compile to a direct CSS declaration with no class-name prefix : the `[mask-type:luminance]` utility produces `mask-type: luminance;`. |
| 249 | + |
| 250 | +### Pattern 5: Type hints (disambiguation) |
| 251 | + |
| 252 | +```html |
| 253 | +<!-- Ambiguous : 200px_100px could be size or position --> |
| 254 | +<div class="bg-[length:200px_100px]">Background-size</div> |
| 255 | +<div class="bg-[position:200px_100px]">Background-position</div> |
| 256 | + |
| 257 | +<!-- Ambiguous : a CSS var could be color or length --> |
| 258 | +<div class="text-[color:var(--accent)]">Color utility</div> |
| 259 | +<div class="text-[length:var(--size)]">Font-size utility</div> |
| 260 | + |
| 261 | +<!-- v4 parens shorthand with type hint --> |
| 262 | +<div class="text-(color:--accent)">v4 color</div> |
| 263 | +<div class="text-(length:--size)">v4 size</div> |
| 264 | + |
| 265 | +<!-- Always-needed type hints --> |
| 266 | +<div class="bg-[image:url('/hero.png')]">Force image interpretation</div> |
| 267 | +<div class="font-[family-name:var(--my-font)]">Force family-name interpretation</div> |
| 268 | +``` |
| 269 | + |
| 270 | +Without the hint, Tailwind would either guess wrong or refuse to compile. |
| 271 | + |
| 272 | +### Pattern 6: CSS variables (v3 vs v4) |
| 273 | + |
| 274 | +```html |
| 275 | +<!-- v3 : ALWAYS brackets with full var() form --> |
| 276 | +<div class="bg-[var(--brand-bg)] text-[var(--brand-fg)]">v3</div> |
| 277 | + |
| 278 | +<!-- v4 : EITHER brackets with var() (still works) OR parens shorthand --> |
| 279 | +<div class="bg-[var(--brand-bg)] text-[var(--brand-fg)]">v4 explicit</div> |
| 280 | +<div class="bg-(--brand-bg) text-(--brand-fg)">v4 shorthand (preferred)</div> |
| 281 | + |
| 282 | +<!-- v4 parens + type hint for ambiguous CSS vars --> |
| 283 | +<div class="text-(color:--accent)">v4 color from CSS var</div> |
| 284 | +<div class="text-(length:--size)">v4 size from CSS var</div> |
| 285 | +``` |
| 286 | + |
| 287 | +The v4 parens shorthand IS NOT available in v3. Mixing in a v3 project breaks silently. |
| 288 | + |
| 289 | +## Performance: JIT and Class Detection |
| 290 | + |
| 291 | +Both v3 and v4 use Just-In-Time compilation : Tailwind scans source files for class names and generates CSS only for the classes that appear. ALWAYS write class names as **complete literal tokens** in source. NEVER concatenate, interpolate, or build them dynamically at template-render time. |
| 292 | + |
| 293 | +```jsx |
| 294 | +// BROKEN : never generates CSS |
| 295 | +<div className={`bg-[${color}]`} /> |
| 296 | + |
| 297 | +// FIXED : the literal class string appears in source |
| 298 | +const STYLE_MAP = { primary: 'bg-[#1da1f2]', secondary: 'bg-[#888]' } |
| 299 | +<div className={STYLE_MAP[variant]} /> |
| 300 | + |
| 301 | +// FIXED for truly dynamic values : use inline style |
| 302 | +<div style={{ backgroundColor: color }} /> |
| 303 | +``` |
| 304 | + |
| 305 | +| Version | Engine | Full build | Incremental | |
| 306 | +|---------|--------|-----------|-------------| |
| 307 | +| v3.4 JIT | JavaScript-based JIT | baseline | baseline | |
| 308 | +| v4.0+ Oxide | Rust + Lightning CSS | ~5x faster | ~100x faster | |
| 309 | + |
| 310 | +The performance gain comes from Oxide ; the source-detection RULES are identical. |
| 311 | + |
| 312 | +## Reference Links |
| 313 | + |
| 314 | +- `references/methods.md` : complete arbitrary-value syntax reference (every flavour, every type hint, v3 vs v4 differences). |
| 315 | +- `references/examples.md` : worked examples for value/modifier/variant/property/type-hinted/CSS-variable forms across both versions. |
| 316 | +- `references/anti-patterns.md` : the dynamic-class-string trap, missing type hints, v4-parens-in-v3 mistakes, spaces inside brackets, reaching for arbitrary when a theme token or v4 dynamic integer fits. |
| 317 | + |
| 318 | +## Verified Sources |
| 319 | + |
| 320 | +- https://tailwindcss.com/docs/styling-with-utility-classes (arbitrary values, type hints, v4 parens shorthand) |
| 321 | +- https://tailwindcss.com/docs/adding-custom-styles (arbitrary values + JIT class-detection rules) |
| 322 | +- https://tailwindcss.com/blog/just-in-time-the-next-generation-of-tailwind-css (v3 JIT origin) |
| 323 | +- https://tailwindcss.com/blog/tailwindcss-v4 (v4 Oxide engine + parens shorthand) |
| 324 | +- https://tailwindcss.com/docs/detecting-classes-in-source-files (literal-token scan rule) |
| 325 | + |
| 326 | +Last verified : 2026-05-19 (Tailwind CSS v4.x current). |
0 commit comments