From bc0ece8eff5b722d3a0c14c4969c241c6679ee1d Mon Sep 17 00:00:00 2001 From: Nic Polumeyv <162764842+Nic-Polumeyv@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:01:16 -0400 Subject: [PATCH 1/3] docs: add scroll management page --- .../docs/30-advanced/66-scroll-management.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 documentation/docs/30-advanced/66-scroll-management.md diff --git a/documentation/docs/30-advanced/66-scroll-management.md b/documentation/docs/30-advanced/66-scroll-management.md new file mode 100644 index 000000000000..5b6c6c146074 --- /dev/null +++ b/documentation/docs/30-advanced/66-scroll-management.md @@ -0,0 +1,41 @@ +--- +title: Scroll management +--- + +When you navigate between pages, SvelteKit mirrors the browser's default behaviour: it scrolls to the top of the page (or to the element with a matching ID, if the link includes a `#hash`), and restores the previous scroll position when you navigate back or forward. You can opt out per link with [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) or per navigation with the `noScroll` option of [`goto`]($app-navigation#goto). + +This applies to the scroll position of the _document_. If your pages scroll inside a custom container instead — for example a layout where `` and `` have `overflow: hidden` and an inner element scrolls, as is common in mobile-style app shells — SvelteKit has no way of knowing which element it should scroll. The container will keep its scroll position when you navigate, and nothing will be restored when you go back. + +Since only your app knows which element scrolls, you can manage the container yourself with [`afterNavigate`]($app-navigation#afterNavigate) and a [snapshot](snapshots) in the layout that owns it: + +```svelte + + + +
+ {@render children()} +
+``` + +On every navigation, `capture` saves the container's scroll position against the outgoing history entry immediately before the page updates, and `afterNavigate` scrolls new pages to the top. On back and forward navigations, `restore` is called with the saved position after the `afterNavigate` callbacks have run, so the two don't conflict. Snapshots are persisted to `sessionStorage`, so the position also survives a reload. + +> [!NOTE] Passing [`behavior: 'instant'`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo) makes the reset immediate even if the container has [`scroll-behavior: smooth`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) in CSS, which would otherwise animate it on every navigation. From 8f1df9904dc39ac86cbb2f88cb81daa130d9ec72 Mon Sep 17 00:00:00 2001 From: Nic Polumeyv <162764842+Nic-Polumeyv@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:09:31 -0400 Subject: [PATCH 2/3] docs: cover hash links and noScroll interplay --- documentation/docs/30-advanced/66-scroll-management.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/docs/30-advanced/66-scroll-management.md b/documentation/docs/30-advanced/66-scroll-management.md index 5b6c6c146074..33a73c4412fe 100644 --- a/documentation/docs/30-advanced/66-scroll-management.md +++ b/documentation/docs/30-advanced/66-scroll-management.md @@ -36,6 +36,8 @@ Since only your app knows which element scrolls, you can manage the container yo ``` -On every navigation, `capture` saves the container's scroll position against the outgoing history entry immediately before the page updates, and `afterNavigate` scrolls new pages to the top. On back and forward navigations, `restore` is called with the saved position after the `afterNavigate` callbacks have run, so the two don't conflict. Snapshots are persisted to `sessionStorage`, so the position also survives a reload. +On every navigation, `capture` saves the container's scroll position against the outgoing history entry immediately before the page updates, and `afterNavigate` scrolls new pages to the top. On back and forward navigations, `restore` is called with the saved position after the `afterNavigate` callbacks have run, so the two don't conflict. Snapshots are persisted to `sessionStorage`, so the position also survives a reload. Links with a `#hash` keep working, since SvelteKit scrolls the targeted element into view within its container after the reset. + +Note that `afterNavigate` callbacks can't tell whether a navigation used [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) or `goto`'s `noScroll` option, so the container is also reset on those navigations. > [!NOTE] Passing [`behavior: 'instant'`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo) makes the reset immediate even if the container has [`scroll-behavior: smooth`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) in CSS, which would otherwise animate it on every navigation. From f1f9105b414c3f7ad6824f1699b33a37939c1d9b Mon Sep 17 00:00:00 2001 From: Nic Polumeyv <162764842+Nic-Polumeyv@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:16:13 -0400 Subject: [PATCH 3/3] docs: drop incorrect ordering claim for hash links --- documentation/docs/30-advanced/66-scroll-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/30-advanced/66-scroll-management.md b/documentation/docs/30-advanced/66-scroll-management.md index 33a73c4412fe..dc026d1efdb0 100644 --- a/documentation/docs/30-advanced/66-scroll-management.md +++ b/documentation/docs/30-advanced/66-scroll-management.md @@ -36,7 +36,7 @@ Since only your app knows which element scrolls, you can manage the container yo ``` -On every navigation, `capture` saves the container's scroll position against the outgoing history entry immediately before the page updates, and `afterNavigate` scrolls new pages to the top. On back and forward navigations, `restore` is called with the saved position after the `afterNavigate` callbacks have run, so the two don't conflict. Snapshots are persisted to `sessionStorage`, so the position also survives a reload. Links with a `#hash` keep working, since SvelteKit scrolls the targeted element into view within its container after the reset. +On every navigation, `capture` saves the container's scroll position against the outgoing history entry immediately before the page updates, and `afterNavigate` scrolls new pages to the top. On back and forward navigations, `restore` is called with the saved position after the `afterNavigate` callbacks have run, so the two don't conflict. Snapshots are persisted to `sessionStorage`, so the position also survives a reload. Links with a `#hash` still end up at the targeted element. Note that `afterNavigate` callbacks can't tell whether a navigation used [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) or `goto`'s `noScroll` option, so the container is also reset on those navigations.