From ec28a7b686f1ed622235ca02e7e725dbdc760561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Thu, 26 Feb 2026 16:00:12 +0100 Subject: [PATCH] Support scroll to text fragment (#4060) --- .../src/components/hooks/useScrollPage.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/gitbook/src/components/hooks/useScrollPage.ts b/packages/gitbook/src/components/hooks/useScrollPage.ts index d6b2e0366..d57a6da5f 100644 --- a/packages/gitbook/src/components/hooks/useScrollPage.ts +++ b/packages/gitbook/src/components/hooks/useScrollPage.ts @@ -6,16 +6,33 @@ import { useHash } from './useHash'; import { usePrevious } from './usePrevious'; /** - * Scroll the page to the hash or reset scroll to the top. - * Only triggered while navigating in the app, not for initial load. + * Handles scroll behavior when the URL hash changes during client-side navigation. + * + * - If a hash is present, scrolls smoothly to the corresponding element. + * - If the hash is removed, scrolls back to the top of the page. + * + * This hook only reacts to in-app navigations. It avoids interfering with: + * - The browser's native hash scrolling on initial load + * - Scroll-to-text fragments (which cannot be reliably detected) */ export function useScrollPage() { const hash = useHash(); const previousHash = usePrevious(hash); React.useEffect(() => { + // If there's no hash: + // - On initial load, `previousHash` is undefined. + // We do nothing to avoid overriding: + // • Native browser hash scrolling + // • Scroll-to-text fragments (undetectable) + if (previousHash === undefined) { + return; + } + if (hash) { - if (previousHash !== undefined && previousHash !== hash) { + // Only scroll if this is not the initial render + // and the hash actually changed. + if (previousHash !== hash) { const element = document.getElementById(hash); if (element) { element.scrollIntoView({ @@ -27,13 +44,8 @@ export function useScrollPage() { return; } - // On initial load `previousHash` can be undefined, - // but if the URL contains a fragment (hash), - // we don't override native anchor scrolling - if (!previousHash && window.location.hash) { - return; - } - + // If the hash was removed during navigation, + // reset scroll position to the top. window.scrollTo(0, 0); }, [hash, previousHash]); }