Skip to content
wannacode

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.

HTML, CSS, JS

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: flex is 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: column turns 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-content arranges 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-items aligns along the cross axis: stretch (the default, all as tall as the line), center, flex-start, flex-end.
  • gap puts 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.

HTML, CSS, JS

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.

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-basis is the basis: the width of the item before anything is shared out. By default it is auto: its width, or as wide as its content.
  • flex-grow says how many shares of the free space the item takes. By default it is 0: it does not grow.
  • flex-shrink says how much it shrinks if there is not enough space. By default it is 1.

The browser works it out like this:

  1. It places every item with its basis.
  2. The free space is the width of the container minus the bases and the gaps.
  3. 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
  1. .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.
  2. First every item gets its flex-basis: 120, 100 and 100px. That is its width before anything is shared out.
  3. Between three items there are two gaps of 20px. Taken: 120 + 100 + 100 + 2 × 20 = 360px.
  4. What is left is the free space: 600 − 360 = 240px.
  5. flex-grow says how many shares each item takes: logo 0, search 2, cart 1, three in all. One share is 240 : 3 = 80px.
  6. Each item grows by its shares: search by 160px, cart by 80px, logo stays as it is. The widths are 120, 260 and 180px.
  7. 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.

HTML, CSS, JS

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: ; }

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.

HTML, CSS, JS

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:

HTML, CSS, JS

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?

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.

HTML, CSS, JS

Test

Test yourself

1. You want three products next to each other. On which element do you write display: flex?
2. A 500px bar with gap: 20px has two items with flex-basis: 100px. The first has flex-grow: 1, the second flex-grow: 3. How wide are they?
3. The container has flex-direction: column. Along which axis does justify-content work?
4. The name in .info { flex: 1 } has white-space: nowrap and text-overflow: ellipsis, but it is not cut and the row goes off the screen. What is missing?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·