skip to content

In CSS scroll snapping, how do scroll-snap-type: y mandatory and y proximity differ, and where do the snap positions come from?

level: middleimportance: nice to knowfreq 30%

answer

  1. two halves: container and children
  2. the children create the positions
  3. one strictness always obeys
  4. one strictness only when close
  5. oversized items become unreachable

basics

~20 s

Snap positions come from scroll-snap-align on the children of a scroll container. With mandatory the scroller must always rest on one of them, so it snaps from anywhere; with proximity it snaps only when the resting position is already close to one.

solid answer

~50 s

Scroll snapping is a two-sided contract. On the scroll container you declare `scroll-snap-type`, naming the axis (`x`, `y`, `block`, `inline`, or `both`) and the strictness; on its children you declare `scroll-snap-align: start | center | end`, which is what actually creates the snap positions — a container with `scroll-snap-type` but no aligned children snaps to nothing. `mandatory` means the scrollport must always come to rest on a snap position, so every scroll ends on one; that is what you want for a full-screen carousel or a paged gallery. `proximity` leaves it to the browser: it snaps only when the natural resting place is near a snap position, otherwise it stays put, which suits a long list where forcing every stop would fight the user. `mandatory` is the riskier one — with items taller than the scrollport, content between snap positions can become impossible to rest on.

code

css · 11 lines
css
.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline-start: 1rem;
}

.carousel > article {
  flex: 0 0 80%;
  scroll-snap-align: start;
}

go deeper

for a junior

Remember the pairing: scroll-snap-type on the scrolling parent, scroll-snap-align on each child, and that the parent must genuinely scroll on that axis.

for a middle

Explain the strictness difference precisely — mandatory always rests on a snap position, proximity snaps only when the resting place is near one — and name scroll-padding and scroll-margin as the geometry adjustments.

for a senior

Weigh the failure modes you have hit in production: oversized snap areas making content unreachable, dynamic content shifting the user, and the accessibility cost of forcing every stop.

for a principal

Decide where snapping belongs in the product at all — which surfaces are genuinely paged, what the fallback experience is when snapping is disabled, and how the component around it exposes state to assistive technology.

## The two halves Scroll snapping needs declarations on both the scroll container and its children, and forgetting the second half is the usual reason "snapping doesn't work". **On the container**, `scroll-snap-type` takes an axis and a strictness: ```css .gallery { overflow-x: auto; scroll-snap-type: x mandatory; display: flex; } ``` Axis keywords are `x`, `y`, `block`, `inline` and `both`. Strictness is `mandatory` or `proximity`; if omitted, `proximity` is used. `scroll-snap-type: none` turns it off. The container must actually be a scroll container — snapping is a property of scrolling, so `overflow` has to be `auto` or `scroll` on the snapping axis. That is the second most common reason nothing snaps. **On the children**, `scroll-snap-align` declares where each item's snap position sits relative to the scrollport: ```css .gallery > * { flex: 0 0 100%; scroll-snap-align: start; } ``` Values are `none`, `start`, `center` and `end` (one value applies to both axes; two set block and inline separately). `start` lines the item's start edge up with the scrollport's start edge — the carousel default. `center` centres it, which is right when items are narrower than the scrollport and you want the focused one in the middle. ## mandatory versus proximity `mandatory` promises that the scrollport will always come to rest at a snap position. Release a flick anywhere and the browser animates to the nearest one. This produces the crisp paged feel of a full-bleed carousel, and it also means the scroller can never stop somewhere ambiguous. That promise is also its danger. If a snap area is larger than the scrollport — an item taller than the visible height — the region between snap positions cannot be rested on, so parts of the content become unreachable: scroll toward them and you are pulled back to a snap position. The same problem appears when content is added dynamically near the current position, which can yank the user somewhere they did not ask to be. `proximity` hands the decision to the browser: it snaps only when the natural end of a scroll lands close enough to a snap position, and otherwise leaves the scroll where it stopped. Nothing becomes unreachable, and the feature degrades into an assist rather than a rule. For long documents, article sections, or lists of variable-height items, `proximity` is the safer default; reserve `mandatory` for genuinely paged surfaces whose items are known to fit the scrollport. ## Adjusting the snap geometry Two more properties tune where the alignment lands: - **`scroll-padding`** on the container insets the scrollport for snapping and for scrolling content into view. This is the fix for a sticky header covering the top of a snapped item: `scroll-padding-block-start: 4rem` moves the effective snapport edge below it. It follows the same shorthand pattern as `padding`, with logical longhands. - **`scroll-margin`** on an item outsets that individual item's snap area, letting one element sit a little differently from its siblings without touching the container. A third, `scroll-snap-stop: always`, prevents a fast flick from skipping past an item: normally a strong gesture can travel several snap positions at once, and `always` forces the scroll to stop at that item. Use it sparingly — it makes long carousels tedious — but it is right for onboarding-style sequences where no step should be jumped. ```css .steps { overflow-y: auto; scroll-snap-type: y mandatory; scroll-padding-block-start: 3.5rem; /* clear the sticky header */ } .steps > section { block-size: 100%; scroll-snap-align: start; scroll-snap-stop: always; } ``` ## Practical cautions - Snapping applies to user scrolling and to scrolls the browser performs; it does not change layout, so a snapped carousel is still an ordinary flex or grid row underneath. - Keyboard and assistive-technology users still need every item reachable. `mandatory` snapping with oversized items is an accessibility problem, not just an aesthetic one. - If a snapping container also nests inside another scroller, containing the chain at its boundary keeps the two from fighting. - Snapping is not a carousel component: there is no notion of "current slide" in CSS, no controls, and no announcement to assistive technology. It is the scrolling behaviour only, and the rest of the component still has to be built.

  • A sticky header covers the top of each snapped section. Which property fixes it?
    `scroll-padding` on the scroll container — typically `scroll-padding-block-start` set to the header's height. It insets the snapport, so `scroll-snap-align: start` lines items up below the header rather than beneath it. The per-item counterpart is `scroll-margin`, which outsets one item's own snap area when only that element needs different treatment.
  • Why can scroll-snap-type: y mandatory make content unreachable?
    Because the scroller must always rest on a snap position. If a snap area is taller than the scrollport, the region between snap positions has no resting place: scrolling toward it pulls you back to the nearest snap position, so that content can never be read. Use `proximity` when item heights are variable or can exceed the scrollport.
  • What does scroll-snap-stop: always change?
    It prevents a scroll gesture from passing that item. By default a fast flick can travel across several snap positions in one go; with `scroll-snap-stop: always` on an item, the scroll must come to rest there first. It is right for sequences where no step should be skipped, and tedious on long carousels where quick traversal is the point.

saying these in an interview costs you the question

  • Sets scroll-snap-type but never scroll-snap-align
  • Forgets the container must actually scroll
  • Uses mandatory for variable-height content
  • Thinks scroll-snap builds a complete carousel
  • Confuses scroll-padding with ordinary padding

context