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.

Every example here was run
The CSS shown is copied from Tailwind CSS 4.3.3's own output. The pixel values in the tables were measured in headless Chrome (a 16px root font size, as in every browser by default). Where a width depends on the font, such as 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:

--spacing: 0.25rem;
.p-4 { padding: calc(var(--spacing) * 4); }

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:

NumberRemPixelsNumberRemPixels
000px102.5rem40px
px—1px123rem48px
0.50.125rem2px164rem64px
10.25rem4px205rem80px
1.50.375rem6px246rem96px
20.5rem8px328rem128px
30.75rem12px4010rem160px
41rem16px4812rem192px
51.25rem20px6416rem256px
61.5rem24px9624rem384px
82rem32px

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:

<div class="p-[13px]">…</div> <div class="p-(--gap)">…</div>
.p-\[13px\] { padding: 13px; } .p-\(--gap\) { padding: var(--gap); }

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:

ClassGenerated CSSSides
p-4padding: calc(var(--spacing) * 4)all four
px-4padding-inline: calc(var(--spacing) * 4)left and right (in left-to-right text)
py-2padding-block: calc(var(--spacing) * 2)top and bottom
pt-1 pr-2 pb-3 pl-4padding-top, padding-right, padding-bottom, padding-leftone physical side
ps-2 pe-3padding-inline-start, padding-inline-endthe 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 -endthe 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.

w-80 is 320px wide, padding included
Padding is added inside the width, not on top of it, because Preflight (chapter 1) sets 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-4 gap-x-2 gap-y-6
gap: calc(var(--spacing) * 4); column-gap: calc(var(--spacing) * 2); row-gap: calc(var(--spacing) * 6);

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:

space-y-2
:where(.space-y-2 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))); }

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:

ClassGenerated CSSMeasured
w-64width: calc(var(--spacing) * 64)256px
w-1/2, w-1/3width: calc(1 / 2 * 100%), calc(1 / 3 * 100%)300px and 200px
w-full, h-fullwidth: 100%, height: 100%of the parent
size-12width and height both calc(var(--spacing) * 12)48px × 48px
w-screen, h-screen100vw, 100vhthe whole viewport
h-dvh, h-svh, min-h-dvh100dvh, 100svh, min-height: 100dvhsee below
w-fit, w-min, w-maxfit-content, min-content, max-contentsized by the content
max-w-mdmax-width: var(--container-md) (28rem)448px
max-w-prosemax-width: 65ch561px here (varies with the font)
min-w-0min-width: 0pxlets a flex child shrink
w-px, h-px1pxhairlines

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:

ClassFont sizeLine heightClassFont sizeLine height
text-xs12px16pxtext-4xl36px40px
text-sm14px20pxtext-5xl48px48px
text-base16px24pxtext-6xl60px60px
text-lg18px28pxtext-7xl72px72px
text-xl20px28pxtext-8xl96px96px
text-2xl24px32pxtext-9xl128px128px
text-3xl30px36px

Each class sets two properties, and the line height is set so it can be overridden:

text-lg
font-size: var(--text-lg); line-height: var(--tw-leading, var(--text-lg--line-height));

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, and text-lg leading-tight gave 22.5px (18 × 1.25). You can also give a number from the spacing scale, such as leading-6, which is a fixed calc(var(--spacing) * 6) (24px), or an arbitrary value, leading-[1.3].
  • A slash after the size: text-lg/7 sets the size and the line height together. It compiled to font-size: var(--text-lg) and line-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

PropertyClassesValues (measured or from the generated CSS)
Weightfont-thin … font-blackthin 100, extralight 200, light 300, normal 400, medium 500, semibold 600, bold 700, extrabold 800, black 900
Familyfont-sans, font-serif, font-monosystem stacks: --font-sans starts with -apple-system, BlinkMacSystemFont, "Segoe UI"; --font-serif with ui-serif, Georgia; --font-mono with ui-monospace
Letter spacingtracking-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:

ClassCSS
text-left, text-center, text-right, text-justifytext-align: left | center | right | justify
uppercase, lowercase, capitalizetext-transform: …
italic, not-italicfont-style: italic | normal
underline, no-underlinetext-decoration-line: underline | none
text-balancetext-wrap: balance (lines of similar length; good for headings)
text-prettytext-wrap: pretty (avoids a single word on the last line)
text-nowrap, whitespace-nowraptext-wrap: nowrap, white-space: nowrap
break-wordsoverflow-wrap: break-word (breaks a long word, such as a URL, rather than overflowing)
truncateoverflow: hidden; text-overflow: ellipsis; white-space: nowrap
line-clamp-3overflow: 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:

<div class="flex w-48"> <div class="flex-1"> <p class="truncate">A very long product name that will not fit in the box</p> </div> </div>
container: 192px wrapper: 366px paragraph: 366px (no ellipsis; it overflows)

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:

ClassCSS
list-disc, list-decimal, list-nonelist-style-type: disc | decimal | none
list-inside, list-outsidelist-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:

<header class="max-w-prose"> <p class="text-xs font-semibold uppercase tracking-widest">Release notes</p> <h1 class="text-4xl font-bold tracking-tight leading-tight text-balance">Version 4.3 adds scrollbar utilities and more</h1> <p class="text-lg/7">A short introduction that says why the article is worth reading.</p> <p class="text-base leading-relaxed">Body copy sits at the default size with a looser line height…</p> </header>
eyebrow 12px / 16px weight 600 letter-spacing 1.2px uppercase title 36px / 45px weight 700 letter-spacing -0.9px text-wrap: balance lede 18px / 28px weight 400 body 16px / 26px weight 400 all four blocks 561px wide (max-w-prose, in this font)

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

Exercise 1

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.

📄 View solution
Exercise 2

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 solution
Exercise 3

In 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 solution

Chapter 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/m all; x left+right; y top+bottom; t r b l physical sides; s e start/end of the line; negative margin -mt-2; mx-auto centres a fixed-width block
  • w-80 includes padding (Preflight sets border-box)
  • gap-*, gap-x-*, gap-y-* on flex and grid; space-x/y-* and divide-x/y put 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; heights h-svh, h-dvh, min-h-dvh beat h-screen on phones
  • Type scale text-xs (12/16) … text-base (16/24) … text-9xl (128/128); text-lg/7 sets size and line height; leading-tight etc. 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 needs min-w-0), line-clamp-3, break-words
  • Preflight removes list markers: list-disc pl-5 or list-decimal pl-5
Coming next
Tailwind CSS Fundamentals 3 adds colour: the palette and its numbered shades, opacity modifiers, backgrounds and gradients, borders, rings and shadows.