ESC

Type to search the knowledge base.

Flexbox Alignment Deep Dive

justify-content, align-items, align-self, align-content — main vs cross axis, wrapping lines, and absolute positioning inside flex.

intermediate3 min read
  • css
  • flexbox
  • alignment

Flex alignment is easy until wrapping, auto margins, and axis flipping collide. Everything hinges on main axis vs cross axis, which depend on flex-direction — not on “horizontal vs vertical” forever.

Docs: MDN aligning items, Box alignment.

Axis map

flex-direction Main axis Cross axis
row horizontal vertical
row-reverse horizontal (reversed) vertical
column vertical horizontal
column-reverse vertical (reversed) horizontal
.row {
  display: flex;
  flex-direction: row;
  justify-content: space-between; /* main */
  align-items: center;            /* cross */
}

justify-content (main axis)

Pack flex lines’ items along the main axis when free space exists:

  • flex-start / start / left (with caveats)
  • flex-end / end
  • center
  • space-between / space-around / space-evenly
.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem; /* gap often replaces space-* hacks */
}

With gap, prefer flex-start + margin-inline-start: auto on a group for “push the rest endward”:

.nav__actions {
  margin-inline-start: auto;
  display: flex;
  gap: 0.5rem;
}

align-items & align-self (cross axis)

align-items sets the default cross-axis alignment for all items on a line.

  • stretch (default) — fill cross size if height/width auto
  • flex-start / center / flex-end / baseline
.form-row {
  display: flex;
  align-items: flex-end; /* bottom-align labels + controls */
  gap: 0.75rem;
}

.form-row .hint {
  align-self: center; /* override one item */
}

baseline alignment is gold for mixed text sizes in a toolbar.

align-content (multi-line only)

When flex-wrap: wrap creates multiple lines, align-content distributes those lines on the cross axis. It does nothing useful with a single line (people confuse it with align-items).

.chips {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 0.5rem;
  min-height: 6rem;
}

Values mirror justify-content-style packing: stretch, space-between, center, etc.

Absolute positioning inside flex

.card {
  display: flex;
  position: relative;
}

.card__badge {
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-end: 0.5rem;
}

Absolutely positioned children are not flex items; they don’t participate in flex packing. The flex container is their containing block if it has position not static (or another CB rules apply).

stretch interactions

.equal-cards {
  display: flex;
  align-items: stretch; /* default */
}

.equal-cards .card {
  /* height auto → stretch to tallest item’s line */
  display: flex;
  flex-direction: column;
}

.equal-cards .card button {
  margin-block-start: auto; /* pin footer actions */
}

Interview out-loud

“justify-content aligns on the main axis; align-items on the cross axis; align-self overrides one item; align-content packs wrapped lines. Axes follow flex-direction. I use gap and auto margins for practical toolbars, and I don’t expect align-content to work on a single line.”

Footguns

  1. Swapping justify/align after changing to flex-direction: column.
  2. align-content vs align-items confusion.
  3. stretch ignored when items have fixed cross-size.
  4. space-between with one item — looks like start.
  5. Centering text with align-items instead of text-align when the item is full width.

Safe alignment defaults for toolbars

.toolbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.toolbar > .spacer {
  flex: 1 1 auto;
  min-width: 0.5rem;
}

A flex spacer item (or margin-inline-start: auto on the next group) is clearer than justify-content: space-between when the toolbar wraps — wrapped rows with space-between often look uneven.

Further reading

Related guides