Limited Time Offer: Get 50% OFF Starter & Basic Yearly Plans 🎉

Align-Content vs Align-Items: A Practical CSS Layout Guide

Oct 15, 2026

Why Cross-Axis Alignment Trips Up Even Careful Developers

Two properties, nearly identical names, the same keyword vocabulary, and completely different responsibilities. align-items positions items inside a single line. align-content distributes leftover space between multiple lines. That one sentence contains the whole distinction, and everything else in this guide is elaboration on it.

The reason the confusion persists is that both properties fail silently. There is no browser warning, no editor squiggle, no console message. A rule that produced a visible result an hour ago simply stops mattering after an unrelated edit: adding a column direction, removing wrapping, swapping a fixed height for content-based sizing. Developers reasonably conclude that the property is broken, or that browsers behave inconsistently, when in fact the specification is being honored precisely.

Flexbox and Grid are now the default layout methods on the web, which means nearly every interface depends on cross-axis decisions. Card grids, toolbars, form rows, hero panels, dashboards, pricing tables, media galleries, and player chrome all ask the same two questions. How should an item sit inside its row? How should the rows themselves be spread across the available height? Answer the first with align-items, the second with align-content, and most layout bugs evaporate before they are written.

This guide builds the mental model from the container outward, compares both properties directly, maps the same ideas onto CSS Grid, and closes with a debugging playbook, worked patterns, and a long FAQ. Code samples are plain CSS, so they can be dropped into any stack without adaptation.

The Mental Model: Axes, Lines, and Free Space

Before touching either property, fix three ideas in your head: the axes, the concept of a line, and the role of leftover space. Nearly every "why is this not working" question resolves into one of those three.

Main axis and cross axis

Every layout container built with Flexbox or Grid has two directions. The main axis is the direction in which the container arranges its children. The cross axis runs perpendicular to it. justify-content and justify-items work along the main axis; align-content and align-items work along the cross axis.

In a default Flexbox row, the main axis is horizontal, so the cross axis is vertical. Vertical alignment questions therefore land on the two align- properties. Switch to flex-direction: column and the axes rotate: the main axis becomes vertical, justify-content starts controlling vertical spacing, and align-items starts controlling horizontal placement. The property names never change, but their visual effects swap. This single fact explains a large share of column-layout bugs.

What counts as a line

A line is a set of items that share the same cross-axis position. In a non-wrapping Flexbox container, every child sits on one line, so the container has exactly one line no matter how many children it holds. With flex-wrap: wrap, a container that runs out of horizontal room breaks into two, three, or a dozen lines, each behaving like a row.

The word "line" is where intuition usually slips. People hear "lines" and think of items arranged in a row, or of text lines, or of grid columns. The useful definition is narrower: a line is a group of items that the container decided to place together along the cross axis. Once wrapping is enabled, the container alone decides how many groups exist, and its decision depends on container width and the intrinsic sizes of the items.

The three conditions that make an alignment value visible

For align-content to change anything on screen, three things must be true at once:

  • the container wraps, or a grid has more than one row track
  • there is more than one line or track
  • there is unused space along the cross axis

Miss any one of them and the declaration is inert. For align-items, only the first condition is irrelevant: it works in single-line containers, which is exactly where it is used most.

Free space is a prerequisite, not a detail

Space distribution only exists when there is space to distribute. A wrapped container whose lines already fill its height has nothing left over, so switching align-content from flex-start to space-between changes nothing at all. This is not a quirk, it is arithmetic. The fastest way to confirm the diagnosis is to give the container a temporary min-height and a background color. If a colored band appears below the content, distribution has room to act. If the band never appears, you have found the answer without editing the property.

align-items in Depth: Placing Items Inside a Line

align-items is declared on the container and sets the default cross-axis position for every child. Reach for it whenever the layout has one line, or when you care about how items sit inside each wrapped row.

The values and what each one does

  • stretch — the default. Items with an automatic cross-axis size expand to fill the line. This is why cards in a row end up equal in height without any explicit height rules.
  • 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 center within the line.
  • baseline — items align on their first text baseline, which is the right choice when type of different sizes shares a row.

Grid uses nearly the same vocabulary, with start and end in place of flex-start and flex-end, plus stretch, center, and baseline.

Choosing between center, baseline, and stretch

Consider a toolbar holding an icon, a label, and a button. With align-items: center, all three are optically centered against the tallest element. With baseline, the label text and the button text sit on a shared typographic line, which usually looks tidier when font sizes differ. With stretch, the button grows to the full toolbar height, which is what you want when the whole bar should read as a single hit target.

A practical rule: use baseline when the row is mostly text, center when the row mixes icons and controls, and stretch when uniform block heights matter more than optical balance.

What align-items cannot do

It has no awareness of the container as a whole, so it cannot create or remove space between wrapped rows. If your card grid has an awkward vertical band between the first and second row, align-items is the wrong tool. That band is a line-distribution problem, which is the next section.

align-self overrides one child

align-self lives on an individual child and overrides the container default for that element only. It accepts every value align-items accepts, plus auto, meaning "inherit the container setting." There is no equivalent for line distribution, because spreading lines apart is inherently a container-level decision. A common use case is a toolbar where one action should sit at the bottom edge:

.toolbar {
  display: flex;
  align-items: center;
}

.toolbar .primary-action {
  align-self: flex-end;
}

align-content in Depth: Spacing Multiple Lines

The cleanest way to think about align-content is as justify-content rotated ninety degrees. Where justify-content distributes free space between items along the main axis, align-content distributes free space between lines along the cross axis.

The activation checklist

Before debugging the property itself, verify the container state:

  1. Is display set to flex or grid? A block container ignores all alignment properties.
  2. Does the Flexbox container have flex-wrap: wrap? Without it, there is only one line.
  3. Does the container actually have unused cross-axis space? Add a temporary height to check.
  4. Are the lines fixed in size? If each line has a hard-coded height, stretch has nothing to expand.

Value-by-value behavior

  • flex-start — all lines packed at the cross-axis start, empty space at the end.
  • flex-end — all lines packed at the end of the cross axis.
  • center — lines grouped in the middle of the container.
  • space-between — first line flush with the start, last line flush with the end, remaining space split between them.
  • space-around — equal space around each line, with half-sized space at the outer edges.
  • space-evenly — identical space between every pair of lines and at both container edges.
  • stretch — the default; lines grow to absorb leftover space when they have no fixed cross-axis size.

The stretch default surprises people in both directions. In Flexbox, items with align-self: auto and no explicit height grow to fill the line, so a wrapped gallery with align-content: stretch may look identical to flex-start until you give the container extra height. In Grid, fractional row tracks already consume the available height, so align-content appears to do nothing because there is genuinely nothing left to distribute.

The fix is the same in both cases: identify what already owns the space. If tracks or items are sized to fill the container, distribution has no room to operate.

.card-grid {
  display: flex;
  flex-wrap: wrap;
  align-content: space-between;
  align-items: center;
  gap: 1rem;
  min-height: 60vh;
}

Remove flex-wrap: wrap and the first property stops mattering. Remove min-height and space-between has nothing to work with. Both edits look harmless and both break the effect, which is why align-content has a reputation for unreliability when it is in fact extremely predictable.

Comparing Both Properties: A Decision Framework

Question align-items align-content
Scope Inside one line Between multiple lines
Where it is declared Container Container only
Needs wrapping No Yes
Needs free cross-axis space No Yes
Affects a single child No, use align-self No
Typical Flexbox use Centering a toolbar row Spreading wrapped rows
Typical Grid use Aligning items in a row track Distributing row track spacing
Main values stretch, flex-start, flex-end, center, baseline stretch, flex-start, flex-end, center, space-between, space-around, space-evenly

A plain-language decision tree

Answer these questions in order:

  • How many lines does the container have? One line means align-items is your tool and align-content is irrelevant.
  • If there are several lines, is there unused cross-axis space? If not, neither property will produce a visible change.
  • Within each line, how should items sit? That is align-items or a per-item align-self.
  • Between lines, how should space be distributed? That is align-content.

How the two properties combine

They do not compete. align-items resolves first, shaping the internal composition of every line. align-content then positions the finished set of lines inside the container's leftover space. You can safely declare both, and many production layouts do.

When gap is the better answer

A lot of historical advice reached for space-between or space-around to create item spacing. Modern gap handles that job in both Flexbox and Grid, works with wrapping, and does not depend on leftover space existing. Use gap for spacing between items, and reserve align-content for spacing between lines.

The Supporting Cast and What It Fixes

justify-content is the other axis

A frequent mix-up is reaching for align-content when the goal is horizontal spacing in a row. In a non-wrapping row, the main axis is horizontal, so horizontal distribution belongs to justify-content. The familiar "two items pushed to opposite edges" pattern is justify-content: space-between on the container, not an alignment property on the children.

auto margins as an escape hatch

Auto margins absorb all remaining space in the direction they are applied. margin-top: auto on a Flexbox child pushes it to the bottom of its line. margin-left: auto pushes it to the right. This works regardless of line count, which makes it a useful fallback when the alignment properties are misbehaving or when the layout is too dynamic for container-level defaults.

safe alignment keywords

When a centered item is taller than its container, center can push content outside the visible area in a way the user cannot scroll to. The safe keyword handles this: an alignment written as safe center falls back to start alignment whenever centering would cause overflow. It is worth knowing for short containers whose content might grow unpredictably, such as toolbars, chips, and compact cards.

align-self for one-off exceptions

Any child can opt out of the container's align-items with align-self. This is the right tool when exactly one element needs different treatment, and it keeps the container rule clean for everyone else.

Mapping the Same Ideas Onto CSS Grid

Grid reuses align-items and align-content, but the container anatomy is different enough to deserve its own pass.

Tracks replace lines

A grid has explicit row tracks rather than implicit wrapped lines. Where Flexbox lines appear only after wrapping, grid rows exist because you declared them or because items were auto-placed into them. That makes align-content relevant far more often in Grid than in Flexbox.

Items, tracks, and the whole grid

  • align-items positions each item inside its own grid area along the block axis.
  • align-self overrides that for a single item.
  • align-content distributes free space among row tracks when the grid is shorter than its container.
  • justify-items, justify-self, and justify-content are the inline-axis equivalents.

A row of three cards with different content lengths is the canonical example. With the default stretch, all three cards match heights. With align-items: start, each card takes its natural height and hangs from the top of the row, which suits editorial layouts where unequal heights read as intentional rather than accidental.

Shorthand forms worth memorizing

place-items: center expands to align-items: center and justify-items: center. place-content: center expands to align-content: center and justify-content: center. The classic centering recipe is display: grid plus place-items: center on the parent, with no changes needed on the child. The Flexbox equivalent requires both justify-content: center and align-items: center.

Both shorthands accept two values, in the order align then justify, so a single declaration can set a start alignment on the block axis and a centered alignment on the inline axis.

Grid-specific gotchas

  • Fractional rows can consume the entire container height, leaving align-content nothing to distribute.
  • An auto-fit column pattern with a minimum width makes the number of rows width-dependent, so alignment behavior varies across viewports.
  • A grid with height: 100% inside an auto-height parent often collapses, which removes the free space that distribution depends on.

A Debugging Playbook for Stubborn Layouts

When an alignment declaration seems to do nothing, work through this list in order. It resolves the great majority of cases in under a minute.

Confirm the display mode

Inspect the computed display value. A block or inline-block container ignores the alignment properties. Also check whether inline-flex is in play, since an inline container sizes itself to content and may eliminate the leftover space you were expecting.

Confirm the number of lines

For Flexbox, verify the computed flex-wrap value, then drag the container narrower in the inspector and watch whether the layout actually breaks into multiple rows. If it never wraps, align-content can never apply.

Confirm free space exists

Give the container a temporary background color and a height. A visible band below the content means distribution is possible. No band means the answer is already clear.

Check for competing declarations

Shorthands and utility classes quietly overwrite each other. place-items, place-content, and framework helpers all compile down to the longhands. Inspect computed values rather than authored rules, and remember that later rules with equal specificity win.

Look for fixed cross-axis sizes

stretch does nothing when the item has an explicit height, and line-level stretch does nothing when lines are fixed. Hard-coded heights are the most common silent killer of stretch-based strategies.

Test at realistic content sizes

Wrap behavior depends on intrinsic content sizes. A layout verified with three identical placeholder cards tells you very little about how it behaves with one card holding a two-line title and another holding a six-line description. Test with the tallest realistic content in each component.

Real-World Patterns, a Workflow, and Common Mistakes

A gallery with flex-wrap: wrap, a min-height, and align-content: space-between pushes the last row toward the bottom of its container while the earlier rows spread evenly. The companion align-items: center keeps captions vertically centered inside each card.

Pattern: dashboard widgets of unequal height

A grid of widgets using an auto-fit column pattern with align-items: stretch produces uniform tiles. Switching to align-items: start lets chart-heavy widgets take extra height and table-heavy widgets stay compact. Adding align-content: start keeps rows packed at the top instead of stretching the final row across the viewport.

Pattern: single-line hero content

A hero with one line of content needs only display: flex, align-items: center, justify-content: center, and a min-height. align-content is irrelevant because there is one line. Recognizing a one-line container is often the fastest way to stop chasing the wrong property.

Pattern: form rows with mismatched control heights

Inputs, selects, and buttons rarely share a natural height. align-items: baseline aligns their text baselines so the row reads as one typographic line. align-items: stretch equalizes heights, which suits dense toolbars better than text-heavy forms.

A repeatable workflow

  1. Decide whether the container needs to wrap. No wrapping means align-items for cross-axis placement and justify-content or gap for main-axis spacing.
  2. If it wraps, add gap for item spacing and align-items for placement inside each row.
  3. Only then consider align-content, and only if there is leftover cross-axis space.
  4. Verify by temporarily coloring the container and giving it a height.
  5. Test with the tallest realistic content and at two or three viewport widths.
  6. Remove the temporary debug styles before committing.

Mistakes worth avoiding

  • Using align-content on a container that never wraps.
  • Forgetting that stretch is the default, which explains most unexplained equal heights.
  • Confusing axes after switching to a column direction.
  • Expecting align-content to space out items inside a single row.
  • Rebuilding spacing with margins when gap already solves it.
  • Verifying a layout at only one viewport width.
  • Trusting authored CSS over computed values when a framework utility is in the mix.

FAQ

Does align-items require wrapping?

No. It applies to single-line containers, which is where it is used most. It positions items within their line along the cross axis regardless of how many lines exist.

Why does align-content do nothing in my Flexbox container?

Almost always one of three causes: the container does not wrap, it has only one line, or the lines already fill the container so no free space remains. Fix any of those and the property becomes active.

What is the difference between align-items and justify-items?

align-items works along the cross or block axis, while justify-items works along the inline axis. In a default Flexbox row that means vertical versus horizontal. Grid supports both; Flexbox relies mainly on align-items and handles main-axis behavior through justify-content, gap, and auto margins.

Can align-content and align-items be used together?

Yes, and they frequently are. align-items shapes each line internally, then align-content positions the resulting set of lines within the container's leftover space. There is no conflict because they act at different scopes.

How do I align one item differently from the rest?

Use align-self on that child. It accepts the same values as align-items and overrides the container default for that element. Setting it to auto restores inherited behavior.

When should I use baseline instead of center?

Baseline is better whenever text of different sizes appears in the same row, because it aligns the first text baseline rather than the geometric center. Center is better for icons, badges, and controls that are not primarily text.

Is place-items the same as align-items plus justify-items?

Yes. place-items: center sets both alignment properties to center. The shorthand accepts two values in align-then-justify order, so one declaration can combine different values on each axis.

Why did alignment change when I switched to a column direction?

Because the main and cross axes swapped. With a column direction, vertical spacing belongs to justify-content and horizontal placement belongs to align-items. The names stay the same while their on-screen effects rotate ninety degrees.

Do auto margins make the alignment properties unnecessary?

They are a complement, not a replacement. Auto margins are excellent for pushing a single element to an edge, but they cannot distribute space across many lines the way align-content can.

What is the shortest way to remember all of this?

align-items aligns items inside a line; align-content aligns lines inside a container. If you can answer "how many lines exist, and is there unused space," you already know which property to use.

Alexander

Alexander