Flexbox

A deep dive into display: flex — main and cross axes, justify-content, align-items, and flex-grow/shrink/basis.

What flexbox solves

Before flexbox, centering something vertically or building an evenly-spaced row of items required hacks — floats, negative margins, display: table. Flexbox (display: flex) is a one-dimensional layout model purpose-built for exactly that: arranging items in a row or column, distributing space between them, and aligning them, all with a small, purpose-built set of properties.

Css
.container {
    display: flex;
}

The moment you set display: flex on a parent, its direct children become flex items and immediately line up in a row — no floats, no clearfixes.

The two axes

Flexbox thinks in terms of a main axis and a cross axis, and which is which depends on flex-direction:

Css
.container {
    display: flex;
    flex-direction: row;    /* main axis: horizontal (default) */
    /* flex-direction: column; */ /* main axis: vertical */
}
  • flex-direction: row (default) — main axis is horizontal, cross axis is vertical.
  • flex-direction: column — main axis is vertical, cross axis is horizontal.

Every alignment property below operates relative to whichever axis is currently "main" vs "cross" — this is the single most important mental model in flexbox.

justify-content — aligning along the main axis

Css
.container {
    display: flex;
    justify-content: flex-start;    /* default — items packed at the start */
    /* justify-content: center; */          /* items centered */
    /* justify-content: flex-end; */        /* items packed at the end */
    /* justify-content: space-between; */   /* first/last flush to edges, gap distributed between */
    /* justify-content: space-around; */    /* equal space around each item */
    /* justify-content: space-evenly; */    /* fully equal space everywhere, including edges */
}

align-items — aligning along the cross axis

Css
.container {
    display: flex;
    align-items: stretch;      /* default — items stretch to fill the container's cross-axis size */
    /* align-items: flex-start; */  /* items align to the top (in a row) */
    /* align-items: center; */      /* items vertically centered */
    /* align-items: flex-end; */    /* items align to the bottom */
}

Combining justify-content: center and align-items: center is the famous "perfectly centered <div>" one-liner flexbox is known for:

Css
.center-everything {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

flex-grow, flex-shrink, flex-basis

These three control how individual flex items size themselves relative to available space, usually written together as the flex shorthand:

Css
.item {
    flex: 1 1 200px;
    /*    │ │ └─ flex-basis: starting size before growing/shrinking */
    /*    │ └─── flex-shrink: how eagerly this item shrinks if there's not enough room */
    /*    └───── flex-grow: how eagerly this item grows to fill extra space */
}
  • flex-grow — a unitless proportion of any leftover space this item should claim relative to its siblings. flex-grow: 2 grows twice as fast as a sibling with flex-grow: 1.
  • flex-shrink — same idea, but for shrinking when there isn't enough room. flex-shrink: 0 prevents an item from shrinking at all.
  • flex-basis — the item's initial size before growing/shrinking is applied — effectively a width (or height, in column direction) that flex-grow/shrink then adjust from.

Common shorthand values:

Css
.equal-width-item  { flex: 1; }         /* flex: 1 1 0% — items share space equally */
.fixed-width-item   { flex: 0 0 200px; } /* never grows, never shrinks, always 200px */
.grow-only-item     { flex: 1 0 auto; }  /* grows to fill space, won't shrink below content size */

Practical example: a responsive navbar

HTML
<nav class="navbar">
    <div class="navbar-brand">NOA Labs</div>
    <ul class="navbar-links">
        <li><a href="/tutorials">Tutorials</a></li>
        <li><a href="/roadmaps">Roadmaps</a></li>
        <li><a href="/pricing">Pricing</a></li>
    </ul>
    <button class="navbar-cta">Sign up</button>
</nav>
Css
.navbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.5rem;
    background: #111827;
}

.navbar-links {
    display: flex;
    gap: 1.5rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.navbar-links a {
    color: #e5e7eb;
    text-decoration: none;
}

.navbar-cta {
    flex-shrink: 0; /* never let the button get squeezed by the links */
}

justify-content: space-between pushes the brand to the left edge and the CTA button to the right edge, with the nav links naturally filling the space between — no manual margin math required.

Practical example: an equal-height card row

HTML
<div class="card-row">
    <div class="card">Short content</div>
    <div class="card">A bit more content here to show height matching</div>
    <div class="card">Content</div>
</div>
Css
.card-row {
    display: flex;
    gap: 1rem;
}

.card {
    flex: 1;                 /* every card claims equal width */
    padding: 1.5rem;
    background: #f9fafb;
    border-radius: 8px;
    /* align-items: stretch (the flex default) already gives every card equal height */
}

Because align-items defaults to stretch, every card automatically matches the height of its tallest sibling — a problem that used to require JavaScript or table layout to solve reliably.

gap

gap puts space between flex items without adding margin to the items themselves (so no "extra margin on the last item" cleanup needed):

Css
.container {
    display: flex;
    gap: 1rem;          /* same gap on both axes */
    /* row-gap: 1rem; column-gap: 2rem; */ /* independent control */
}

Common mistakes

  • Forgetting that justify-content and align-items swap meaning between flex-direction: row and columnalign-items always targets the cross axis, not literally "vertical."
  • Using margins to fake gaps between flex items instead of the gap property, requiring extra :last-child overrides to avoid unwanted trailing margin.
  • Expecting flex: 1 on every item to always produce perfectly equal widths — it only produces equal widths when items have equal (or no) content-based flex-basis and similar minimum content sizes; long unbreakable content can still force an item wider.
  • Not setting flex-shrink: 0 on an item that should never shrink (like an icon or button next to flexible text) — by default, everything can shrink.