Web Platform · CSS Layout
Alignment, Overflow, and Scroll Containers
How alignment distributes free space inside a box, what overflow values do to the box model, and why one axis cannot stay visible when the other scrolls.
Alignment and overflow are usually taught separately, but they answer the same question from opposite sides: what happens to a box when its content does not exactly fill the space it was given. Alignment distributes a surplus. Overflow decides what happens to a deficit. The two interact through the scroll container, which is the mechanism that turns a deficit into scrollable territory rather than visible spill.
The interaction is governed by a small number of rules. Free space is distributed according to the alignment properties before overflow is considered; when the content is larger than the box, alignment behaves as if the content were start-aligned unless a safe or unsafe keyword says otherwise; and an overflow value on one axis can force a computed value on the other.
The four alignment properties
Box alignment is expressed through a small vocabulary that applies across layout modes:
justify-contentandalign-contentdistribute space between and around the contents of a container along the inline and block axes respectively.justify-itemsandalign-itemsset the default position of each item within its grid area or flex line.justify-selfandalign-selfoverride that per item.place-content,place-itemsandplace-selfare shorthands setting both axes.
The values fall into two groups. Positional values (start, end, center, flex-start, flex-end, self-start, self-end) place the whole group of content at one position and leave the remaining free space in one piece. Distribution values (space-between, space-around, space-evenly, stretch) divide the free space into gaps between or around the items.
space-between puts the first item at the start edge and the last at the end edge, with equal gaps between. With a single item there is nothing to put between, so the item sits at the start. space-around gives each item equal space on both sides, so the gaps between items are twice the space at the edges. space-evenly gives equal space everywhere, edges included. stretch grows auto-sized items to fill the container, and it is what the initial value normal resolves to for align-items in flex and grid.
align-content only has an effect when there is free space in the cross axis. In a single-line flex container, the line already fills the container, so align-content does nothing and align-items positions the items within that line. In a grid with rows that exactly fill the container, the same is true. This is a frequent reason for a declaration appearing inert: it is being applied to a container that has no surplus to distribute.
Auto margins and the order of resolution
Auto margins take precedence over alignment. An item with margin-left: auto in a flex row absorbs all the free space on its side before justify-content sees any, so the item is pushed to the far end and justify-content: center has nothing left to centre. The same is true in grid: an item with margin: auto inside its grid area is centred in that area and its alignment properties are ignored.
The mechanism is the same one described for block-level centring in used values and the containing block: an auto margin consumes free space, and alignment distributes whatever remains. Auto margins are per item, so they can produce different offsets for different items on the same line, while alignment applies uniformly unless overridden per item.
There is an important asymmetry: auto margins absorb free space but do not absorb negative free space. When content overflows, auto margins compute to zero, and the item overflows at the start edge.
Safe and unsafe alignment
When the content is larger than the container, centring it pushes equal amounts outside both edges. The start of the content becomes unreachable if the container does not scroll, because the overflowing part on the start side is not in the scrollable region; scrollable overflow only extends in the end direction. Keyboard users and screen readers can lose access to the beginning of the content.
The safe and unsafe keywords control this. justify-content: safe center behaves as center when the content fits and as start when it overflows, so the start edge stays visible. unsafe center forces centring even when content overflows, which is the behaviour of plain center.
Plain center is unsafe: overflow is allowed on both sides, and the start of the content can end up outside the scrollable region. Specifying safe states the fallback instead of relying on the plain value’s behaviour. The keyword is accepted on justify-content, align-content, justify-items, align-items and their self forms.
What overflow values actually do
overflow has values visible, hidden, clip, scroll, and auto.
visible paints content outside the box. hidden clips the content and creates a scroll container, but without user-visible scrollbars; the content is nonetheless programmatically scrollable, so scrollTop can be set and scrollIntoView works. clip clips without creating a scroll container at all, so the content is neither scrollable nor reachable; overflow-clip-margin extends the clip region by a specified distance, which allows shadows to escape while content remains clipped. scroll always shows scrollbars for the axis even when nothing overflows. auto shows them only when needed.
The distinction between hidden and clip matters for more than scrollbars. A scroll container establishes a scrollport and becomes the reference for sticky positioning and for scrollIntoView. clip does not, which makes it the correct choice for decorative clipping that should not introduce scroll behaviour. clip also does not create a formatting context, while hidden does, so the margin collapsing rules differ between the two.
Overflow also changes the containing block chain for absolutely positioned descendants only when the scroll container is positioned; scrolling alone does not make a box a containing block.
Why one axis cannot stay visible
Setting overflow-x: hidden; overflow-y: auto does not produce a box that clips horizontally and scrolls vertically. The computed values become hidden auto, and the used behaviour is a two-axis scroll container. The specification requires this because a scroll container with one axis visible would have to paint content outside itself in one direction while clipping and scrolling in the other, and the scrollable overflow region would be ill-defined. The rule is: if one axis is visible and the other is not, the visible value computes to auto.
The practical consequence is that a horizontal clipping trick also clips vertical overflow and may introduce a vertical scrollbar. If the goal is to clip one axis without scrolling, overflow: clip with overflow-clip-margin is the mechanism that keeps the other axis independent. Axes can be independently clipped with overflow-x: clip; overflow-y: visible, since clip does not force the other axis to change.
scrollbar-gutter: stable reserves space for the scrollbar whether or not it is present, which prevents layout shifts when content grows past the scrollport. Without it, a container that gains a vertical scrollbar loses width to the scrollbar and can reflow its contents.
Scroll containers as layout contexts
A scroll container is a box whose overflow is not visible or clip. Its scrollport is the rectangle inside its padding box that the content scrolls within. The scrollable overflow region is the union of the border boxes of the box’s descendants, extended by margins and by some box shadows, transformed out of the scroller’s coordinate space.
Sticky positioning is defined relative to the nearest scroll container. A position: sticky element sticks within its containing block as the scrollport moves, and stops sticking when its containing block’s edge reaches the scrollport’s edge. If an intermediate ancestor has overflow: hidden, that ancestor becomes the scroll container, and the sticky element sticks relative to it, which often means it never appears to stick at all, because the ancestor has no scrolling range. This is the most common reason a sticky header does not work.
Scroll chaining is the propagation of a scroll gesture from a scroll container to its ancestors once the container reaches its limit. overscroll-behavior: contain stops the chain at the container, which prevents a page from scrolling when an inner panel reaches its end. overscroll-behavior: none also disables the browser’s overscroll effects such as pull-to-refresh on the container.
/* One axis scrolls; the visible value on the other computes to auto. */
.panel {
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
}
/* Clip horizontally without creating a scroll container at all. */
.strip {
overflow-x: clip;
overflow-y: visible;
}
Scroll snap changes the relationship between the scrollport and the content by requiring the scroll position to settle on defined points. scroll-snap-type on the container and scroll-snap-align on the children define those points; the browser adjusts the final position after a scroll, which means programmatic scrolling and layout changes can produce snap adjustments that are not visible in the scroll offset arithmetic.
Overflow interacts with the sizing of grid and flex items. A grid item with overflow: auto and a min-content contribution larger than its track has its automatic minimum suppressed, so it can shrink and scroll instead of forcing the track wider. An item with overflow: visible keeps the automatic minimum and widens the track. That single difference decides whether a data table inside a grid track forces the whole layout wider or scrolls inside its cell.