Skip to main content
← Back to list

· Working

Tailwind for designers: reading AI-written code calmly

How to read a Tailwind class string in groups, a table of common classes and the CSS they produce, what hover:, md: and dark: mean, where tokens live in a v4 theme, and how to keep AI on your tokens. Examples come from this site's code.

A Tailwind class is close to one line of CSS written in shorthand: p-4 is 16px of padding, flex is display: flex, hover:bg-muted changes the background on hover. To read Tailwind that an AI wrote, sort the class string into groups (layout, spacing, type, colour, state) and check whether each group uses your system's tokens.

I'm Hai Le, a UI/UX designer with more than five years of experience who also writes React, Tailwind and Next.js. There are plenty of Tailwind cheat sheets for developers. This one is for the designer who has to approve a pull request full of class names. The site you're reading runs Tailwind CSS 4.3, so every example here is real code from its repository. If flexbox or the box model still feel shaky, how much HTML and CSS designers should learn is a better first stop.

How do you read a Tailwind class string?

Here is a filter tab from this site's Blog page, with the shared classes and the inactive-state classes combined:

className="type-body-sm flex h-9 items-center rounded-md px-3 whitespace-nowrap
  text-muted-foreground hover:bg-muted hover:text-foreground
  transition-colors duration-[var(--duration-fast)] ease-out-brand"

Thirteen classes look like noise. I read them by sorting into five groups, in the same order I'd inspect a Figma frame:

GroupClasses in the exampleMeaning
Layoutflex items-centerHorizontal auto layout, vertically centred
Size and spacingh-9 px-336px tall, 12px padding left and right
Typetype-body-sm whitespace-nowrapThe system's 16px text style, no wrapping
Colour, stroke, radiustext-muted-foreground rounded-mdSecond-tier grey text, radius from a token
State and motionhover:bg-muted hover:text-foreground transition-colors duration-[var(--duration-fast)] ease-out-brandOn hover, grey surface and darker text, over 120ms

According to the Tailwind docs, class order in your markup does not decide which class wins. Read by group, not in whatever order the AI happened to write.

Which CSS does each common class produce?

For spacing, Tailwind v4 multiplies the number in the class by a --spacing variable that defaults to 0.25rem (4px). So p-4 is 16px, gap-6 is 24px, h-9 is 36px. Multiply by four and you're done.

ClassGenerated CSSFigma equivalent
flexdisplay: flexHorizontal auto layout
flex-colflex-direction: columnVertical auto layout
items-centeralign-items: centerCentre on the cross axis
justify-betweenjustify-content: space-betweenSpacing set to "Auto"
gap-4gap: calc(var(--spacing) * 4), i.e. 16pxSpacing between items
p-4, px-3, py-2padding, padding-inline, padding-blockPadding
mt-2margin-top: 8pxNo auto layout equivalent; closest to a manual offset
w-fullwidth: 100%Fill container (horizontally)
shrink-0flex-shrink: 0Fixed, never squeezed
min-w-0min-width: 0pxLets the item shrink so text can truncate
grid grid-cols-3display: grid and grid-template-columns: repeat(3, minmax(0, 1fr))Three equal columns
max-w-3xl mx-automax-width: 48rem and margin-inline: autoA 768px content column, centred
relative, absolute inset-0position: relative, position: absolute; inset: 0Ignore auto layout, covering the parent
sticky top-0position: sticky; top: 0An element that sticks while scrolling in a prototype
overflow-hiddenoverflow: hiddenClip content
line-clamp-2Cuts text after two lines with an ellipsisTruncated text with a max line count
text-smfont-size: 0.875rem (14px), plus a line heightFont size
font-medium, font-semiboldfont-weight: 500, 600Weight
borderborder-width: 1px1px stroke
rounded-fullFully rounded cornersMaximum corner radius
hiddendisplay: noneHidden layer

I checked every row by compiling each class with this repo's Tailwind 4.3. rounded-md is missing on purpose: on this site it compiles to 12px, which is not the default. The token section below explains why.

What do hover:, md: and dark: mean?

The part before the colon is a variant, a condition for the class after it. Three families come up constantly.

States. hover: applies on mouse-over. Tailwind v4 wraps it in @media (hover: hover), so it only takes effect when the primary input can hover, like a mouse or trackpad. On a touch-only phone, hover: classes do not apply. focus-visible: applies when an element receives keyboard focus. aria-expanded: applies when aria-expanded="true", for example a menu button that is open. group-hover: lets a child change when you hover a parent marked with group.

Breakpoints. Tailwind is mobile-first. Unprefixed classes apply at every width, and md: means at md and wider.

PrefixApplies from
sm:40rem (640px)
md:48rem (768px)
lg:64rem (1024px)
xl:80rem (1280px)
2xl:96rem (1536px)

So w-28 sm:w-44 reads as: the thumbnail is 112px wide on phones and 176px from 640px up. For the reverse, use max-md: (below 768px).

Dark mode. By default dark: follows the operating system's light or dark setting. This site has a theme toggle, so it switches to a class with one line at the top of app/globals.css:

@custom-variant dark (&:is(.dark *));

Variants stack, as in dark:md:hover:bg-muted. Read left to right: dark mode, 768px and wider, on hover.

Where do tokens live in the theme?

Tailwind v4 no longer needs a tailwind.config.js. Tokens are declared in an @theme block in CSS, and each variable in a namespace generates classes: --color-* feeds bg-*, text-* and border-*; --radius-* feeds rounded-*; --breakpoint-* feeds the responsive prefixes.

This site wires its semantic tokens into Tailwind like this (shortened):

@theme inline {
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-primary-text: var(--primary-text);
  --radius-md: 12px;
}

That is why bg-muted compiles to background-color: var(--muted), and why every bg-muted changes together when dark mode redefines --muted. It is also why rounded-md is 12px here. What design tokens are covers the three token layers behind these variables.

Something I found while writing this: a comment in my own globals.css says you can't type bg-gray-600. What I meant was that the site's raw grey scale isn't exposed to Tailwind. The file still does a full @import "tailwindcss" and never resets the default palette, though, so bg-gray-600 compiles anyway, to Tailwind's default oklch(44.6% 0.03 256.802). That grey has a slight blue cast, which breaks this site's neutral-grey rule. The Tailwind docs show the fix: put --color-*: initial; in @theme to remove every default colour, then declare only your own.

A class that compiles is not necessarily a class from your system. To be sure, read the @theme block and check whether the default palette has been reset.

What do square brackets in a class mean?

bg-[#ff009d], p-[13px] and text-[22px] are arbitrary values: Tailwind generates a class for exactly the value you wrote. The syntax is valid. In a design system, it is the first thing I search for in a diff, because it usually means the AI skipped a token.

I keep one kind: brackets that wrap a system variable. This site's code uses duration-[var(--duration-fast)]. Tailwind v4 also has a shorter form for the same thing, duration-(--duration-fast). Both read from the token, so changing --duration-fast updates every use.

How do you get AI to use tokens instead of hard-coded values?

Four things I do, from lightest to strictest:

  1. Write the rule into the instruction file. This repo's AGENTS.md says not to hard-code colour or size in components, to use type-* classes for text, and to use shadcn-style colour tokens. Claude reads that file every session. What DESIGN.md is covers how to organise these files.
  2. Name the token in the prompt. Instead of "give the card a light grey hover", I write "hover uses bg-muted, no shadow". The AI guesses less when you hand it the name.
  3. Make the AI audit itself before handing over. A prompt I use often: "List every bracket class and every colour code in the files you just changed, and explain why each one isn't a token." Prompting Claude has more patterns.
  4. Lock it at the theme level. Reset the default palette with --color-*: initial, so classes outside your system compile to nothing.

If your tokens start in Figma, Figma MCP and Claude Code shows how to let the AI read variables directly instead of guessing from a screenshot.

Where to go next

If your team needs a designer who can read the Tailwind in your codebase, the about page has my contact details.

Sources

The Tailwind pages above are the v4 docs, checked on 4 October 2026. The CSS in the class table was compiled with this repo's Tailwind 4.3.3 the same day, including the bg-gray-600 case.

Frequently asked questions

Does the order of Tailwind classes matter?
Not for the result. Tailwind's docs say that when two classes set the same property, the one that comes later in the generated stylesheet wins, whatever order you wrote them in. So avoid putting conflicting classes such as flex and grid on one element.
Does md: mean medium screens only?
No. Tailwind is mobile-first, so md: means 48rem (768px) and wider. Classes without a prefix apply at every size. To target only screens narrower than md, use max-md:.
Is a bracket class like p-[13px] wrong?
It is valid syntax, called an arbitrary value. In a design system it usually signals a value outside your tokens. I keep brackets only when they wrap a system variable, such as duration-[var(--duration-fast)].
Do designers need to learn Tailwind configuration?
Only enough to find the tokens. In Tailwind v4 they live in an @theme block in the main CSS file. Reading that block tells you which classes are valid in your system.