Flexbox: row and column
In short: Flexbox lays items out in a row or a column along the main axis, aligns them on the cross axis and shares the free space with flex-grow.
Problem
The header on three lines
The new header of the shop should be one line: the logo on the left, the menu next to it, the cart on the right. On a computer it is three lines, one under the other.
Console
The CSS has no mistake. <nav> is a block element, and block elements stand one under the other, each taking a whole line. Links are inline elements and sit on one line with the text around them. So the logo is on the first line, the menu on the second and the cart on the third.
The idea
Container and items
The children of the header need another way to be laid out. It is display: flex on the parent:
header { display: flex;}- The element with
display: flexis a flex container. Its children (only the direct children) are flex items. - The items stand next to each other along the main axis. By default it goes left to right.
flex-direction: columnturns it top to bottom. - At a right angle to it is the cross axis.
The layout is set on the container, not on the children:
justify-contentarranges along the main axis:flex-start(at the start),center,flex-end,space-between(the first at the start, the last at the end, the space between them).align-itemsaligns along the cross axis:stretch(the default, all as tall as the line),center,flex-start,flex-end.gapputs the same space between the items, with nothing before the first or after the last.
An element can be a flex item and a flex container at once. nav { display: flex; gap: 12px; } lays out the links in the menu, and the menu itself is an item of the header.
Try it
Logo on the left, cart on the right
Put the header on one line: the logo at the left edge, the cart at the right, the menu between them. Use justify-content. The check measures how many lines the children of <header> are on.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The shop</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<a class="logo" href="/">The shop</a>
<nav>
<a href="/bread">Bread</a>
<a href="/milk">Milk</a>
<a href="/fruit">Fruit</a>
</nav>
<a class="cart" href="/cart">Cart (2)</a>
</header>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
header {
padding: 12px 16px;
background: #f3efe6;
display: flex;
justify-content: space-between;
align-items: center;
}
a { color: #1f2430; }
.logo { font-weight: 700; }
nav a { margin-right: 12px; }
display: flex on header puts the three children in a row. justify-content: space-between sends the first to the start, the last to the end, and the free space goes between them. align-items: center centres them vertically. Here the three are equally tall, but if the cart becomes a button with padding, the difference shows.
Try it
Find the bug
The header is in a row, but the cart does not go to the right. Why?
Pick the line with the bug.
justify-content arranges the children of a container, so it is written on the container. On line 6 it is on .cart: it has no flex children, and the property does nothing. It should be justify-content: space-between in header, or margin-left: auto on .cart, which the next card is about.
The idea
The free space
The header also has a search field. It should take all the space between the logo and the cart, however wide the screen is. Three properties of the flex item help here:
flex-basisis the basis: the width of the item before anything is shared out. By default it isauto: itswidth, or as wide as its content.flex-growsays how many shares of the free space the item takes. By default it is 0: it does not grow.flex-shrinksays how much it shrinks if there is not enough space. By default it is 1.
The browser works it out like this:
- It places every item with its basis.
- The free space is the width of the container minus the bases and the gaps.
- It shares the free space by
flex-grow, and every item grows by its share.
The flex property sets all three at once. flex: 1 is flex-grow: 1, flex-shrink: 1 and flex-basis: 0%: the item grows and its basis is zero. That is why three items with flex: 1 become equally wide, whatever text is in them.
See it
See how the space is shared
A 600px bar with three items. Watch the sum at the top and the green free space.
.bar { display: flex; width: 600px; gap: 20px;}.logo { flex-basis: 120px; }.search { flex-basis: 100px; flex-grow: 2; }.cart { flex-basis: 100px; flex-grow: 1; }Step by step, in words
- .bar is a flex container 600px wide. Its children are flex items: they stand in a row along the main axis, from left to right.
- First every item gets its flex-basis: 120, 100 and 100px. That is its width before anything is shared out.
- Between three items there are two gaps of 20px. Taken: 120 + 100 + 100 + 2 × 20 = 360px.
- What is left is the free space: 600 − 360 = 240px.
- flex-grow says how many shares each item takes: logo 0, search 2, cart 1, three in all. One share is 240 : 3 = 80px.
- Each item grows by its shares: search by 160px, cart by 80px, logo stays as it is. The widths are 120, 260 and 180px.
- 120 + 20 + 260 + 20 + 180 = 600: the row is full. The browser gives exactly these widths. Note that search is not twice as wide as cart: only the free space is shared 2 : 1.
search has flex-grow: 2 and cart has 1, but search is not twice as wide: 260 against 180. Only the free space is shared, and the bases stay. So when you want equal columns, the basis has to be zero: flex: 1.
Try it
The search takes the rest
The logo is 120px, the cart 110px, and the header is up to 640px. Make the search field take all the space between them. The check measures its width in pixels: 640 − 2 × 16 (padding) − 120 − 110 − 2 × 16 (gap) = 346px.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The shop</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<a class="logo" href="/">The shop</a>
<input type="search" aria-label="Search" placeholder="Bread, milk…">
<a class="cart" href="/cart">Cart (2)</a>
</header>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
header {
max-width: 640px;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 16px;
background: #f3efe6;
}
a { color: #1f2430; }
.logo { width: 120px; font-weight: 700; }
.cart { width: 110px; text-align: right; }
input {
flex: 1;
padding: 6px 8px;
}
flex: 1 on the field: it is the only item with flex-grow, so it takes all the free space. The logo and the cart keep their widths. width: 100% does not work: the field asks for the whole header, the space is not enough, and flex-shrink shrinks both the logo and the cart.
Try it
One thing on the right
Sometimes you want the logo and the menu on the left and only the cart on the right. space-between does not work: the menu would go to the middle. There is a property of the item itself: a margin with the value auto takes all the free space on its side and pushes the item.
header { display: flex; gap: 16px; }
.cart { margin-left: ; }margin-left: auto: the left margin of the cart eats the free space, and the cart stands at the right edge. The logo and the menu stay next to each other on the left. In a flex container auto margins are worked out before justify-content.
Try it
A new line when they do not fit
By default a flex container keeps all items on one line (flex-wrap: nowrap). If they do not fit, it shrinks them, and when it cannot any more, the line goes off the screen. With flex-wrap: wrap the items that do not fit move to a new line.
On a phone (360px) the four products are squeezed into one line and the page scrolls sideways. Put them two per line, each at least 150px, with the two in a line equally wide. The check counts the lines and columns and measures the width of the cards.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Offers</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Offers</h1>
<div class="products">
<article class="card">
<h2>White bread</h2>
<p>€1.20</p>
</article>
<article class="card">
<h2>Fresh milk</h2>
<p>€1.60</p>
</article>
<article class="card">
<h2>White cheese</h2>
<p>€7.90</p>
</article>
<article class="card">
<h2>Apples</h2>
<p>€2.10</p>
</article>
</div>
</main>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
main { padding: 12px; }
h1 { margin: 0 0 12px; font-size: 1.4rem; }
.products {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.card {
flex: 1 1 150px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card h2 { margin: 0 0 4px; font-size: 1rem; }
.card p { margin: 0; }
flex-wrap: wrap on the container allows a new line. flex: 1 1 150px on the card: the basis is 150px, so two fit (150 + 12 + 150 = 312 ≤ 336) and three do not. Then the free space in the line is shared equally: each becomes (336 − 12) : 2 = 162px. flex-wrap: wrap alone makes lines, but each card is as wide as its text.
The idea
The name that does not shrink
In the cart the name of the product is next to the picture, and next to it are the quantity and the price. The name is long. We want it cut with "…" on one line: white-space: nowrap, overflow: hidden and text-overflow: ellipsis. The block with the name has flex: 1. On a phone, though, the line goes off the screen and the price is squeezed:
Console
The reason is one rule. A flex item does not shrink below its narrowest content: min-width is auto by default, not 0. For .info the narrowest content is the whole name on one line. min-width: 0 on the flex item removes that limit. Then .info shrinks, and the heading inside cuts the text.
Someone added min-width: 0, but the name is still not cut. Which line is wrong?
min-width: 0 on line 4 is on the heading .name. It is not a flex item: the flex item is .info, its parent. The limit is on .info, so min-width: 0 has to go there.
More precisely, if you are curious
Shrinking. If the bases and gaps add up to more than the container, the free space is negative and the items shrink. The share is not by flex-shrink alone but by flex-shrink × the basis: a wide item shrinks more. The bar from the scene, but 280px wide: 120 + 100 + 100 + 40 = 360, 80px are missing. The weights are 120, 100 and 100. logo loses 80 × 120 : 320 = 30px, the others 25px each: 90, 75 and 75px. An item with flex-shrink: 0 does not shrink.
The values of flex. flex: 1 is 1 1 0%. flex: auto is 1 1 auto: it grows, but from the width of its content. flex: none is 0 0 auto: it neither grows nor shrinks. flex: 0 0 64px is an item of exactly 64px.
What does not shrink. Pictures and fields have their own width, and min-width: auto keeps them from shrinking. That is why the price (<strong>) is squeezed in the cart and the picture is not.
Another way. overflow: hidden on the flex item itself also removes the limit: an element that clips its content can shrink down to 0.
One item differently. align-self aligns a single item on the cross axis. order changes its place on the screen but not in the tree: the keyboard and the screen reader follow the tree. So we do not move things people click with order.
Lines with wrap. align-content arranges the lines themselves on the cross axis when the container is taller than them. display: inline-flex makes a container that sits in the text like a word.
Exercise
A line in the cart
Lay out a line of the cart on a phone (360px): the picture, the name, the quantity and the price in a row, centred vertically, with 12px between them. The picture stays 64px, the quantity 56px, the price 72px. The name takes the rest of the space (here 108px) and is on one line, and if it is long, it is cut with "…". The page does not scroll sideways.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Cart</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Cart</h1>
<div class="line">
<img class="thumb" src="img/bread.svg" alt="">
<div class="info">
<h2 class="name">Wholegrain rye bread with seeds, sliced, 500 g</h2>
<p class="unit">€1.90 each</p>
</div>
<input class="qty" type="number" value="2" min="1" aria-label="Quantity">
<strong class="price">€3.80</strong>
</div>
</main>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
h1 { margin: 0; padding: 12px; font-size: 1.4rem; }
.line {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-top: 1px solid #ddd;
}
.thumb { width: 64px; height: 64px; border-radius: 8px; }
.info {
flex: 1;
min-width: 0;
}
.name {
margin: 0 0 4px;
font-size: 1rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.unit { margin: 0; color: #6b7180; }
.qty { width: 56px; }
.price { width: 72px; text-align: right; }
.line { display: flex; align-items: center; gap: 12px;}.info { flex: 1; min-width: 0; }.name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}flex: 1 gives .info a basis of 0 and all the free space: 336 − 64 − 56 − 72 − 36 = 108px. With flex-grow: 1 the basis stays the length of the name. Then the space is negative and flex-shrink squeezes the price. Without min-width: 0 the name does not shrink and the line goes off the screen.
Test
Test yourself
Related lessons
Section titled "Related lessons"- Builds on: The HTML document · Forms · Selectors and the cascade · The box model
- Continues in: Grid: rows and columns · Responsive design · The shop front
- Tasks on this topic: CSS: how the page looks