Arbitrary Values & Properties

Tailwind CSS · Lesson 21 of 27 7 min read Updated

The theme covers most of what you need, but sometimes a design calls for something exact: a logo that must sit exactly 117px from the top, a colour from a client's brand guide, a grid with an unusual column pattern, or a progress bar whose width comes from a database. For these cases Tailwind lets you write arbitrary values right in the class name.

In this lesson you will use square-bracket values, CSS variables with the parentheses shorthand, type hints for ambiguous values, arbitrary CSS properties, and arbitrary variants. You will also learn the safe way to pass values that are only known at run time.

Square brackets for one-off values

Almost every utility accepts a value in square brackets instead of a theme key:

HTML
<div class="top-[117px] w-[calc(100%-2rem)] bg-[#0f766e] text-[22px] grid-cols-[14rem_1fr_auto]">...</div>

Tailwind copies the value into the CSS as it is. w-[calc(100%-2rem)] becomes width: calc(100% - 2rem), and Tailwind even adds the spaces around the minus sign that calc() needs. Arbitrary values work with every variant, so md:top-[140px] and hover:bg-[#115e59] are fine.

Class names cannot contain spaces, because a space separates two classes. Write an underscore where the CSS needs a space: grid-cols-[14rem_1fr_auto] becomes grid-template-columns: 14rem 1fr auto. If you need a real underscore, escape it with a backslash, as in before:content-['file\_name']. Inside url() underscores are kept as they are, so image paths with underscores work.

CSS variables: the parentheses shorthand

To use a CSS custom property as the value, write it in round brackets:

HTML
<div class="bg-(--brand-color) p-(--card-padding) text-(--heading-size)">...</div>

bg-(--brand-color) is short for bg-[var(--brand-color)]. You can even add an opacity modifier: bg-(--brand-color)/50.

Some utilities do two jobs. text-* sets either a colour or a font size, so with a variable Tailwind cannot tell which you mean. Add a type hint before the value:

  • text-(length:--heading-size) sets the font size.
  • text-(color:--heading-colour) sets the colour.
  • The same works with brackets: text-[length:var(--size)].

Dynamic values: a CSS variable in the style attribute

Values that come from your data, such as a student's progress percentage, a product's brand colour or a chart bar's height, cannot be written in your source files, so Tailwind cannot generate classes for them. The clean solution is to set a CSS variable in a style attribute and read it with a class:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Progress bars</title>
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
</head>
<body class="flex max-w-md flex-col gap-4 p-6">
  <h2 class="font-semibold">Course progress</h2>
  <div style="--progress: 72%">
    <p class="flex justify-between text-sm"><span>Aarav</span><span>72%</span></p>
    <div class="mt-1 h-3 rounded-full bg-slate-200">
      <div class="h-3 w-(--progress) rounded-full bg-emerald-500"></div>
    </div>
  </div>
  <div style="--progress: 35%">
    <p class="flex justify-between text-sm"><span>Isha</span><span>35%</span></p>
    <div class="mt-1 h-3 rounded-full bg-slate-200">
      <div class="h-3 w-(--progress) rounded-full bg-amber-500"></div>
    </div>
  </div>
</body>
</html>

Every bar uses the same class, w-(--progress); only the variable in style changes. In a PHP template you would write style="--progress: <?= (int) $percent ?>%", and in React style={{ '--progress': percent + '%' }}. Never build the class name itself, such as w-[${percent}%], because Tailwind will not find it.

Arbitrary properties

If Tailwind has no utility for a CSS property at all, write the whole declaration in square brackets, again with underscores for spaces:

HTML
<p class="[text-wrap:stable] [font-variant-caps:small-caps] hover:[text-shadow:0_0_8px_white]">...</p>
<div class="[--card-padding:1.5rem] p-(--card-padding) md:[--card-padding:2rem]">...</div>

The second line is a handy pattern: an arbitrary property sets a CSS variable, a breakpoint changes it, and another utility uses it.

Arbitrary variants

Variants can be arbitrary too. Write a selector in square brackets, with & standing for the element itself:

ClassGenerated selector
[&:nth-child(3)]:font-bold.class:nth-child(3)
[&>li]:py-2.class > li
[&_a]:underline.class a (any descendant link)
[@supports(display:grid)]:gridan @supports rule

[&_a]:underline is especially useful when you cannot add classes to the inner elements, such as HTML that comes from a database:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Arbitrary variants</title>
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
</head>
<body class="p-6">
  <div class="max-w-prose text-slate-700 [&_a]:text-sky-700 [&_a]:underline [&_h2]:mt-6 [&_h2]:text-xl [&_h2]:font-bold [&_h2]:text-slate-900 [&_li]:ms-5 [&_li]:list-disc [&_p]:mt-3">
    <h2>Admission notice</h2>
    <p>Forms for the B.Sc. Computer Science course in Rohtak are now open.</p>
    <p>Bring these documents to the office:</p>
    <ul>
      <li>Class 12 marksheet</li>
      <li>Two passport-size photos</li>
    </ul>
    <p>Read the <a href="#">full prospectus</a> before you apply.</p>
  </div>
</body>
</html>

The HTML inside the <div> has no classes at all; every style comes from arbitrary variants on the parent. For long articles, the typography plugin's prose class (see Typography) is usually simpler, but this technique is great for small blocks.

Many common needs already have named variants, which read better than brackets: *: for direct children, has-[...] for :has(), supports-[display:grid]: for feature queries, data-[state=open]: and aria-[sort=ascending]: for attributes, and min-[400px]: for one-off breakpoints. Prefer those when they exist.

When to use arbitrary values

Arbitrary values are an escape hatch, not the main road. A good rule:

  • Used once, for something that really is unique? An arbitrary value is perfect.
  • Used twice or more, or part of your brand? Add it to the theme with @theme, give it a name, and use bg-brand instead of bg-[#0f766e] everywhere. See Customising with @theme.
  • Known only at run time? Set a CSS variable in style and read it with w-(--x) or similar.

Common mistakes

  • Spaces inside brackets. grid-cols-[1fr 2fr] is read as two broken classes. Use grid-cols-[1fr_2fr].
  • Building arbitrary classes from data. `w-[${n}px]` never appears in your files. Use a CSS variable in style instead.
  • The v3 variable syntax. bg-[--brand] is the old form; write bg-(--brand) in v4.
  • Arbitrary values everywhere. A page full of p-[13px] and text-[#333] loses the consistency that makes Tailwind useful. Prefer theme values.

Key points

  • Square brackets accept any CSS value: top-[117px], bg-[#0f766e], w-[calc(100%-2rem)].
  • Use underscores for spaces inside brackets, and \_ for a real underscore.
  • bg-(--brand) is the v4 shorthand for a CSS variable; add type hints like text-(length:--size) when needed.
  • For run-time values, set a CSS variable in style and read it with a class such as w-(--progress).
  • Arbitrary properties ([mask-type:luminance]) and arbitrary variants ([&_a]:underline) cover anything else.
  • Repeated arbitrary values belong in the theme.

Exercise

A marks report in Kochi shows each subject as a horizontal bar. Write the HTML for three subjects (Maths 88, Physics 74, Chemistry 91) where each bar's width comes from a --score CSS variable set in the style attribute. Make the bar colour come from a second variable, --bar, using the parentheses shorthand. Give the bar container a fixed height of exactly 14px with an arbitrary value.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Marks report</title>
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
</head>
<body class="flex max-w-md flex-col gap-3 p-6">
  <div style="--score: 88%; --bar: #0284c7">
    <p class="text-sm">Maths: 88</p>
    <div class="h-[14px] rounded bg-slate-200"><div class="h-full w-(--score) rounded bg-(--bar)"></div></div>
  </div>
  <div style="--score: 74%; --bar: #d97706">
    <p class="text-sm">Physics: 74</p>
    <div class="h-[14px] rounded bg-slate-200"><div class="h-full w-(--score) rounded bg-(--bar)"></div></div>
  </div>
  <div style="--score: 91%; --bar: #059669">
    <p class="text-sm">Chemistry: 91</p>
    <div class="h-[14px] rounded bg-slate-200"><div class="h-full w-(--score) rounded bg-(--bar)"></div></div>
  </div>
</body>
</html>

The classes are identical for every subject; only the style attribute changes, which is exactly how you would output the bars from a loop over database rows.

Quick Quiz

1. How do you write grid-template-columns: 1fr 2fr as a Tailwind class?

2. What is the Tailwind v4 way to use the CSS variable --brand as a background colour?

3. A progress value comes from your database. What is the safest way to set the bar width?

4. What does [&_p]:mt-3 on a <div> do?

Learn Tailwind CSS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call