Scroll Behavior
When navigating between routes, @esmx/router automatically manages scroll position to match user expectations. Pushing to a new page scrolls to the top; going back restores the previous scroll position. This mirrors how traditional multi-page websites behave.
Default Behavior
The router handles scrolling differently based on the navigation type:
push: Scrolls to top(0, 0)replace: Scrolls to top(0, 0)back: Restores saved scroll positionforward: Restores saved scroll positiongo(n): Restores saved scroll positionpushWindow: Handled by browserreplaceWindow: Handled by browser
This works out of the box with no configuration needed.
How Scroll Positions Are Saved
When leaving a page (via push, replace, or history navigation), the router saves the current scroll position using two mechanisms:
- In-memory map: A
Map<string, ScrollPosition>keyed by the page's full URL - History state: The position is also stored in
history.stateunder the__scroll_position_keyproperty
Storing in history.state means scroll positions survive page refreshes — when the user refreshes and then navigates back, the correct scroll position can still be restored.
Manual Scroll Restoration
The router sets history.scrollRestoration = 'manual' automatically. This tells the browser not to attempt its own scroll restoration, leaving full control to the router.
This is configured during the confirm phase of navigation — you don't need to set it yourself.
Keeping Scroll Position
Sometimes you don't want navigation to scroll to the top. For example, when switching tabs or filtering content, the user expects to stay where they are:
When keepScrollPosition is set to true:
- The page does not scroll to top
- The current scroll position is not saved (since we're staying at the same position)
- The
__keepScrollPositionflag is stored inhistory.state
This flag is also checked during back/forward navigation — if the target history entry was created with keepScrollPosition: true, scroll restoration is skipped.
Scroll to Element
The scroll system supports scrolling to a specific element on the page using a CSS selector. You can specify the target via the el property:
- CSS selector strings (e.g.,
'#my-id','.my-class','[data-section]') - DOM
Elementreferences
If you need to scroll to an element after navigation, use the afterEach hook with the native scroll API:
Layers and Scroll
Routes opened via layer routing (using pushLayer or createLayer) skip scroll handling entirely. Since layers render in an overlay on top of the current page, scrolling the background page would be disruptive:
This behavior is built into the router's confirm phase — scroll logic is skipped when router.isLayer is true.
Scroll Position Flow
Here's the complete flow of how scroll is handled during different navigation types:
push / replace
back / forward / go
Window Navigation (pushWindow / replaceWindow)
Summary
- Scroll to top on push/replace: Enabled by default. Pass
keepScrollPosition: trueto disable. - Restore scroll on back/forward: Enabled by default. Uses saved positions automatically.
- Browser scroll restoration: Disabled (
'manual'). Set automatically by router. - Layer scroll handling: Skipped. Automatic for layers.
- Persist across page refresh: Via
history.state. Automatic.