Tailwind CSS in Production · Theming and Consistency

Fourteen Shades of Blue

A year of arbitrary values left the product with fourteen different blues, five border radii and spacing that never quite lined up.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

Example team: five teams build features on one product. When a design calls for "our blue", developers copy the hex code from the design file into arbitrary values: bg-[#1d4ed8], text-[#1e40af], bg-[#2563eb]…

What you see in production

A rebrand needs a new primary colour. Search finds fourteen slightly different blues and dozens of one-off sizes. The rebrand estimate grows from a day to a month.

The naive code

<button class="bg-[#2563eb] rounded-[7px] px-[18px] py-[9px]">Save</button>
<a class="text-[#1d4ed8] hover:text-[#1e3a8a]">Details</a>

Reproduce it

Counting arbitrary values in a codebase shows the drift:

Arbitrary values found in templatesTry it yourself
import re
from collections import Counter

templates = """
<button class="bg-[#2563eb] rounded-[7px] px-[18px]">Save</button>
<a class="text-[#1d4ed8] hover:text-[#1e3a8a]">Details</a>
<div class="border-[#2563ea] rounded-[8px]">Card</div>
<button class="bg-[#2564eb] rounded-[6px] px-[16px]">Send</button>
<span class="text-[#1d4ed8]">Note</span>
"""
values = Counter(re.findall(r"\[(#[0-9a-f]{6}|\d+px)\]", templates))
colors = {v: n for v, n in values.items() if v.startswith("#")}
sizes = {v: n for v, n in values.items() if v.endswith("px")}
print(f"{len(colors)} different colours:", colors)
print(f"{len(sizes)} different pixel sizes:", sizes)

Output

5 different colours: {'#2563eb': 1, '#1d4ed8': 2, '#1e3a8a': 1, '#2563ea': 1, '#2564eb': 1}
5 different pixel sizes: {'7px': 1, '18px': 1, '8px': 1, '6px': 1, '16px': 1}

Root cause

Arbitrary values bypass the design system. Each one is a decision made in isolation, so near-duplicates accumulate and nothing can be changed centrally.

The fix

Define tokens once in @theme, named by purpose, and use them everywhere:

Semantic tokens in @themeTry it yourself
<style type="text/tailwindcss">
  @theme {
    --color-primary: oklch(0.55 0.22 263);
    --color-primary-hover: oklch(0.48 0.22 263);
    --radius-control: 0.5rem;
  }
</style>
<div class="flex gap-3 p-6">
  <button class="rounded-control bg-primary px-4 py-2 text-white hover:bg-primary-hover">Save</button>
  <a href="#" class="rounded-control px-4 py-2 text-primary hover:text-primary-hover">Details</a>
</div>

Add a lint rule or CI check that flags new arbitrary colours, and review exceptions.

Trade-offs

  • Tokens take design and engineering agreement up front, but every later change becomes one line.
  • Arbitrary values are still fine for genuinely one-off layout values (grid-cols-[14rem_1fr]).

Detect it in production

  • A periodic report of arbitrary values per category (colours, radii, spacing).
  • Visual audits of buttons and links across pages.

Key points

  • Arbitrary values are an escape hatch, not a design system.
  • Put brand decisions in @theme tokens with semantic names.
  • Measure drift and stop it in CI.

Practice questions

How do you keep colours and spacing consistent across a large Tailwind codebase?

Answer

Define design tokens in @theme (semantic colours, radii, fonts), use only those utilities, restrict arbitrary values with lint rules or CI checks, and share components for common UI so styles live in one place.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?