CSS Flexbox Alignment (justify-content, align-items)

Once you turn an element into a flex container with display: flex, its children line up along a single row or column. But by default they hug the start of that line and stretch to fill the cross direction — rarely what you actually want. justify-content and align-items are the two properties that give you full control over where flex items sit inside the container, and understanding how they relate to the flex container’s two axes is the single most important skill for building layouts with flexbox.

This lesson covers both properties in depth: what each value does, how the browser computes positions along each axis, how the axes swap when you change flex-direction, and the mistakes that trip up almost every beginner.

Overview: The Two Axes

Every flex container has two axes. The main axis is the direction items are laid out in, controlled by flex-direction. When flex-direction is row (the default), the main axis runs horizontally, left to right. When it’s column, the main axis runs vertically, top to bottom. The cross axis is always perpendicular to the main axis — vertical when the main axis is horizontal, and horizontal when the main axis is vertical.

justify-content always aligns items along the main axis. align-items always aligns items along the cross axis. This is the part that confuses newcomers: these properties don’t mean “horizontal” and “vertical” — they mean “main” and “cross”, and which physical direction that maps to depends entirely on flex-direction. Flip flex-direction from row to column and justify-content suddenly controls vertical position instead of horizontal.

Under the hood, the rendering engine performs layout in two passes for each flex line. First it resolves the size of every flex item along the main axis (accounting for flex-grow, flex-shrink, and flex-basis, or their natural content size if flex item sizing isn’t specified). Once it knows the total size all items occupy on the main axis, it computes the leftover space in the container — the container’s content-box size minus the sum of item sizes and gaps. justify-content tells the engine what to do with that leftover space: push it all before the first item (flex-end), split it evenly between items (space-between), and so on. Then, independently, for each item on the cross axis, the engine looks at the container’s cross-axis size (its height, in a row layout) and the item’s own cross-axis size, and align-items decides where within that available space the item sits — or whether it stretches to fill it.

align-items vs. align-content vs. align-self

It’s worth distinguishing three easily-confused properties up front: align-items sets the default cross-axis alignment for every item in the container, in a single sentence: “how does each item sit within its line.” align-self, set on an individual item, overrides align-items for just that one item. align-content is different again — it only matters when the flex container has multiple lines (via flex-wrap: wrap) and controls how those lines, as a group, are distributed within the container’s cross-axis space. If there’s only one line, align-content has no visible effect.

Syntax

Both properties are set directly on the flex container, never on the items themselves (items use align-self instead):

.container {
  display: flex;
  justify-content: value;
  align-items: value;
}
Part Meaning
display: flex Required — establishes the flex formatting context. Neither property does anything without it.
justify-content Distributes items and leftover space along the main axis.
align-items Positions items within the available space of the cross axis.

Common values for justify-content:

Value Effect
flex-start (default) Items packed toward the start of the main axis.
flex-end Items packed toward the end of the main axis.
center Items packed in the center of the main axis.
space-between Equal space is inserted between items; no space at the outer edges.
space-around Equal space around each item (edges get half as much space as between items).
space-evenly Space between items and at the edges is all exactly equal.

Common values for align-items:

Value Effect
stretch (default) Items stretch to fill the container’s cross-axis size, unless they have an explicit cross-axis size set.
flex-start Items align to the start edge of the cross axis.
flex-end Items align to the end edge of the cross axis.
center Items are centered on the cross axis.
baseline Items align so their text baselines line up, useful when items contain different font sizes.

Examples

Example 1: Centering items both ways

This applies to a container div (class flex-container) holding three plain div children (class flex-item):

.flex-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 200px;
  border: 2px solid #333;
}

.flex-item {
  width: 60px;
  height: 60px;
  background-color: #4a90d9;
  margin: 0 8px;
}

Result: The container is a 200px-tall box with a dark border. The three 60px blue squares sit next to each other as a group, and that group is centered horizontally (main axis, via justify-content: center) and vertically (cross axis, via align-items: center) within the box, with 8px of horizontal margin between them.

Because the main axis is horizontal here (default flex-direction: row), justify-content: center centers the squares left-to-right, while align-items: center centers them top-to-bottom relative to the container’s 200px height.

Example 2: A realistic navbar with space-between

Applied to a nav element (class navbar) containing a logo div and a div of links:

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 64px;
  padding: 0 24px;
  background-color: #1f2937;
}

.navbar .logo {
  font-size: 1.25rem;
  color: #ffffff;
}

.navbar .nav-links {
  display: flex;
  gap: 16px;
}

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

Result: A dark, 64px-tall navigation bar. The logo text sits flush against the left edge (minus the 24px padding) and the group of nav links sits flush against the right edge, with all the leftover horizontal space pushed into the single gap between them, courtesy of space-between. Because align-items: center is set, both the logo and the link group are vertically centered within the 64px bar even though they may have different heights.

Notice the inner .nav-links is also a flex container, with its own gap spacing the individual links — a common real-world pattern of nesting flex containers.

Example 3: Column direction flips the axes

Applied to a div (class card) containing a span (class badge), an h3 (class title), and a button (class cta):

.card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  height: 280px;
  padding: 20px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
}

.card .badge {
  align-self: flex-end;
  background-color: #f59e0b;
  color: #ffffff;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.75rem;
}

.card .title {
  font-size: 1.1rem;
  font-weight: 600;
}

.card .cta {
  align-self: stretch;
  text-align: center;
  padding: 10px;
  background-color: #2563eb;
  color: #ffffff;
  border-radius: 6px;
}

Result: A 280px-tall bordered card. Because flex-direction: column makes the main axis vertical, justify-content: space-between now spreads the three children out top-to-bottom, pushing the badge toward the top and the button toward the bottom with the title’s leftover space between them. align-items: flex-start makes everything default to hugging the left edge (the cross axis is now horizontal) — except the badge, which overrides that with align-self: flex-end and sticks to the right edge instead, and the button, which overrides it with align-self: stretch and stretches full-width.

This example demonstrates the core lesson: swapping flex-direction to column doesn’t just change item order — it swaps which physical direction justify-content and align-items each control.

How It Works Step by Step

  1. The browser resolves flex-direction first, which fixes which physical direction is the main axis and which is the cross axis for this container.
  2. It calculates each item’s main-axis size (via flex-basis/content size, adjusted by flex-grow/flex-shrink) and lays items out along the main axis in document order (or the order given by the order property).
  3. It sums the items’ main-axis sizes plus any gap values and subtracts that from the container’s main-axis content-box size to get the leftover main-axis space.
  4. justify-content is applied to that leftover space — deciding whether it goes before the first item, after the last item, is split evenly between items, or some other distribution.
  5. Independently, for the cross axis, the browser looks at the tallest (or widest, if column) item on each flex line, which typically determines the line’s cross-axis size unless the container has an explicit fixed size.
  6. align-items positions each item’s box within that cross-axis space — or, if the value is stretch and the item has no fixed cross-axis size, it grows the item to fill it.
  7. If any item declares align-self, that value replaces align-items for that one item only, all other computation stays the same.
  8. If there are multiple flex lines (via flex-wrap), align-content runs last, distributing the lines themselves within the container’s remaining cross-axis space.

Common Mistakes

Mistake 1: Forgetting display: flex

justify-content and align-items only have meaning inside a flex (or grid) formatting context. Setting them on a normal block element does nothing at all — the browser simply ignores them because the element’s children are laid out in normal block flow, not along flex axes.

.wrong-container {
  justify-content: center;
  align-items: center;
  height: 200px;
}

Nothing will visually change here because display: flex is missing. The fix is to always declare display: flex in the same ruleset:

.fixed-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 200px;
}

Mistake 2: Assuming align-items always means “vertical”

Because in the default row layout align-items happens to control vertical position, beginners often assume it’s always the “vertical” property. But it tracks the cross axis, not a fixed direction. In a column layout, it’s justify-content that becomes vertical and align-items that becomes horizontal.

.wrong-column {
  display: flex;
  flex-direction: column;
  align-items: center;
}

A developer expecting this to vertically center items in a tall column container will be surprised: with flex-direction: column, align-items: center only centers items horizontally. To actually center them vertically in a column layout, justify-content is the property to reach for:

.fixed-column {
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 400px;
}

Best Practices

  • Always pair display: flex with the alignment properties in the same ruleset so the dependency is obvious at a glance.
  • Before choosing values, ask which direction is your main axis right now — check flex-direction first, since it determines what justify-content and align-items physically do.
  • Use gap instead of margins on individual items for spacing between flex children — it doesn’t add extra space at the container’s outer edges the way space-around/margin combinations can accidentally double up.
  • Reach for align-self when one item needs to break from the group’s alignment, rather than wrapping it in an extra container.
  • Give the flex container an explicit height (or let it inherit one) when you want align-items‘s vertical effect to be visible in a row layout — a container that only grows to fit its content has no leftover cross-axis space to distribute.
  • Remember align-content is a no-op with a single flex line; if wrapping isn’t enabled via flex-wrap: wrap, don’t reach for it expecting an effect.

Practice Exercises

  1. Build a full-height hero section (height: 100vh) with a heading and a button, both centered horizontally and vertically using only display: flex, justify-content, and align-items.
  2. Create a three-column pricing table container using flex-direction: row and justify-content: space-evenly so the three cards have perfectly equal spacing on all sides, including the container’s own edges.
  3. Take the column-direction card from Example 3 and modify it so the badge sits at the top-left instead of the top-right, without changing align-items on the parent — only by changing the one align-self value on .badge.

Summary

  • justify-content aligns items along the flex container’s main axis; align-items aligns items along the cross axis.
  • Which physical direction is “main” depends entirely on flex-direction — switching to column swaps what each property visually controls.
  • Both properties require display: flex on the parent; they have no effect otherwise.
  • align-self, set on an individual item, overrides the container’s align-items for that item alone.
  • align-content is a separate property that only matters with multiple wrapped flex lines.
  • space-between, space-around, and space-evenly each distribute leftover main-axis space differently — know the difference at the edges.