
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.
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.
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.
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.
For align-content to change anything on screen, three things must be true at once:
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.
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 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.
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.
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.
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 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;
}
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.
Before debugging the property itself, verify the container state:
display set to flex or grid? A block container ignores all alignment properties.flex-wrap: wrap? Without it, there is only one line.stretch has nothing to expand.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.
| 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 |
Answer these questions in order:
align-items is your tool and align-content is irrelevant.align-items or a per-item align-self.align-content.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.
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.
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 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.
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.
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.
Grid reuses align-items and align-content, but the container anatomy is different enough to deserve its own pass.
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.
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.
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.
align-content nothing to distribute.height: 100% inside an auto-height parent often collapses, which removes the free space that distribution depends on.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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
align-items for cross-axis placement and justify-content or gap for main-axis spacing.gap for item spacing and align-items for placement inside each row.align-content, and only if there is leftover cross-axis space.align-content on a container that never wraps.stretch is the default, which explains most unexplained equal heights.align-content to space out items inside a single row.gap already solves it.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.
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.
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.
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.
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.
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.
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.
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.
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.
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
Vidu Q4 alternatives compared: Kling 4.0, Seedance 2.5, Wan 3.0, and Runway Gen-4 — 4K support, input modes, durations, audio, and what each costs in credits on Domer.
The top 4K AI image generators on Domer compared: Flux 3 Klein, Nano Banana 2.1, GPT Image 2.5, GPT Image 2, and Nano Banana Pro — 4K credit costs, reference limits, and speed.
Nano Banana 2.1 vs GPT Image 2.5 Flare compared: credit costs per resolution, aspect ratios, reference images, text rendering, and which tasks fit each model.