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

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

Share & Support

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 yoursite.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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to trace exactly why goToTab only ever sets location.hash rather than directly toggling classes — the point is that the hash change becomes the single source of truth, so clicking a button and manually editing the URL bar produce identical behavior. It's also worth asking the assistant to convert this to History API-based routing with pushState/popstate for cleaner URLs without a leading #, or to add support for a query parameter alongside the hash (e.g. #pricing?plan=pro) that the panel content can read.

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 "tabs with URL sync" component in plain HTML, CSS, and vanilla JavaScript, with no router library.

Requirements:
- A tab bar of at least three buttons where clicking a tab does not directly toggle CSS classes but instead sets location.hash to the tab's name.
- A single hashchange event listener that reads location.hash, validates it against a known list of tab names, falls back to a default tab if the hash is missing or invalid, and then toggles the active class on the matching tab button and content panel.
- The same activation function must run once on initial page load (not just on hashchange) so a page opened directly with a hash like #pricing shows the correct tab immediately.
- Verify that clicking through tabs creates browser history entries so the native Back and Forward buttons move between previously visited tabs.
- Style the tabs with a colored active-tab underline consistent with a standard tab bar, and keep the whole thing dependency-free.

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
    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.