· 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:
| Group | Classes in the example | Meaning |
|---|---|---|
| Layout | flex items-center | Horizontal auto layout, vertically centred |
| Size and spacing | h-9 px-3 | 36px tall, 12px padding left and right |
| Type | type-body-sm whitespace-nowrap | The system's 16px text style, no wrapping |
| Colour, stroke, radius | text-muted-foreground rounded-md | Second-tier grey text, radius from a token |
| State and motion | hover:bg-muted hover:text-foreground transition-colors duration-[var(--duration-fast)] ease-out-brand | On 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.
| Class | Generated CSS | Figma equivalent |
|---|---|---|
flex | display: flex | Horizontal auto layout |
flex-col | flex-direction: column | Vertical auto layout |
items-center | align-items: center | Centre on the cross axis |
justify-between | justify-content: space-between | Spacing set to "Auto" |
gap-4 | gap: calc(var(--spacing) * 4), i.e. 16px | Spacing between items |
p-4, px-3, py-2 | padding, padding-inline, padding-block | Padding |
mt-2 | margin-top: 8px | No auto layout equivalent; closest to a manual offset |
w-full | width: 100% | Fill container (horizontally) |
shrink-0 | flex-shrink: 0 | Fixed, never squeezed |
min-w-0 | min-width: 0px | Lets the item shrink so text can truncate |
grid grid-cols-3 | display: grid and grid-template-columns: repeat(3, minmax(0, 1fr)) | Three equal columns |
max-w-3xl mx-auto | max-width: 48rem and margin-inline: auto | A 768px content column, centred |
relative, absolute inset-0 | position: relative, position: absolute; inset: 0 | Ignore auto layout, covering the parent |
sticky top-0 | position: sticky; top: 0 | An element that sticks while scrolling in a prototype |
overflow-hidden | overflow: hidden | Clip content |
line-clamp-2 | Cuts text after two lines with an ellipsis | Truncated text with a max line count |
text-sm | font-size: 0.875rem (14px), plus a line height | Font size |
font-medium, font-semibold | font-weight: 500, 600 | Weight |
border | border-width: 1px | 1px stroke |
rounded-full | Fully rounded corners | Maximum corner radius |
hidden | display: none | Hidden 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.
| Prefix | Applies 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:
- Write the rule into the instruction file. This repo's
AGENTS.mdsays not to hard-code colour or size in components, to usetype-*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. - 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. - 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.
- 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
- Claude Code for designers: the workflow from a Figma design to a working page.
- How much HTML and CSS designers should learn: a minimum CSS roadmap and a Figma-to-CSS table.
- Car From Japan case study: redesigning an inquiry form where button labels and form states decided the outcome.
If your team needs a designer who can read the Tailwind in your codebase, the about page has my contact details.
Sources
- Tailwind CSS, Styling with utility classes: class order and conflicting utilities
- Tailwind CSS, Hover, focus, and other states:
hoverinside@media (hover: hover),group,aria-*, stacked variants - Tailwind CSS, Responsive design: default breakpoints, mobile-first,
max-* - Tailwind CSS, Dark mode:
@custom-variant dark - Tailwind CSS, Theme variables:
@theme, namespaces,--color-*: initial,@theme inline - Tailwind CSS, Adding custom styles: arbitrary values and the
(--var)shorthand - Tailwind CSS, Padding:
calc(var(--spacing) * n) - MDN, @media hover
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.
You might also like
What is Claude Code? Install it and run your first session, no coding needed
A designer's walkthrough for people who have never opened a terminal: what Claude Code is, what you need, installing on macOS and Windows, a safe first session, and fixes for the usual errors. Every command comes from Anthropic's docs.
Working· 2026-10-04
Claude Code for designers: turning a Figma design into a working page
How I use Claude Code as a designer to get from Figma frames to a real, maintained website: what to set up, how to write rules the agent follows, a six-step workflow, and the mistakes I made building this site.
Working· 2026-10-04
What is Claude? A guide for people who have never used AI
Claude is Anthropic's AI assistant. This guide covers what it can do, how the free and paid plans differ, how to run your first conversation, and where Claude tends to get things wrong. Every feature and price was checked against Anthropic's own pages.
Working· 2026-10-04