Skip to content

Flexbox

docs.scrimba.com

You want three buttons sitting in a row, evenly spaced, all the same height, and vertically centred no matter how much text each one holds. In the old days this meant floats, clearfixes, and a lot of guessing. Flexbox is the tool built for exactly this: you tell one container to lay its children out in a line, and then you nudge them along two axes until they sit where you want. Most of the everyday layout you see on the web, nav bars, toolbars, rows of cards, is a flex container underneath.

The flex container

Flexbox starts with one line on a parent element. Set display: flex on a container and its direct children line up in a row, side by side. That container is now a flex container, and each child inside it becomes a flex item.

css
.nav {
  display: flex;    /* the children now sit in a row */
}

The items lay out along two directions. The main axis runs left to right, the way the items line up. The cross axis runs top to bottom, across the row. You will steer items along both of these, so it helps to picture them now: the main axis is the line the items sit on, the cross axis is the direction across it.

display: flex turns an element into a flex container and its direct children into flex items. Only the direct children take part; anything nested deeper is laid out by its own parent. By default the items sit in a row, left to right, each shrunk to the width of its content.

css
.nav {
  display: flex;    /* direct children become a row of flex items */
}

The whole model runs on two axes. The main axis is the direction the items flow along, a row by default, so left to right. The cross axis is perpendicular to it, running top to bottom. Every alignment property you meet works on one axis or the other, so keeping the two straight is the thing that makes the rest click into place.

display: flex gives an element a flex formatting context: its direct children become flex items and are laid out by the flex algorithm rather than normal flow. Only direct children participate; text runs and nested descendants are handled below them. This is the same kind of boundary that stops margin collapsing, which is one reason flex layouts behave more predictably than the float-and-clear era they replaced.

css
.nav {
  display: flex;    /* establishes a flex formatting context */
}

Two axes govern everything. The main axis is set by flex-direction (a row by default, so inline-start to inline-end), and the cross axis is perpendicular to it. The distinction is not cosmetic: justify-content always acts along the main axis and align-items always acts along the cross axis, so the moment you flip flex-direction to column, those two properties swap the direction they push in. Naming the layout by axis rather than by "horizontal" and "vertical" is what keeps it coherent when the direction changes.

css
.nav {
  display: flex;
}

A single declaration, and the children of any block stop stacking top to bottom and start sitting in a row. Everything else in this chapter is about steering those items once they are in the container.

JunoThe flex container Put display: flex on a parent and its direct children line up in a row. That parent is the flex container, the children are flex items. The items move along two directions: the main axis is the line they sit on, the cross axis runs across it, and you will push items along both.
JunoThe flex containerdisplay: flex makes a container lay its direct children out as a row of flex items. Only the direct children take part, not anything nested deeper. Keep the two axes clear in your head: the main axis is the flow direction, the cross axis is perpendicular, and every alignment property works on one or the other.
JunoThe flex containerdisplay: flex starts a flex formatting context, so direct children lay out by the flex algorithm and margins stop collapsing inside it. Name the layout by axis, not by horizontal and vertical, because justify-content tracks the main axis and align-items the cross axis. Flip flex-direction to column and those two swap which way they push.

Aligning along the axes

Once your items are in a row, two properties move them around. justify-content spaces items along the main axis, the direction they sit in. align-items moves them along the cross axis, up and down within the row.

css
.nav {
  display: flex;
  justify-content: space-between;   /* push items apart along the row */
  align-items: center;              /* line them up in the vertical middle */
}

The common values for justify-content are flex-start (bunched at the left), center (bunched in the middle), flex-end (bunched at the right), space-between (gaps between items, none at the ends), and space-around (gaps on both sides of each item). For align-items, center is the one you reach for most.

To centre something both ways, the move everyone learns first: set both to center.

css
.hero {
  display: flex;
  justify-content: center;   /* centred along the main axis */
  align-items: center;       /* centred along the cross axis */
}

Two properties handle alignment, one per axis. justify-content distributes items along the main axis, and align-items positions them on the cross axis. Because they own different axes, they never fight each other, they point in perpendicular directions.

css
.nav {
  display: flex;
  justify-content: space-between;   /* spreads items along the main axis */
  align-items: center;              /* centres them on the cross axis */
}

For justify-content, the values worth knowing are flex-start, center, flex-end, space-between (equal gaps between items, flush to the edges), and space-around (equal space around each item, so the end gaps are half the middle ones). For align-items, the default is stretch, which makes every item fill the container's height, and that is why a row of cards comes out the same height even when their content differs. Set align-items: center and they shrink back to their content height and sit in the middle instead.

Centring on both axes is the property pair that makes flexbox famous:

css
.hero {
  display: flex;
  justify-content: center;   /* main axis */
  align-items: center;       /* cross axis */
}

Alignment splits cleanly by axis. justify-content distributes free space along the main axis, and align-items sets the default cross-axis position of every item. They cannot conflict because they operate on perpendicular axes, but they do both depend on which direction is "main", so a flex-direction: column swaps what each one controls.

css
.nav {
  display: flex;
  justify-content: space-between;   /* free space goes between items */
  align-items: center;              /* each item centred on the cross axis */
}

The distinction that trips people up is that justify-content only has anything to do when there is leftover space on the main axis. It distributes free space, so if the items already fill the container (or overflow it), space-between and center look identical because there is nothing to distribute. align-items, by contrast, positions each item independently within the container's cross size, and its initial value is stretch: items with no fixed cross-axis size grow to fill the container. The equal-height card row is align-items: stretch doing its default job. Override it per item with align-self, covered in the sizing section below.

css
.hero {
  display: flex;
  justify-content: center;   /* main-axis centring */
  align-items: center;       /* cross-axis centring, overrides the stretch default */
}

Two items and a min-height on the container, both set to center, and you have solved the centring problem that took a generation of developers absolute positioning and negative margins to fake.

JunoAligning along the axesjustify-content spaces items along the main axis, align-items moves them along the cross axis. The values to remember for the first are center, space-between, and flex-start. Set both to center and whatever is inside sits dead in the middle, which is the layout move you will use over and over.
JunoAligning along the axes One property per axis: justify-content on the main axis, align-items on the cross. The default align-items: stretch is why a row of cards comes out the same height without you asking. And both set to center is the centre-anything move worth committing to memory.
JunoAligning along the axesjustify-content distributes leftover main-axis space, so if the items already fill the row it does nothing, which explains a lot of "why isn't this centring" moments. align-items positions each item on the cross axis and defaults to stretch, giving you equal-height rows for free. Flip flex-direction and remember both properties swap the axis they act on.

Direction and wrapping

By default a flex container lays items out in a row. Change that with flex-direction. Set it to column and the items stack top to bottom instead of sitting side by side.

css
.sidebar {
  display: flex;
  flex-direction: column;   /* items stack vertically now */
}

When items in a row run out of room, they get squashed thinner and thinner. To let them drop onto a new line instead, add flex-wrap: wrap.

css
.card-row {
  display: flex;
  flex-wrap: wrap;   /* items that do not fit move to the next line */
}

For the space between items, use gap. It adds a consistent gap between every item without adding space at the outer edges.

css
.card-row {
  display: flex;
  gap: 16px;   /* 16px between each item */
}

flex-direction sets the main axis. The default row runs left to right; column turns the main axis vertical, so items stack top to bottom and, importantly, justify-content now controls vertical spacing while align-items controls horizontal. There are also row-reverse and column-reverse if you need the order flipped.

css
.sidebar {
  display: flex;
  flex-direction: column;   /* main axis is now vertical */
  justify-content: center;  /* which means this centres vertically */
}

By default a flex line never breaks: items shrink to fit rather than wrap. Set flex-wrap: wrap and items that cannot fit move to a new line, which is what makes a responsive card grid possible without media queries.

css
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;   /* gap works on both axes: 16px between rows and columns */
}

Use gap for spacing, not margins on the items. gap applies only between items, never at the outer edges, so you never have to strip a trailing margin off the last child.

flex-direction defines the main axis, and every axis-dependent property follows it. row (the default) makes the main axis horizontal; column makes it vertical and silently reassigns justify-content to vertical spacing and align-items to horizontal. The -reverse variants flip the direction of the main axis, but note they only reorder visually: the DOM order is unchanged, so keyboard and screen-reader order still follow the source, which is a real accessibility consideration when you reach for row-reverse.

css
.sidebar {
  display: flex;
  flex-direction: column;    /* main axis vertical; justify-content now centres vertically */
  gap: 8px;
}

flex-wrap: wrap lifts the single-line restriction. Without it, a flex container forces all items onto one line and distributes any shortfall by shrinking them; with it, items that overflow the main size break onto new lines, and the container becomes multi-line. Once wrapping is on, align-content (distinct from align-items) governs how the lines are spaced on the cross axis. gap sets the space between items and, when wrapped, between lines, and it is the correct tool: unlike margins it collapses nothing, adds no edge space, and reads as a single spacing decision.

css
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 24px;   /* row-gap 16px, column-gap 24px */
}

This is also where the boundary with CSS grid shows up. Flex wrap fills a line, then breaks, so items on different rows do not align into columns. When you need a true two-dimensional grid with aligned rows and columns, that is grid's job, not flexbox's.

JunoDirection and wrappingflex-direction: column stacks items vertically instead of in a row. flex-wrap: wrap lets items drop to a new line when they run out of room, instead of getting squashed. And gap puts even space between items without adding any at the edges, so reach for it before margins.
JunoDirection and wrappingflex-direction sets the main axis, and switching to column means justify-content now spaces vertically, which surprises people once. flex-wrap: wrap is what makes a card row reflow without media queries. Use gap for spacing so you never chase a stray margin on the last item.
JunoDirection and wrappingflex-direction defines the main axis and every axis-based property follows it, while the -reverse values reorder visually but leave DOM and focus order alone, so use them with care. flex-wrap turns a container multi-line and hands line spacing to align-content. When wrapped items need to line up into real columns, that is grid, not flexbox.

Flexible item sizing

The point of flexbox is that items can flex, growing or shrinking to share the space. The shorthand you will use most is flex: 1 on an item, which tells it to grow and take an equal share of whatever room is available.

css
.card {
  flex: 1;   /* every card grows to fill an equal share of the row */
}

Put flex: 1 on three cards in a row and they split the container into three equal widths. Give one card flex: 2 and it takes twice as much space as the others. That single number is how items divide up the leftover room between them.

Behind flex: 1 are three separate controls: how much an item grows, how much it shrinks, and its starting size. You rarely set them one by one at first, the shorthand covers the common cases, but it helps to know they are there.

Three properties decide how items share space. flex-grow sets how much an item grows to fill leftover space (0 means do not grow). flex-shrink sets how much it gives up when space is tight (1, the default, means it may shrink). flex-basis sets the item's starting size before growing or shrinking, and it defaults to auto, meaning "use the content or the width".

The flex shorthand bundles all three, and the values you actually type are few:

css
.card {
  flex: 1;   /* grow 1, shrink 1, basis 0: equal-width columns */
}

.sidebar {
  flex: 0 0 240px;   /* do not grow, do not shrink, stay 240px */
}

flex: 1 is the workhorse: it makes items split the row into equal shares regardless of their content, because the 0 basis means they start from nothing and grow only by their grow factor. flex: 0 0 240px is the fixed-width sidebar next to a flex: 1 main area, a layout you will build constantly.

Item sizing resolves through three properties the container evaluates in order. flex-basis is the item's starting main-axis size, taken before any free space is distributed; its default auto defers to the item's width (or content size). Then the container measures the difference between the sum of the basis values and its own size. If there is free space, it is handed out in proportion to each item's flex-grow. If there is a shortfall, it is taken back in proportion to each item's flex-shrink weighted by its basis.

css
/* flex: 1 expands to flex-grow: 1, flex-shrink: 1, flex-basis: 0% */
.card {
  flex: 1;   /* basis 0, so items ignore content width and split space by grow factor */
}

The subtle part is flex-basis versus width, because they interact. flex-basis: 0 (as in flex: 1) makes items ignore their content and divide space by grow factor alone, giving true equal columns. flex-basis: auto (as in flex: auto, or a plain width) makes each item start at its content or declared width and only then share the remainder, so items with more content end up wider. Choosing between them is really choosing whether content should influence the final widths.

Two production notes. First, flex-basis beats width when both are set, so once an item is a flex item, prefer expressing its size through flex. Second, the overflow gotcha: a flex item has a default min-width: auto, meaning it refuses to shrink below its content's intrinsic size, so a long word or an overflowing child can blow out the whole row. The fix is min-width: 0 on the item that should be allowed to shrink.

css
.main {
  flex: 1;
  min-width: 0;   /* lets this item shrink below its content, so long text truncates instead of overflowing */
}

align-self overrides align-items for a single item, so one item can sit at flex-start while the rest centre. And the tooling call: reach for flexbox for one-dimensional layouts, a nav bar, a toolbar, a single row or column of items. When you need aligned rows and columns at once, CSS grid is the right tool. If you are unsure how the widths resolve underneath all this, sizing units covers how percentages and other units behave here.

css
.layout {
  display: flex;
  gap: 24px;
}
.sidebar {
  flex: 0 0 240px;   /* fixed 240px, never grows or shrinks */
}
.main {
  flex: 1;           /* takes all the remaining space */
}

A fixed sidebar beside a flexible main column, three lines of intent. This one pattern, plus the aligned nav bar from earlier, covers a large slice of the layouts you will build.

JunoFlexible item sizingflex: 1 on an item tells it to grow and take an equal share of the row, so three cards with flex: 1 come out the same width. Bump one to flex: 2 and it takes twice the space. That one number is how flex items split up the room between them.
JunoFlexible item sizing The flex shorthand bundles grow, shrink, and basis. flex: 1 gives equal-width columns because its 0 basis makes items start from nothing and grow evenly. flex: 0 0 240px is your fixed sidebar next to a flex: 1 main area, a layout you will reach for again and again.
JunoFlexible item sizingflex-basis is the starting size before space is shared, and it beats width on a flex item, so flex: 1 (basis 0) splits space evenly while flex: auto lets content influence widths. Watch the default min-width: auto, which stops an item shrinking below its content and overflows the row until you set min-width: 0. Use align-self to peel one item out of the alignment, and switch to grid the moment you need aligned rows and columns.