EmpireUI
Get Pro
← Blog7 min read#tailwind#arbitrary-values#css

Tailwind Arbitrary Values: When to Use Them and When to Stop

Tailwind arbitrary values are handy until they're not. Learn when [value] syntax helps, when it hurts, and how to keep your codebase maintainable.

Code editor showing CSS utility class definitions on a dark background

What Arbitrary Values Actually Are

Honestly, the square-bracket syntax in Tailwind is one of those features that looks like a hack but is actually a deliberate design decision. You write w-[347px] or bg-[rgba(255,255,255,0.15)] directly in your HTML, and Tailwind generates the corresponding CSS on the fly. No config edits. No new class names. Just write it and move on.

The feature landed in Tailwind v2.2 and got progressively more capable through v3. By Tailwind v4.0.2, arbitrary values work across almost every utility — spacing, colors, shadows, grid-template columns, animation durations, you name it. The compiler handles it at build time so there's zero runtime cost.

It's genuinely useful. But it can also quietly rot your codebase if you reach for it out of habit instead of intention. That's what this article is about.

The Legitimate Use Cases for Arbitrary Values

Some values just don't belong in your design system. A background image URL. A specific SVG path for clip-path. A grid-template-columns value like repeat(3, minmax(0, 1fr)) 2fr that's unique to one layout. These are perfect candidates for the bracket syntax because adding them to tailwind.config.js would be noise — you'd use them exactly once.

Third-party integrations are another solid case. When you're embedding a map widget that has a fixed height of 420px, or positioning an absolutely-placed element at top-[68px] because your navbar is exactly 68px tall, arbitrary values save you from polluting your theme with one-off tokens.

CSS variables work here too. Writing bg-[var(--brand-surface)] lets you hook into a token system that lives outside Tailwind entirely. This is especially handy when you're working with a design tool export or matching a legacy CSS file. If you're curious how this intersects with modern color approaches, the Tailwind OKLCH colors guide covers that territory in depth.

Where Arbitrary Values Start to Hurt

Here's the thing: the moment you write p-[13px] twice in different files, you've invented an implicit token. If that value ever changes — and spacing values always change — you're doing a global find-and-replace in raw HTML. That's the kind of maintenance work that arbitrary values were supposed to eliminate.

The readability cost compounds fast. Compare text-[clamp(1rem,2.5vw,1.5rem)] sitting inside a 200-character class string versus a single text-fluid-base utility that's defined once in your config. The former is technically correct but practically unreadable at 11pm during a production incident.

Another trap is color. It's tempting to write bg-[#1a2b3c] instead of spending ten minutes adding a proper palette. But colors leak. That same hex will appear in hover states, borders, shadows. Before long you have five slightly different grays scattered across your templates with no single source of truth. See how Tailwind component patterns handles this with proper token structure.

How to Write Arbitrary Values Without Making a Mess

The rule I actually follow: if you're writing the same arbitrary value more than once, move it to config. One time is fine. Twice means it's a token. The friction of adding it to tailwind.config.js is much lower than the friction of hunting it down six months later.

For values that genuinely must be arbitrary, keep them as close to self-documenting as possible. Use CSS custom properties rather than raw values where you can. border-[var(--card-border-width)] tells the next developer something. border-[1.5px] tells them nothing about why 1.5px and not 2px.

Also: group your arbitrary values. If an element needs w-[340px] h-[220px] rounded-[18px] because it's matching a specific card spec, that's coherent — the arbitrary values cluster around a single design decision. What's messy is when they're scattered randomly across a component with no obvious pattern.

Arbitrary Values vs Extending the Theme: A Practical Comparison

Let's look at a real scenario. You have a card component used in 12 places. It needs a very specific 8px gap between icon and label that doesn't exist in Tailwind's default scale.

// Option A — arbitrary value everywhere
<div className="flex gap-[8px] items-center">
  <Icon />
  <Label />
</div>

// Option B — extend the theme once
// tailwind.config.ts
export default {
  theme: {
    extend: {
      gap: {
        'icon': '8px',
      }
    }
  }
}

// Then in your component:
<div className="flex gap-icon items-center">
  <Icon />
  <Label />
</div>

Option A works. Option B scales. When design decides that 8px gap becomes 10px, you change one line in config and you're done. The arbitrary value version means grep, review, and manually update every instance — and hope you didn't miss the one in a string concatenation somewhere.

For one-off values — an intrinsic width on a specific modal, a clip-path value, that 68px navbar offset — Option A is exactly right. Don't over-engineer it. This is the judgement call that separates good Tailwind usage from compulsive config bloat.

Arbitrary Values with Tailwind v4 and CSS-First Config

Tailwind v4 changes the calculus here a bit. The new v4 features move most configuration into a CSS file using @theme blocks. That means adding a token is even less friction — it's just a CSS custom property declaration rather than a JavaScript object mutation.

In practice this means the bar for "just use arbitrary" should be slightly lower in v4 than in v3. You can define --spacing-icon: 8px in your CSS theme and reference it as gap-[var(--spacing-icon)] with very little overhead. Or you let the new engine pick it up automatically if you define it as part of the theme namespace.

What doesn't change: the discipline around colors and spacing values that appear repeatedly. Arbitrary values are still a liability when they proliferate. The v4 engine doesn't magically track duplicates for you.

One thing v4 does add that's relevant here: CSS variable-based arbitrary values get slightly better autocomplete support in the official VS Code extension. Small quality-of-life improvement, but it's there.

Glassmorphism and Arbitrary Values: A Common Pattern

Glassmorphism effects are one of the most common places developers reach for arbitrary values — and it's usually the right call. You can't express backdrop-blur(12px) with a default Tailwind value in older setups, and the RGBA background colors are almost always custom.

// Glassmorphism card with arbitrary values
<div
  className="
    rounded-2xl
    border border-white/10
    bg-[rgba(255,255,255,0.08)]
    backdrop-blur-[12px]
    shadow-[0_8px_32px_rgba(0,0,0,0.3)]
    p-6
  "
>
  {children}
</div>

These values are deeply contextual. The rgba(255,255,255,0.08) is calibrated against a specific dark background. The 32px shadow spread is chosen for a specific card elevation. Moving them to config would just be moving magic numbers to a different file — not adding semantic meaning. If you want to go deeper on these patterns, what is glassmorphism covers the theory, and tailwind glassmorphism advanced shows production-ready implementations.

The exception: if you're building a component library and these values need to be consistent across many components, they absolutely belong in design tokens. Empire UI handles this by defining them as CSS custom properties that arbitrary values can reference, giving you the best of both worlds.

A Decision Framework That Actually Works

Will this value appear more than once? If yes — config. If no — arbitrary is fine. That's genuinely most of the decision. Everything else is a refinement of that rule.

Does the value carry semantic meaning? A color called --surface-glass is more useful than rgba(255,255,255,0.08) everywhere. A spacing called --navbar-height is more useful than 68px. When a value has a name that explains its purpose, it belongs in your token system regardless of how many times it appears.

Is the value truly dynamic or computed? Things like viewport-relative sizing (w-[calc(100vw-2rem)]), or values derived from JavaScript state passed as inline styles, can't live in config anyway. Use arbitrary values there without guilt.

And honestly — stop worrying about it so much. The worst Tailwind codebases I've seen aren't the ones with too many arbitrary values. They're the ones where no one agreed on any conventions at all. Pick a rule, document it in your contributing guide, and move on. The goal is shipping good UI, not winning an argument about config files.

FAQ

Do arbitrary values affect Tailwind's bundle size?

Not meaningfully. Tailwind scans your source files at build time and generates only the CSS classes that appear in your code, whether those are predefined utilities or arbitrary values. An arbitrary value like w-[347px] generates one CSS rule, same as w-32. The concern about bundle size from arbitrary values is largely a myth.

Can I use arbitrary values with Tailwind v4's CSS-first configuration?

Yes, fully. Tailwind v4 supports arbitrary values exactly as before. You can also combine them with CSS custom properties defined in your @theme block — for example bg-[var(--color-brand-surface)] — which gives you the flexibility of arbitrary syntax with the maintainability of a token system.

What's the difference between arbitrary values and arbitrary properties in Tailwind?

Arbitrary values go inside an existing utility — like p-[13px] or text-[#ff6b6b]. Arbitrary properties let you write any CSS property entirely from scratch using the [property:value] syntax, for example [mask-image:linear-gradient(to_bottom,black,transparent)]. Both are valid, but arbitrary properties are even more of a last resort since they bypass Tailwind's utility structure entirely.

Should I use arbitrary values in a component library?

Sparingly. In a library, consistency matters more than in a single application because your components are used by people who can't easily trace where a value came from. Prefer extending the Tailwind theme or defining CSS custom properties, and document any necessary arbitrary values clearly. If you're publishing to npm, arbitrary values also require consumers to have your source files in their Tailwind content config — which can be a headache.

Why does Tailwind IntelliSense not autocomplete my arbitrary values?

Autocomplete only works for predefined utilities and theme extensions. Arbitrary values by definition aren't in any lookup table — you're writing raw CSS values, so IntelliSense has nothing to suggest. Some editors will at least give you color pickers for hex values in the bracket syntax, which helps. For anything more, you need to move the value into your theme config where the language server can index it.

Is there a linting rule to prevent overuse of arbitrary values?

Yes. The eslint-plugin-tailwindcss package has a no-arbitrary-value rule you can configure, though most teams set it to warn rather than error since arbitrary values are legitimate in many cases. A more nuanced approach is to allow arbitrary values for certain utility prefixes (like clip-path or grid-template) while warning on common ones like p, m, and text where theme tokens should cover most needs.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Tailwind Container Queries: Responsive Components Without Media QueriesNeobrutalism with Tailwind: offset-y Shadows, Bold Borders, Raw TypographyAdvanced CSS & JavaScript Patterns: Production-Grade Techniques 2026Container Style Queries: CSS Theming Without JavaScript