diff --git a/docs/01-app/01-getting-started/04-linking-and-navigating.mdx b/docs/01-app/01-getting-started/04-linking-and-navigating.mdx index 984fc5225d954f..5492fc3489784c 100644 --- a/docs/01-app/01-getting-started/04-linking-and-navigating.mdx +++ b/docs/01-app/01-getting-started/04-linking-and-navigating.mdx @@ -438,3 +438,55 @@ export function LocaleSwitcher() { ) } ``` + +### Scrolling with sticky headers + +When navigating between routes, Next.js automatically scrolls to the top of the new content if it's not already visible. During this process, Next.js skips `sticky` and `fixed` positioned elements (like a navigation bar) to find the first scrollable content element. + +This can cause content to appear behind a sticky header after navigation. You can fix this using the CSS [`scroll-padding-top`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding-top) property, which tells the browser to account for the sticky element's height when calculating scroll positions: + +```css filename="app/globals.css" +html { + scroll-padding-top: 64px; /* Height of the sticky header */ +} +``` + +```tsx filename="app/layout.tsx" switcher +import './globals.css' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + +
+ {/* Sticky navigation */} +
+ {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import './globals.css' + +export default function RootLayout({ children }) { + return ( + + +
+ {/* Sticky navigation */} +
+ {children} + + + ) +} +``` + +This also works for hash fragment (`#id`) scrolling. See the [`` scroll prop](/docs/app/api-reference/components/link#scroll) for more details on how Next.js handles scroll behavior. diff --git a/docs/01-app/03-api-reference/02-components/link.mdx b/docs/01-app/03-api-reference/02-components/link.mdx index 3a0cca73aeb1a4..0f506241958d6f 100644 --- a/docs/01-app/03-api-reference/02-components/link.mdx +++ b/docs/01-app/03-api-reference/02-components/link.mdx @@ -234,6 +234,20 @@ When `scroll = {false}`, Next.js will not attempt to scroll to the first Page el > **Good to know**: Next.js checks if `scroll: false` before managing scroll behavior. If scrolling is enabled, it identifies the relevant DOM node for navigation and inspects each top-level element. All non-scrollable elements and those without rendered HTML are bypassed, this includes sticky or fixed positioned elements, and non-visible elements such as those calculated with `getBoundingClientRect`. Next.js then continues through siblings until it identifies a scrollable element that is visible in the viewport. +#### Handling sticky headers with `scroll-padding-top` + +Because Next.js skips `sticky` and `fixed` positioned elements when scrolling, it scrolls to the first visible non-sticky content element. If you have a sticky header (e.g., a navigation bar), the page content may appear to scroll "behind" the header, making it look like the scroll didn't go all the way to the top. + +To fix this, use the CSS [`scroll-padding-top`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding-top) property on the scroll container (typically `html`) to account for the height of the sticky element: + +```css filename="app/globals.css" +html { + scroll-padding-top: 64px; /* Height of the sticky header */ +} +``` + +This tells the browser to offset any scroll-based positioning by the specified amount, ensuring content isn't hidden behind the sticky header. This works for both Next.js auto-scrolling and hash fragment (`#id`) scrolling. + ```tsx filename="app/page.tsx" switcher