webdevpuneet.com

Tabs with URL Sync — Free HTML CSS JS Hash-Linked Tab Bar Snippet

Tabs with URL Sync · Navigation · Plain HTML, CSS & JS · Live preview

CategoryNavigation

Copy & paste

Source Code

<div class="tabs" role="tablist" aria-label="URL synced tabs">
  <button class="tab" data-tab="overview" onclick="goToTab('overview')">Overview</button>
  <button class="tab" data-tab="pricing" onclick="goToTab('pricing')">Pricing</button>
  <button class="tab" data-tab="faq" onclick="goToTab('faq')">FAQ</button>
</div>
<div class="panels">
  <div class="panel" data-panel="overview">
    <h3>Overview</h3>
    <p>This tab's state lives in the URL hash. Try refreshing the page after switching tabs.</p>
  </div>
  <div class="panel" data-panel="pricing">
    <h3>Pricing</h3>
    <p>Share a link straight to this tab — <code>#pricing</code> opens it automatically on load.</p>
  </div>
  <div class="panel" data-panel="faq">
    <h3>FAQ</h3>
    <p>Browser back/forward buttons also switch tabs, since each click pushes a new hash entry.</p>
  </div>
</div>
<p class="hash-hint">Current hash: <code id="hashDisplay">#overview</code></p>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Active tab persists across page refresh via location.hash
Shareable deep links — #pricing opens straight to that tab
Native browser back/forward buttons step through tab history automatically
Single readHashAndActivate function handles both clicks and manual URL edits identically
Falls back to a default tab when the hash is missing or invalid
No router or history.pushState bookkeeping required — hashchange does the work
Same underline-indicator styling as a standard tab bar for visual consistency
Scales to any number of tabs by extending one validation array

About this UI Snippet

Tabs with URL Sync — HTML, CSS & JavaScript Hash-Based Tab State

Screenshot of the Tabs with URL Sync snippet rendered live

Ordinary tab bars lose their state on refresh — click "Pricing", hit F5, and you're back on the first tab. This snippet fixes that by storing the active tab in the URL hash (location.hash), so the browser itself remembers which tab was open. Refreshing, bookmarking, or sending a colleague a link to webdevpuneet.com/page#pricing all open the correct panel.

How the hash becomes the source of truth

Clicking a tab doesn't directly toggle classes — it calls goToTab(tabName), which simply sets location.hash = tabName. Setting the hash is what triggers the browser's native hashchange event, which fires a listener calling readHashAndActivate(). That function reads location.hash, strips the leading #, and calls activateTab(name) to update the visible tab and panel. The result: the hash change is the only thing that ever changes the visible tab — clicking a tab bar button and typing a hash directly into the address bar go through the exact same code path.

How back/forward buttons work for free

Every time location.hash is set to a new value, the browser pushes a new entry onto the session history. That means the native Back and Forward buttons walk through your tab history automatically — no manual history.pushState bookkeeping required. This is one of the main advantages of hash-based state over a plain JS variable.

How the initial load is handled

On first load there may be no hash at all (a fresh visit to the page) or an invalid one (a stale bookmark). activateTab guards against this with a valid.includes(tabName) check, falling back to 'overview' if the hash is missing or unrecognized. readHashAndActivate() is called once immediately on script load, in addition to being wired to the hashchange event, so the correct tab is shown even before the user clicks anything.

Extending to more tabs

Add the tab name to the valid array, add a matching data-tab/data-panel pair in the HTML, and the hash-driven activation continues to work with no other logic changes.

Step by step

How to Use

  1. 1
    Load the snippetClick "Tabs with URL Sync" in the sidebar Library tab to load the panels and preview.
  2. 2
    Click a tab and check the hashClick Pricing or FAQ — notice the "Current hash" text and the browser URL bar (in the exported HTML file) both update.
  3. 3
    Test refresh persistenceExport to an HTML file, open it, switch tabs, and refresh — the same tab reopens because the hash survives a reload.
  4. 4
    Test back/forwardClick through a couple of tabs then press the browser Back button — it steps back through your tab history.
  5. 5
    Add a tabAdd the new name to the valid array in JS and add matching data-tab/data-panel elements in HTML.
  6. 6
    Export and saveUse the export buttons for a standalone file or component, or click "Save as" to keep your version.

Real-world uses

Common Use Cases

Shareable documentation tabs
Let users link directly to a specific tab in docs or settings pages, e.g. #installation or #api-reference.
Multi-step settings pages
Keep a settings page's active section in the URL so support links and bookmarks always open the right panel.
Dashboards with persistent views
Users switching between Analytics/Reports/Settings won't lose their place on an accidental refresh.
Learn hash-based state management
Study how a single browser API (location.hash + hashchange) can replace a chunk of client-side routing logic.
Foundation for a lightweight router
Extend the pattern with more routes and query parameters to build a minimal hash router without a framework.
SEO-safe tabbed content fallback
Pair with server-rendered content per hash route for progressively enhanced tab navigation.

Got questions?

Frequently Asked Questions

A plain variable resets on every page load. location.hash is part of the URL, so it survives refreshes, gets included when the link is shared or bookmarked, and integrates with the browser's native back/forward history for free.

No. Setting location.hash does not trigger a network request or full page reload — it only fires the hashchange event, which the script listens for to update the visible tab.

activateTab checks the requested tab name against a valid array. If it is not recognized (or the hash is empty), it falls back to showing the default "overview" tab instead of showing nothing.

Every hash assignment pushes a new browser history entry automatically. The Back and Forward buttons move through those entries, which re-fires hashchange and re-activates the corresponding tab — no extra code needed.

Yes, conceptually. Replace location.hash assignments with history.pushState calls and listen for the popstate event instead of hashchange, then read location.pathname instead of location.hash.

Add its name to the valid array in the JS panel, then add a matching button with data-tab="name" and a panel with data-panel="name" in the HTML panel.

No — like any dynamically-toggled tab pattern, it requires JavaScript to read the hash and toggle the active classes. Without JS, no panel would ever become visible.

Yes. While the Tabs with URL Sync snippet gives you one pattern to copy, the free Responsive Navbar Builder at webdevpuneet.com/navbar-builder/ designs a whole navigation bar: logo, links and dropdown menus, a mobile hamburger menu (dropdown panel, slide-in drawer or full screen) and a scroll effect (sticky, shrink, hide on scroll or transparent). It has six presets, a live preview that runs the real code, and exports HTML + CSS + vanilla JS, React, Tailwind, Vue 3 or Angular — or you can click Fork & Edit to keep editing it in My Code.