Skip to content
C Codeloom
Tailwind

Tailwind Arbitrary Values and the JIT Engine

How Tailwind's JIT engine generates classes on demand, when arbitrary values are the right tool, and how to keep your design system tidy.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What the JIT engine actually does
  • How arbitrary value classes are generated
  • When to reach for [200px] vs adding a token
  • How to safelist values that are built dynamically
  • How to keep arbitrary values from sprawling

Prerequisites

  • A project using Tailwind CSS

What and Why

Tailwind used to ship every possible class in a huge stylesheet that you then purged. The JIT (Just-In-Time) engine flipped the model: it scans your files, sees which classes you actually use, and generates only those at build time. The compile is fast, the output is small, and you get a new superpower along the way: arbitrary values.

Arbitrary values let you write one-off classes like mt-[17px], bg-[#1d4ed8], or grid-cols-[200px_1fr_120px] without editing the config. They are perfect for the 5% of cases your design system has not covered.

Mental Model

The JIT engine works in three steps: scan your content files for class names, parse each candidate, and emit CSS for the ones it recognizes.

src/**/*.{html,tsx,astro}
      |
      v  (regex scan)
candidate class names
      |
      v  (parse)
matches design tokens? --> emit utility rule
matches arbitrary syntax? --> emit one-off rule
      |
      v
final stylesheet (only used classes)
From source to stylesheet

A regular utility like text-blue-600 comes from your theme. An arbitrary value like text-[#1d4ed8] is parsed inline and the rule is generated for that file only.

The square bracket syntax accepts almost any CSS value: lengths, colors, calc expressions, even custom properties.

Hands-on Example

Add a one-off color and size without touching the config:

<div class="bg-[#0f766e] text-[#ecfeff] p-[18px] rounded-[14px]">
  Custom card
</div>

Use arithmetic and CSS functions:

<div class="w-[calc(100%-2rem)] h-[clamp(120px,20vh,240px)]"></div>

Reference a CSS variable defined elsewhere:

<div class="text-[--brand] border-[length:var(--ring)]">
  Themed text
</div>

Notice the [length:...] type hint. When the value’s CSS property is ambiguous (a number could be a color or a length), prefix with the type so Tailwind generates the right rule.

Complex grids feel natural:

<div class="grid grid-cols-[200px_1fr_minmax(0,320px)] gap-4">
  <aside></aside>
  <main></main>
  <aside></aside>
</div>

Compose with modifiers as usual:

<button class="hover:bg-[#1d4ed8] focus:ring-[3px] focus:ring-[--ring]">
  Save
</button>

Arbitrary properties (entire CSS declarations) work too:

<div class="[mask-image:linear-gradient(black,transparent)]"></div>

Use sparingly. They are great for niche CSS features that have no utility yet.

When a value will appear more than two or three times, move it into the theme:

// tailwind.config.js
export default {
  theme: {
    extend: {
      colors: { brand: '#0f766e' },
      spacing: { '4.5': '1.125rem' },
      borderRadius: { lg2: '14px' },
    },
  },
};

Now bg-brand, p-4.5, and rounded-lg2 are first-class. Reaching for tokens beats sprinkling magic numbers through templates.

Common Pitfalls

Dynamic class strings the scanner cannot see:

<div className={`bg-${color}-500`} />

The JIT engine looks at the literal text of your source. Concatenated class names never appear as full strings, so the rule is never generated and your color is missing in production. Fix it by mapping conditions to whole class names:

const tone = {
  success: 'bg-green-500',
  warning: 'bg-amber-500',
  danger:  'bg-red-500',
}[status];

<div className={tone} />

Or, as a last resort, add the values to the safelist in your config.

Overusing arbitrary values. A template covered in mt-[7px], p-[13px], text-[#3a3a3a] is a design system slowly dying. Each one-off should make you ask: should this be a token?

Forgetting type hints. bg-[123] is ambiguous; bg-[123px] or bg-[#123456] is not. When in doubt, write valid CSS.

Spaces inside arbitrary values. Use underscores: grid-cols-[200px_1fr_120px], not spaces. Tailwind converts underscores to spaces in the emitted CSS. To include a literal underscore, escape it.

Confusing arbitrary values with arbitrary variants. The variant syntax is [&:hover]:bg-red-500 (note the &) and lets you write any selector inline. Powerful, but easy to abuse.

Practical Tips

Treat arbitrary values as drafts. The first time you write mt-[18px], ship it. The second time, consider adding spacing.4.5. The third time, definitely add a token.

Use the theme() helper inside arbitrary values to stay anchored: w-[calc(theme(spacing.8)+2rem)].

For one-off media queries, the arbitrary variant syntax shines: [@media(min-width:900px)]:flex. Better, define the breakpoint in screens if it repeats.

Run npx tailwindcss --watch while iterating; the JIT compile is so fast you can experiment freely without a heavy build step.

Use Prettier’s Tailwind plugin to sort classes consistently. It makes long class strings scannable and reduces merge conflicts.

Wrap-up

The JIT engine made Tailwind feel like writing CSS without leaving HTML, and arbitrary values are the escape hatch that keeps you productive at the edges. Use them for one-offs, lift repeated values into the theme, and write your class strings as literals the scanner can see. The result is a tiny stylesheet, a tidy design system, and almost no friction between idea and pixel.