Spacing and Type
Tailwind CSS Fundamentals
Chapter 2 · The Scale: Spacing, Sizing and Typography
Most of what you type in Tailwind is a number on a scale. p-4, gap-6, w-64 and text-lg are not arbitrary:
each number is a step, and the steps are the same everywhere, so a page built from them looks consistent without you measuring anything. This chapter covers the
scales for space, size and text. Colour is the next chapter.
65ch, I say so.
The Spacing Scale
Padding, margin, gap, width, height and several other things all share one scale, built from a single theme variable:
That is the whole idea: the number in a class is a multiple of 0.25rem, which is 4px at the default font size. p-4 is four of those,
16px. Measured values for the steps you will use most:
| Number | Rem | Pixels | Number | Rem | Pixels |
|---|---|---|---|---|---|
| 0 | 0 | 0px | 10 | 2.5rem | 40px |
| px | — | 1px | 12 | 3rem | 48px |
| 0.5 | 0.125rem | 2px | 16 | 4rem | 64px |
| 1 | 0.25rem | 4px | 20 | 5rem | 80px |
| 1.5 | 0.375rem | 6px | 24 | 6rem | 96px |
| 2 | 0.5rem | 8px | 32 | 8rem | 128px |
| 3 | 0.75rem | 12px | 40 | 10rem | 160px |
| 4 | 1rem | 16px | 48 | 12rem | 192px |
| 5 | 1.25rem | 20px | 64 | 16rem | 256px |
| 6 | 1.5rem | 24px | 96 | 24rem | 384px |
| 8 | 2rem | 32px |
To go from a design measurement to a class, divide the pixels by 4: 24px of padding is p-6, a 320px width is w-80. The table is a
sample, not a list of the only values. In v4 any number works, because the rule is just "that number times the spacing": I compiled p-13 and got
calc(var(--spacing) * 13), and p-3.5 gave calc(var(--spacing) * 3.5). Two special cases are not multiples:
p-px is exactly 1px, and p-0 is 0px.
When you need a value that is not on the scale, put it in square brackets, or point at a CSS variable with parentheses:
Use these sparingly: the whole point of the scale is that most values come from it. Chapter 10 returns to arbitrary values. And because the scale comes from one
variable, changing --spacing in your theme rescales every padding, margin, gap and size at once (chapter 9).
Padding and Margin
Padding is the space inside an element, margin is the space outside it. The letter picks which, and the suffix picks the side:
| Class | Generated CSS | Sides |
|---|---|---|
| p-4 | padding: calc(var(--spacing) * 4) | all four |
| px-4 | padding-inline: calc(var(--spacing) * 4) | left and right (in left-to-right text) |
| py-2 | padding-block: calc(var(--spacing) * 2) | top and bottom |
| pt-1 pr-2 pb-3 pl-4 | padding-top, padding-right, padding-bottom, padding-left | one physical side |
| ps-2 pe-3 | padding-inline-start, padding-inline-end | the start or end of a line, which flips in right-to-left languages |
| m-4, mx-2, my-2, mt-2 … | margin, margin-inline, margin-block, margin-top …; ms-2 and me-2 are margin-inline-start and -end | the same letters as padding |
Margin has two extras that padding does not. A negative margin puts a minus sign in front of the class: -mt-2 compiles to
margin-top: calc(var(--spacing) * -2), which pulls an element up by 8px (or -mx-1 for both sides). And auto
margins: mx-auto is margin-inline: auto, the classic way to centre a block that has a fixed width. I measured it: a w-64
box (256px) with mx-auto inside a 900px page got a 322px margin on the left, which is exactly (900 − 256) ÷ 2. mt-auto pushes an
element away from what is above it, which becomes useful once you meet flexbox.
box-sizing: border-box on everything. An element with
w-80 p-6 measures 320px across, and its content area is 320 − 24 − 24 = 272px. This is what you want almost every time, and one reason layouts in
Tailwind rarely need arithmetic.
Gap, Space-Between and Dividers
To put space between the children of a flex or grid container, use gap. It belongs to the container, so you set it once:
gap-x-* is the space between columns and gap-y-* the space between rows. Gap also adds space between the rows when items wrap onto
several lines, which margins on the children cannot do cleanly. There is an older way, still common in Tailwind code: space-x-4 and
space-y-2. They work on any container, including a plain block, and this is what they generate:
So space-y-2 puts a bottom margin on every child except the last, rather than a gap on the parent. Use gap when the container is flex
or grid (chapter 4), and space-y-* for a simple stack of blocks. The divide-y and divide-x classes work the same way with
borders: a 1px border between children, on every child except the last.
Width and Height
Widths and heights use the same scale, plus fractions, keywords and a few viewport units. The measured results come from a 600px-wide parent:
| Class | Generated CSS | Measured |
|---|---|---|
| w-64 | width: calc(var(--spacing) * 64) | 256px |
| w-1/2, w-1/3 | width: calc(1 / 2 * 100%), calc(1 / 3 * 100%) | 300px and 200px |
| w-full, h-full | width: 100%, height: 100% | of the parent |
| size-12 | width and height both calc(var(--spacing) * 12) | 48px × 48px |
| w-screen, h-screen | 100vw, 100vh | the whole viewport |
| h-dvh, h-svh, min-h-dvh | 100dvh, 100svh, min-height: 100dvh | see below |
| w-fit, w-min, w-max | fit-content, min-content, max-content | sized by the content |
| max-w-md | max-width: var(--container-md) (28rem) | 448px |
| max-w-prose | max-width: 65ch | 561px here (varies with the font) |
| min-w-0 | min-width: 0px | lets a flex child shrink |
| w-px, h-px | 1px | hairlines |
A few points. h-screen is 100vh, which on phones is the height with the browser toolbar hidden, so a "full-height" section can
run behind the toolbar. The newer units fix that: h-svh is the small viewport (toolbars showing), h-lvh the large one, and
h-dvh the dynamic one that follows the toolbar as it moves. For a full-height page, min-h-dvh is usually what you want. The named
maximum widths come from the container scale (max-w-sm is 24rem, max-w-xl 36rem, max-w-3xl 48rem,
max-w-7xl 80rem), which is the reusable way to cap a column. And max-w-prose caps a column at about 65 characters, a comfortable line
length for reading: ch is the width of the digit 0 in the current font, so the pixel width depends on your font.
Font Size and Line Height
The type scale is a set of named sizes, each with a matching line height. Measured, with each class applied to a paragraph:
| Class | Font size | Line height | Class | Font size | Line height |
|---|---|---|---|---|---|
| text-xs | 12px | 16px | text-4xl | 36px | 40px |
| text-sm | 14px | 20px | text-5xl | 48px | 48px |
| text-base | 16px | 24px | text-6xl | 60px | 60px |
| text-lg | 18px | 28px | text-7xl | 72px | 72px |
| text-xl | 20px | 28px | text-8xl | 96px | 96px |
| text-2xl | 24px | 32px | text-9xl | 128px | 128px |
| text-3xl | 30px | 36px |
Each class sets two properties, and the line height is set so it can be overridden:
The var(--tw-leading, …) means "use the line height someone chose with a leading-* class, and fall back to the size's default".
That is why text-lg leading-tight works in either order: the leading class always wins. There are two ways to change the line height:
- A leading class:
leading-none(1),leading-tight(1.25),leading-snug(1.375),leading-normal(1.5),leading-relaxed(1.625),leading-loose(2). These are multiples of the font size: on a 16px paragraph I measured 16, 20, 22, 24, 26 and 32px, andtext-lg leading-tightgave 22.5px (18 × 1.25). You can also give a number from the spacing scale, such asleading-6, which is a fixedcalc(var(--spacing) * 6)(24px), or an arbitrary value,leading-[1.3]. - A slash after the size:
text-lg/7sets the size and the line height together. It compiled tofont-size: var(--text-lg)andline-height: calc(var(--spacing) * 7), and measured 18px with a 28px line. It is shorter than two classes and keeps them together.
For a size that is not on the scale, text-[17px] sets just font-size: 17px (no line height).
Weight, Family and Letter Spacing
| Property | Classes | Values (measured or from the generated CSS) |
|---|---|---|
| Weight | font-thin … font-black | thin 100, extralight 200, light 300, normal 400, medium 500, semibold 600, bold 700, extrabold 800, black 900 |
| Family | font-sans, font-serif, font-mono | system stacks: --font-sans starts with -apple-system, BlinkMacSystemFont, "Segoe UI"; --font-serif with ui-serif, Georgia; --font-mono with ui-monospace |
| Letter spacing | tracking-tighter … tracking-widest | −0.05em, −0.025em, 0, 0.025em, 0.05em, 0.1em (on 16px text: −0.8px, −0.4px, normal, 0.4px, 0.8px, 1.6px) |
The weights only change anything if the font has those weights, so font-thin on a font with no thin face just shows the nearest one. The tracking
classes are in em, so they scale with the text: a large heading gets a larger adjustment than small text for the same class. Large headings usually
look better slightly tighter (tracking-tight) and small uppercase labels slightly looser (tracking-wide or
tracking-widest). Chapter 9 shows how to add your own font families.
Text Layout: Alignment, Wrapping and Truncation
A group of small classes controls how text sits and breaks. Everything here is copied from the generated CSS:
| Class | CSS |
|---|---|
| text-left, text-center, text-right, text-justify | text-align: left | center | right | justify |
| uppercase, lowercase, capitalize | text-transform: … |
| italic, not-italic | font-style: italic | normal |
| underline, no-underline | text-decoration-line: underline | none |
| text-balance | text-wrap: balance (lines of similar length; good for headings) |
| text-pretty | text-wrap: pretty (avoids a single word on the last line) |
| text-nowrap, whitespace-nowrap | text-wrap: nowrap, white-space: nowrap |
| break-words | overflow-wrap: break-word (breaks a long word, such as a URL, rather than overflowing) |
| truncate | overflow: hidden; text-overflow: ellipsis; white-space: nowrap |
| line-clamp-3 | overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3 |
The Tailwind compatibility page lists text-wrap: balance among the newer features with limited browser support, so treat text-balance as an
enhancement: in a browser that lacks it the heading still displays, just without balanced lines.
Truncating to one line is truncate, and it needs two things: a width that is smaller than the text, and (inside flexbox) a
parent that is allowed to shrink. I measured the failure. A 192px-wide flex row holds a wrapper with a long title in a truncate paragraph:
The wrapper refused to shrink below the width of its text, so the paragraph inside was never narrower than its text and there was nothing to truncate. Adding
min-w-0 to the wrapper (class="min-w-0 flex-1") gave a 192px wrapper and a 192px paragraph, with the ellipsis showing. Putting
truncate directly on the flex item also worked (192px). The rule to remember: a flex child's minimum width is its content, unless you say
min-w-0, so any wrapper between the flex container and the truncating text needs it.
Clamping to several lines is line-clamp-3. A 192px paragraph of placeholder text was 240px high (ten lines of 24px); with
line-clamp-3 it was 72px, exactly three lines, with an ellipsis at the end of the third. line-clamp-none undoes it (for example at a
larger breakpoint, once chapter 7 covers those).
Lists
Preflight removed the bullets and numbers (chapter 1, Exercise 3), so you add them back with a class:
| Class | CSS |
|---|---|
| list-disc, list-decimal, list-none | list-style-type: disc | decimal | none |
| list-inside, list-outside | list-style-position: inside | outside |
A typical list is list-disc pl-5: the bullets, and padding on the left so the bullets (which sit outside the text, list-outside being
the default) are not cut off by the edge of the container. Use space-y-1 or gap (on a flex column) for the space between items.
Putting It Together
Here is an article header built only from classes in this chapter. I measured every value in headless Chrome so you can see what each class contributed:
Look at the title: text-4xl alone would give a 40px line height, but leading-tight replaced it with 1.25 × 36 = 45px, as the
--tw-leading variable promised. A small uppercase label, a large tight heading, a slightly larger lede and a looser body: four levels of hierarchy
from thirteen classes, all on the scale.
Hands-On Exercises
A designer specifies a card: 320px wide, 24px of padding, a 32px gap below it, centred horizontally in its parent, with its three children stacked and 12px apart. Write the classes (start from flex flex-col, which chapter 4 explains), build the page, and confirm every measurement in the browser's computed values, including the width of the content area inside the padding.
Style an article header to a spec: eyebrow 12px, uppercase, 600 weight, letter-spacing 0.1em; title 36px, bold, 1.25 line height, letter-spacing -0.025em, balanced lines; lede 18px on a 28px line; body 16px on a 26px line; every block no wider than 65 characters. Pick the classes, build, and check each value. Then find out which single class you could swap to make the lede's 28px line come from the spacing scale instead of from the type scale's default.
📄 View solutionIn a 192px-wide row, show a product title that truncates with an ellipsis next to a price that never shrinks, and below it a description clamped to three lines. The title sits in a wrapper inside a flex row. Make it work, then remove the one class that makes the truncation fail and measure the damage.
📄 View solutionChapter 2 Quick Reference
- Spacing scale: the number is a multiple of
--spacing(0.25rem= 4px):p-4= 16px; any number works (p-13,p-3.5);p-px= 1px; off-scale:p-[13px],p-(--var) - Sides:
p/mall;xleft+right;ytop+bottom;t r b lphysical sides;s estart/end of the line; negative margin-mt-2;mx-autocentres a fixed-width block w-80includes padding (Preflight setsborder-box)gap-*,gap-x-*,gap-y-*on flex and grid;space-x/y-*anddivide-x/yput margins and borders on the children (all but the last)- Sizes:
w-64,w-1/2,w-full,size-12,max-w-md(--container-md),max-w-prose(65ch),w-fit; heightsh-svh,h-dvh,min-h-dvhbeath-screenon phones - Type scale
text-xs(12/16) …text-base(16/24) …text-9xl(128/128);text-lg/7sets size and line height;leading-tightetc. override the line height in any order font-thin…font-black(100–900),font-sans/serif/mono,tracking-tighter…tracking-widest(em)text-balance(headings),text-pretty(paragraphs),truncate(one line, needs a width; a wrapper in a flex row needsmin-w-0),line-clamp-3,break-words- Preflight removes list markers:
list-disc pl-5orlist-decimal pl-5