Scroll Position Memory Across Tab Switches — Each Panel Remembers Where You Left Off

Scroll Position Memory Across Tab Switches · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Each panel remembers its own scroll position completely independently of every other panel
Scroll position is saved at the precise last moment a panel is still visible, right before it's hidden
Scroll position is restored only after a panel becomes visible again, avoiding unreliable scrollTop writes on a hidden element
No debounced scroll listener, requestAnimationFrame loop, or external library — the entire mechanism is two well-timed lines
Works correctly regardless of how many tabs exist or how many times a user switches back and forth between them
Tab buttons maintain proper ARIA state (aria-selected) in sync with which panel is actually visible
Each panel is populated with enough content to be genuinely scrollable, making the memory effect clearly demonstrable

About this UI Snippet

Scroll Position Memory Across Tabs — Getting the Timing Right

Screenshot of the Scroll Position Memory Across Tab Switches snippet rendered live

Switching tabs and finding your scroll position reset to the top is a small but genuinely annoying UX papercut — you were fifteen posts deep in a feed, glanced at another tab, and now you're scrolling back down from scratch. The fix is conceptually simple (remember a number, restore it later) but has exactly one timing detail that determines whether it actually works: *when* you save and *when* you restore.

One scroll position per panel, stored independently

scrollMemory is a plain object keyed by tab name, holding each panel's last known scrollTop value. There's no shared or global scroll state — each panel's memory is entirely independent of the others, which is what allows switching between three, five, or any number of tabs to correctly preserve each one's own distinct scroll position simultaneously, not just "the last tab you were on before this one."

Saving happens at the exact moment a panel is about to be hidden, not earlier

switchTab() reads activePanel.scrollTop and writes it into scrollMemory as the very first thing it does — *before* hiding that panel. This is deliberate: it would be tempting to instead save scroll position continuously via a debounced scroll event listener, but that adds unnecessary complexity and a class of subtle staleness bugs (what if the debounce hasn't fired yet at the exact moment of a fast tab switch?) for zero actual benefit — the only moment that genuinely matters is the last one before the panel disappears, so that's the only moment the code reads it.

Restoring happens only after the panel becomes visible again, not before

Setting scrollTop on an element while it's still hidden (or display: none) is unreliable — a hidden element generally has no meaningful scrollable viewport to apply a scroll position to yet. switchTab() unhides the incoming panel (nextPanel.hidden = false) *before* setting its scrollTop, guaranteeing the browser has a genuinely visible, laid-out scrollable element to apply the restored position to at the moment it's set, rather than trying to restore scroll on an element that isn't rendered yet.

Why this is simpler than it might first seem

There's no scroll-tracking library, no requestAnimationFrame loop, no MutationObserver — the entire mechanism is two lines: read scrollTop right before hiding, write scrollTop right after showing. The apparent complexity of "remembering scroll position" mostly comes from getting these two moments right, not from the storage mechanism itself, which is intentionally as plain as possible.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain precisely why setting scrollTop on a hidden element is unreliable across browsers, and why saving scroll position at the moment of switching (rather than via a continuously-running scroll listener) is both simpler and avoids a real staleness edge case. It's also worth asking for a version that persists scroll memory across full page reloads using sessionStorage, or one that also handles a dynamically added/removed set of tabs rather than a fixed, known set of three.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a tabbed interface where each panel independently remembers and restores its own scroll position when switching between tabs, using HTML, CSS, and vanilla JavaScript — no external library.

Requirements:
- At least three tabs, each with its own scrollable panel containing enough list content to require scrolling to see it all.
- Store each panel's scroll position independently, keyed by its own tab identifier, in a plain in-memory object — not a single shared "last scroll position" value.
- When switching tabs, save the outgoing panel's current scroll position at the exact moment just before hiding it (not via a continuously running or debounced scroll listener), then hide it.
- Show the incoming panel, and only AFTER it becomes visible, restore its previously saved scroll position (defaulting to the top if it has never been visited before) — do not attempt to set scroll position while the panel is still hidden.
- Verify the pattern works correctly regardless of the order tabs are switched in, and that each tab's scroll memory remains independently correct even after switching through several other tabs in between visits to it.
- Keep tab button ARIA state (aria-selected) correctly in sync with whichever panel is currently visible.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Scroll down in the Feed tabScroll partway through the list of feed items.
  2. 2
    Switch to the Following tabIt opens at its own top (or wherever it was last left, on a subsequent visit) — Feed's scroll position is saved the instant you switch away from it.
  3. 3
    Switch back to FeedIt restores to exactly the scroll position you left it at, rather than resetting to the top.
  4. 4
    Scroll in Following, then check Saved, then return to FollowingEach panel maintains its own independent scroll memory simultaneously — switching through multiple tabs doesn't interfere with any of their individually remembered positions.
  5. 5
    Adapt scrollMemory for your own tabbed contentApply the same save-on-hide, restore-after-show pattern to any tabbed or panel-swapping UI where scroll position should persist per panel.

Real-world uses

Common Use Cases

FEED
Social feed and content tabs
Feed, Following, and Saved-style tabbed content views where users frequently switch tabs and expect their reading position preserved.
SETTINGS
Multi-tab settings or dashboard panels
Any tabbed interface with independently scrollable panel content benefits from per-panel scroll memory.
INBOX
Inbox category tabs
Email or notification inboxes with category tabs (Primary, Social, Promotions) where scroll position loss between tabs is a common frustration.
DOCS
Multi-section documentation viewers
Tabbed documentation or help content where users bounce between related sections and expect to return to where they left off.
Related: Scroll Transformation Story
See the Scroll Transformation Story for a related scroll pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Continuously tracking scroll via a debounced listener adds unnecessary complexity and a class of subtle staleness bugs if a fast tab switch happens before the debounce fires. Since the only moment that actually matters is the very last one before the panel disappears, reading scrollTop at exactly that moment is simpler and strictly correct — there's no benefit to tracking it any earlier or more frequently.

Setting scrollTop on a hidden (or display:none) element is unreliable, since the element generally has no meaningful scrollable viewport at that point. Unhiding the panel first guarantees the browser has a genuinely visible, laid-out element to apply the restored scroll position to.

Each tab's scroll position is stored independently in the scrollMemory object, keyed by that tab's own name. Switching through several tabs in any order preserves every one of their individual scroll positions simultaneously — there is no single shared value that gets overwritten by whichever tab was visited most recently.

scrollMemory[nextTab] is undefined the first time, and the code falls back to 0 (the top) via the || 0 default — so a panel with no prior recorded scroll position simply opens at its natural starting point.

Yes — the scrollMemory object and the save/restore logic are entirely generic and keyed by tab name, so the same mechanism scales to any number of tabs without any structural changes.

Serialize the scrollMemory object to sessionStorage (or localStorage, depending on how long you want it to persist) whenever it changes, and read it back to initialize scrollMemory on page load — the save/restore timing logic itself would remain unchanged.