Flex and Grid
Tailwind CSS Fundamentals
Chapter 4 · Layout: Display, Flexbox and Grid
Layout is where Tailwind earns its keep. Flexbox and grid are CSS features, and the classes are thin names for their properties, so this chapter is as much about
knowing what flexbox and grid do as about knowing the class names. For each family I give the class, the CSS it generates, and numbers measured in a real browser,
because the clearest way to understand justify-between or flex-auto is to see where the boxes land. Positioning and overflow are chapter 5.
Display
display decides how an element lays out its children and how it sits among its neighbours. The classes use the CSS names:
| Class | CSS | What it does |
|---|---|---|
| block, inline, inline-block | display: block | inline | inline-block | the normal flow values |
| flex, inline-flex | display: flex | inline-flex | children become flex items (full-width box or inline box) |
| grid, inline-grid | display: grid | inline-grid | children become grid items |
| hidden | display: none | removed from the layout entirely |
| contents | display: contents | the element's own box disappears; its children behave as if they were its parent's children |
| flow-root | display: flow-root | a box that contains its floats and stops margins collapsing through it |
| invisible | visibility: hidden | not painted, but still takes up its space |
The last two rows are easy to confuse, so I measured them. Three 40px-high blocks, the middle one hidden: with hidden the third block started at 40px
(the middle one took no space); with invisible it started at 80px (the middle one was a 40px gap). Use hidden to take something out of the
page and invisible to keep the layout stable while hiding it.
Flexbox: One Row or Column
Put flex on a container and its children are laid out along one line, the main axis; the other direction is the
cross axis. Everything below is set on the container, except where noted.
- Direction.
flex-row(the default,flex-direction: row),flex-col(column),flex-row-reverse,flex-col-reverse. The main axis follows the direction. - Wrapping.
flex-wraplets items move onto a second line;flex-nowrap(the default) squeezes them into one;flex-wrap-reversewraps upwards. Combine withgap-4(chapter 2).
Along the main axis, justify-* spreads the free space. I measured three 100px-wide items in a 600px container (so 300px free), and
the left edge of each item:
| Class | CSS | Left edges of items 1, 2, 3 |
|---|---|---|
| justify-start | justify-content: flex-start | 0, 100, 200 |
| justify-center | center | 150, 250, 350 |
| justify-end | flex-end | 300, 400, 500 |
| justify-between | space-between | 0, 250, 500 |
| justify-around | space-around | 50, 250, 450 |
| justify-evenly | space-evenly | 75, 250, 425 |
justify-between puts the first item at the start and the last at the end with equal gaps between (150px each here). justify-around gives each
item the same space on both sides, so the outer gaps are half the inner ones (50 and 100). justify-evenly makes all four gaps equal (75).
Across the cross axis, items-* aligns the items in the container; self-* overrides it for one item. I measured three
items of different heights (20px, 40px and 60px) in a 96px-high row; each cell is the item's top offset:
| Class | CSS | Top offsets (item 1, 2, 3) |
|---|---|---|
| items-start | align-items: flex-start | 0, 0, 0 |
| items-center | center | 38, 28, 18 |
| items-end | flex-end | 76, 56, 36 |
| items-stretch | stretch (the default) | 0, 0, 0, and items that have no height of their own grow to 96px |
| items-baseline | baseline | 8, 8, 0 (the items' text baselines line up; the third item has bigger text) |
Stretch is why flex children in a row end up the same height without you asking, as in a row of cards. An item with its own height (the 20px one) keeps it; the
others stretched to 96px. There is also content-* (align-content) for the spacing of wrapped lines.
How Items Share Space: flex-1, flex-auto, grow, shrink
These classes go on the items. Three items in a 600px row with widths w-20, w-32 and w-48 (80, 128 and 192px,
400px in total, 200px free):
| Class on each item | CSS | Measured widths | What happened |
|---|---|---|---|
| flex-1 | flex: 1 | 200, 200, 200 | starts from 0 and shares everything equally, ignoring their widths |
| flex-auto | flex: auto | 146.66, 194.67, 258.67 | starts from each item's own size, then shares the 200px of free space equally (+66.67 each) |
| grow | flex-grow: 1 | 146.66, 194.67, 258.67 | same as flex-auto here |
| flex-none | flex: none | 80, 128, 192 | fixed: no growing and no shrinking |
| flex-initial | flex: 0 auto | 80, 128, 192 | can shrink but never grows |
So flex-1 is for "equal columns" and for "take whatever is left" (a search box between a logo and a button); flex-auto or
grow is for "let content decide first, then fill". shrink-0 stops an item being squeezed, which you saw on the price in chapter 2, and
grow-0 and shrink are the opposite switches. basis-1/2 and basis-64 set the starting size (flex-basis)
as a fraction or a spacing step. And remember min-w-0 from chapter 2 for flex items that contain text which should truncate.
order-* changes the visual order without moving the HTML: order-1 is order: 1, order-first is
order: -9999, order-last is order: 9999, and order-none is 0. Keep the HTML in a sensible reading order
first: screen readers and the Tab key follow the HTML, not the visual order.
Grid: Rows and Columns Together
Use grid when you care about both directions at once. grid makes the container, and grid-cols-3 defines three equal columns:
The 600px is split as (600 − 2 × 16) ÷ 3 = 189.33px per column, with 16px gaps (gap-4). Item 2 spans two columns: 2 × 189.33 + 16 = 394.67px, and
item 3 wraps to the next row. The related classes:
grid-cols-1…grid-cols-12(repeat(N, minmax(0, 1fr))),grid-cols-none, andgrid-cols-subgrid(grid-template-columns: subgrid, so a nested grid lines up with its parent's columns; I compiled it but did not test it in a browser).grid-rows-3(the same for rows),auto-rows-fr(every implicit row the same height),auto-cols-min.col-span-2(span 2 / span 2),col-span-full(1 / -1, from the first line to the last),col-start-2,col-end-4, and the same withrow-.grid-flow-col(fill columns first),grid-flow-row-dense(row dense: back-fill gaps left by spans).
Your own column layout. When the columns are not equal, write the template in square brackets, using an underscore where CSS has a space:
grid-cols-[200px_1fr] compiles to grid-template-columns: 200px 1fr. In a 600px container with gap-4 I measured the two columns
at 200px and 384px: a fixed sidebar and a main area that takes the rest. And the most useful arbitrary template needs no breakpoints at all:
That reads "as many columns as fit, each at least 12rem (192px), sharing the space". At 900px, four columns fit (a fifth would need 5 × 192 + 4 × 16 = 1024px) and they stretch to 213px; narrower screens get fewer columns, wider ones more. It is the simplest responsive card grid.
1fr means "a share of the free space, but never smaller than the content". With a long unbreakable string in one cell, that
"never smaller" wins. I put a 120-character word in the first cell of a 600px grid: with grid-cols-[1fr_1fr] the columns measured
1238.44px and 9.41px, so the page overflowed and the second column was crushed; with grid-cols-2 they were 300px and
300px. Tailwind's numbered columns are minmax(0, 1fr) precisely to stop this. If you write your own template, use
minmax(0,1fr) rather than bare 1fr for columns that may hold long content, and pair it with break-words (chapter 2).
Placing Items and Centring
The place-* classes set two alignments at once: place-items-center is place-items: center (align and justify the items),
place-content-center is place-content: center (the whole grid within its container), and place-self-center centres one item.
justify-items-center is the horizontal half alone. They are mostly used for the question everyone asks: how do I centre something? I measured a
100 × 50px box in a 600 × 300px container with three recipes (offsets from the container's top-left corner):
| Recipe | Classes on the container (or the box) | Box offset (left, top) |
|---|---|---|
| Flexbox | flex items-center justify-center | 250, 125 |
| Grid | grid place-items-center | 250, 125 |
| Auto margins (horizontal only) | mx-auto on the box, which needs a width | 250, 0 |
Both centre exactly: (600 − 100) ÷ 2 = 250 and (300 − 50) ÷ 2 = 125. Use grid place-items-center when there is a single child (it is two classes);
use flex when there are several items to centre as a group, or when you need flex-col. A third way, absolute positioning with
inset-0 m-auto, is in chapter 5.
Container and Aspect Ratio
The container class sets a width of 100% and then a max-width at each breakpoint:
Two things to see in that output. It does not centre itself or add side padding (the generated rule has no margin or padding), so a page wrapper is normally
container mx-auto px-4. And the steps are the breakpoints from chapter 7. Many projects skip container and use a plain
mx-auto max-w-5xl px-4 (one fixed maximum width), which is simpler to reason about.
Aspect ratio keeps a box in proportion as its width changes. aspect-video uses the theme variable --aspect-video, which is
16 / 9 (Chrome reported 16 / 9); aspect-square is 1 / 1, and your own ratio goes in brackets
(aspect-[4/3]) or in the shorter form aspect-3/2; aspect-auto removes it. A 256px-wide box measured 256 × 144 with
aspect-video, 256 × 256 with aspect-square and 256 × 192 with aspect-[4/3]. The height follows the width, so you set one of them.
Two Recipes Worth Memorising
A navigation bar: the logo at the left, the links next to it, and a button pushed to the far right. The trick is ml-auto, a margin on the
left that absorbs all the free space:
A sticky footer: on a short page the footer should sit at the bottom of the screen, not float in the middle. Make the body a column that is at least one screen tall, and let the main area grow:
min-h-dvh makes the body at least as tall as the screen, flex-col stacks the three parts, and flex-1 lets main
take all the leftover height (293 = 405 − 64 − 48). Remove flex-1 and the leftover space has nowhere to go, so the footer rides up under the content.
Hands-On Exercises
Build the navigation bar above in a 900px-wide page, then prove with measurements that the logo, link group and button share a centre line, that the button's right edge is exactly as far from the bar's edge as the logo is from its left edge, and what happens to the button's position if you delete ml-auto.
Build the sticky-footer page and show with measurements that the footer touches the bottom of the viewport. Then change the page so the footer stays at the bottom and the main area is vertically centred content: a heading and a paragraph in the middle of the leftover space.
📄 View solutionMake a gallery in a 900px container: a three-column grid with 16px gaps, 16:9 tiles, five tiles, the first spanning two columns. Report the size and position of every tile. Then change it to a grid that chooses its own number of columns (each at least 12rem wide) and report how many columns you get at 900px.
📄 View solutionChapter 4 Quick Reference
- Display:
block inline inline-block flex grid contents flow-root;hidden(removed from layout) vsinvisible(keeps its space) - Flex container:
flex,flex-col,flex-wrap,gap-*; main axisjustify-start|center|end|between|around|evenly; cross axisitems-start|center|end|stretch|baseline; one itemself-* justify-betweenwith 3×100px in 600px: 0, 250, 500;justify-evenly: 75, 250, 425- Flex items:
flex-1(equal shares from 0),flex-auto/grow(own size, then share the rest),flex-none(fixed),shrink-0,basis-*,order-*,ml-autopushes an item to the end - Grid:
grid grid-cols-3 gap-4(columns areminmax(0, 1fr));col-span-2,col-span-full,col-start-*; customgrid-cols-[200px_1fr](underscore = space); responsive without breakpointsgrid-cols-[repeat(auto-fill,minmax(12rem,1fr))] - Plain
1frcan overflow on long content (measured 1238px + 9px);grid-cols-2held 300px + 300px - Centring:
flex items-center justify-centerorgrid place-items-center(both 250, 125 in 600×300);mx-autocentres horizontally only container=width: 100%plus max-widths at 40, 48, 64, 80 and 96rem; addmx-auto px-4;aspect-video16/9,aspect-square,aspect-[4/3]- Sticky footer:
min-h-dvh flex flex-colon the page andflex-1onmain
relative, absolute, fixed and sticky, inset-*,
z-*, overflow and scrolling, and images.