Scroll-Snap and Scroll Timelines Together
Scroll-snap and scroll timelines read the same scroll offset and never conflict, but they shape it differently: snapping constrains where the scroller comes to rest, and a timeline reports where it currently is. Combine them without thinking about it and you get a progress indicator that jumps between fixed positions, which looks broken and is behaving exactly as specified. This guide explains what is happening and how to choose. It sits under scroll-driven media and gallery effects.
When to use this approach
- A gallery or carousel that should settle on items — snapping is what makes a horizontal row feel deliberate rather than loose, and it pairs naturally with per-item effects.
- A full-screen sectioned page — snap points per section, with a per-section reveal driven by a view timeline.
- Anywhere you already have snapping and are adding an effect — this guide is mostly about the second case, because the interaction is only visible once both exist.
Avoid mandatory snapping on any scroller whose progress is being displayed continuously, and avoid snapping altogether on long documents, where it fights the reader’s ability to stop where they want.
Implementation
1. Choose the snap strictness deliberately
.gallery {
overflow-x: auto;
scroll-snap-type: inline proximity; /* keeps progress continuous */
scroll-timeline: --gallery inline;
}
.gallery > .item {
scroll-snap-align: center;
}
proximity snaps when the scroller comes to rest near a snap point and leaves it alone otherwise, so intermediate positions exist and a bound timeline reports them. mandatory forbids resting anywhere else, which is the stronger interaction design and the one that steps.
2. Match the effect to the strictness
If the scroller carries both a per-item state effect and a continuous indicator, the indicator decides: use proximity, or drop the indicator.
3. Align snap points with where the effect peaks
/* a symmetric focus effect peaks at the centre, so centre-align the snap */
.gallery > .item {
scroll-snap-align: center;
animation: focus linear both;
animation-timeline: view(inline);
animation-range: cover 15% cover 85%;
}
@keyframes focus {
from, to { scale: 0.9; opacity: 0.6; }
50% { scale: 1; opacity: 1; }
}
With start alignment the same effect peaks while the item is still travelling and then shrinks slightly as it settles, which reads as the item deflating on arrival.
4. Account for padding on both sides
.gallery {
scroll-padding-inline: 2rem;
padding-inline: 2rem;
}
scroll-padding moves where snapped items rest; container padding moves the scrollport that view timelines measure against. They usually want the same value, and setting only one of them produces effects that peak slightly off from where items settle.
Verification
Scroll slowly with a trackpad and watch the indicator. Continuous movement means proximity snapping is in effect; movement in jumps means mandatory. This is faster than reading the stylesheet, because a snap type inherited from a shared component is easy to miss.
Then flick quickly and watch what happens at the end of the momentum. Under mandatory snapping the scroller travels to the nearest snap point after the gesture ends, and any timeline reading it animates during that settle — an effect tuned to a slow drag can look abrupt in that final movement.
Check the peak position: pause on a settled item and confirm the effect is at its maximum. A mismatch between snap alignment and range symmetry shows up here as an item that is not quite at full scale when it comes to rest.
Finally, tab through the items. Focus scrolls each into view and triggers a snap, which is a legitimate way for the timeline to advance and a good check that the effect looks right when driven by something other than a gesture.
Edge cases and gotchas
Snapping inherited from a parent. scroll-snap-type applies to the scroll container it is set on, but codebases frequently set it broadly. An unexpectedly stepped indicator is often a snap type nobody remembers adding.
scroll-snap-stop: always. This forbids skipping past snap points during momentum, so a fast flick advances one item at a time. Any timeline bound to the scroller advances in single steps too, which is a much larger behavioural change than the snap type alone.
Snap points and view() progress do not have to agree. The timeline measures visibility, not snap position. An item can be at maximum visibility slightly before or after it snaps, depending on alignment and padding — which is what the alignment section above is really about.
Anchor navigation and snapping. A link to an in-page anchor scrolls the container, which triggers snapping and drives the timeline. On a mandatory-snap scroller the landing position may not be the anchor’s exact position, which shifts where the effect ends up.
Mandatory snapping on a short viewport. If an item is taller than the scrollport, mandatory snapping can make parts of it unreachable — the scroller cannot rest anywhere except snap points, and the middle of a tall item is not one. This is a snapping bug rather than a timeline bug, but it surfaces when a section that worked on desktop is tested on a phone.
Browser-specific notes
All three engines implement snapping and scroll timelines independently, and neither feature is aware of the other — the interaction described here is emergent rather than specified, which is why the behaviour is consistent.
Chromium’s snap settle animation is compositor-driven, so a timeline reading the scroller during the settle advances smoothly. Firefox and Safari also animate the settle, with slightly different durations, so an effect that runs during the settle looks marginally different across engines. Nothing here needs a branch.
Safari’s rubber-band overscroll interacts with mandatory snapping at the ends of a scroller: the container springs back to the final snap point, and progress clamps during the spring. The visible result is the effect holding at its endpoint, which is correct.
Firefox applies scroll-snap-stop: always slightly more eagerly during fast wheel scrolling than the other engines. On a gallery that also drives an effect this makes the animation advance one clear step per gesture there, where Chromium may pass several items.
Frequently Asked Questions
Can I keep mandatory snapping and still have a smooth indicator?
Not from the same scroller — the intermediate positions genuinely do not exist. What you can do is display the indicator discretely to match: a segmented bar with one segment per item, lit according to the current index, reads as intentional where a smoothly-scaling bar that jumps reads as broken. The design change is smaller than it sounds and removes the problem entirely.
Does snapping affect a view() timeline as much as a scroll() one?
It affects both, because both derive from the same scroll offset, but the visible impact differs. A scroll() timeline maps offset directly to progress, so stepping is fully visible. A view() timeline maps a subject’s visibility, and because snapping brings items to consistent positions, per-item progress ends up taking a consistent set of values — which for a state-style effect is exactly what you want.
Should a snapped gallery have overscroll-behavior: contain?
Usually yes. Without it, reaching the end of the gallery propagates the gesture to the page, so a reader flicking through items suddenly finds the whole page scrolling. Containing the overscroll also makes manual testing of the gallery’s own timeline much easier, because one gesture drives one scroller.
Is snapping accessible?
It can be, and it needs attention. Mandatory snapping removes the reader’s ability to rest between positions, which is a problem for anyone using a screen magnifier or reading a tall item on a short viewport. Proximity snapping avoids that entirely, which — along with the timeline behaviour — is why it is the default recommendation in this guide.
Related
- Horizontal scroll gallery with a scroll timeline — the component this most often applies to
- Building Scroll Progress Indicators — the indicator whose behaviour snapping changes
- Horizontal scroll timelines and the inline axis — axis rules for snapped horizontal scrollers
- Stacking cards with sticky positioning — the vertical sectioned pattern where snapping is also common