More Navigation Snippets
Bottom Navigation — Free HTML CSS JS Mobile Snippet
Mobile Bottom Nav · Navigation · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Mobile Bottom Navigation — data-page Routing, Notification Badge & iOS Phone Mockup

A bottom navigation bar is the dominant primary navigation pattern for mobile apps — iOS, Android, and mobile-first Progressive Web Apps all rely on it. Placing navigation at the thumb-reach zone at the bottom of the screen dramatically improves one-handed usability compared to hamburger menus or top navigation bars. This snippet implements a complete, interactive iOS-style bottom tab bar (the desktop equivalent is the tab bar) inside a realistic phone mockup, complete with page switching, active state indication, a notification badge counter, and smooth page transition animations.
The switchPage() routing system
The switchPage(btn) JavaScript function is the core of this component. When a navigation tab is clicked, the function reads btn.dataset.page — the value stored in the button's data-page HTML attribute. It then calls querySelectorAll('.nav-item').forEach(b => b.classList.remove('active')) to clear all active states, followed by querySelectorAll('.page').forEach(p => p.classList.remove('active')) to hide all page panels. Finally it adds .active back to just the clicked button and to the matching page div via document.getElementById('pg-' + btn.dataset.page). This data-attribute routing pattern requires zero JavaScript mapping objects — the HTML itself defines the relationship between each tab and its page via the data-page value.
The active tab indicator with icon lift
The .nav-item.active CSS rule applies color: #6366f1 to change the icon and label from muted grey to the accent colour. A subtle transform: translateY(-2px) on .nav-item.active svg lifts the active icon slightly upward, giving a physical feel to the selection. Both the colour and transform transition over 0.15s–0.2s for a polished tap response.
The notification badge
The Alerts tab demonstrates a notification badge: a small red circle positioned absolutely over the bell icon using position: absolute; top: -4px; right: -6px. The badge has min-width: 14px so it expands horizontally for two-digit counts. A white border around the badge (border: 1.5px solid #fff) creates a gap between the badge and the icon, preventing them from visually merging.
Page transition animation
Each .page element is display: none by default. When .active is added, a @keyframes fadeIn animation runs: from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } over 0.2s. This gives each page switch a subtle reveal motion that communicates forward navigation without being distracting.
The phone mockup frame
The outer .phone div has border-radius: 28px; overflow: hidden; height: 500px which clips all content to the rounded rectangle frame, creating a realistic device mockup directly in the browser. This makes the snippet presentation-ready for client demos, design presentations, and portfolio showcases.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to trace the data-page wiring by hand to see why it works. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how switchPage reads btn.dataset.page and constructs the target page's id string, and why that eliminates the need for any JavaScript mapping object between tabs and pages. The same assistant can help optimize it — asking whether querying all .nav-item and .page elements on every single click is wasteful for a five-tab bar versus caching the node lists once, or whether the notification badge should be updated via a MutationObserver-safe pattern instead of direct textContent writes. It's also useful for extending the bar: ask it to add a center elevated "create" tab, swipe gestures between pages, or a route-synced version using the History API instead of pure data attributes. Treat the code less like a finished artifact and more like a starting point for a conversation.
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:
Build a mobile "bottom navigation" tab bar inside a phone mockup frame using plain HTML, CSS, and JavaScript, with data-attribute based routing and no URL changes.
Requirements:
- A phone frame div with fixed width and height, rounded corners, and overflow hidden, containing a scrollable content area and a fixed bottom tab bar.
- Each tab button carries a data-page attribute, and each corresponding content page is a div with an id built from a "pg-" prefix plus that same data-page value, so a single JavaScript function can map any button to its page purely by string concatenation, without a lookup object.
- A switchPage function that removes an active class from every tab button and every page, then adds it back only to the clicked button and to the page whose id matches "pg-" plus that button's data-page value.
- The active tab must change color and lift its icon slightly upward with a CSS transform, both transitioning smoothly.
- The newly shown page must fade and slide in slightly from below using a CSS keyframe animation triggered by the active class being added.
- One tab must show a small numeric notification badge positioned absolutely over its icon, styled so it never visually merges with the icon it overlaps.
- Adding a new tab and its page must require zero changes to the JavaScript — only a new button with a data-page value and a matching page div with the corresponding id.Step by step
How to Use
- 1Click each tab in the phone mockup to switch pagesClick the Home, Search, Alerts, and Profile tabs to switch between pages. Watch the active tab highlight in indigo, the icon lift slightly, and the new page fade in from below. The notification badge on the Alerts tab shows a count of 3.
- 2Update the tab labels, icons, and data-page valuesIn the HTML panel, change the SVG icon paths for each .nav-item, update the <span> label text, and change the data-page attribute value. The data-page value must match the id of the corresponding .page div: data-page='messages' maps to id='pg-messages'.
- 3Add page content to each tab's screen panelIn the HTML panel, find the .page divs with ids like pg-home, pg-search, etc. Replace the placeholder h2 and p elements with your actual page content — lists, cards, forms, or any HTML you want to show when that tab is active.
- 4Add a fifth tab and page panelDuplicate one .nav-item button, give it a new data-page value like 'messages', and add a new <div class='page' id='pg-messages'> inside the .screen .content element. The switchPage() function handles the new tab automatically because it uses data-page to look up the page by ID.
- 5Update the notification badge count dynamicallyThe .notif-badge span shows a static count of 3. To update it dynamically from JavaScript: document.querySelector('.notif-badge').textContent = newCount. To hide it when count is zero: badge.style.display = count > 0 ? 'flex' : 'none'. Call this whenever your notification state changes.
- 6Export for use outside the phone mockupClick 'HTML' or 'JSX' to export. To use without the phone frame in a real app, remove the .phone wrapper and apply position: fixed; bottom: 0; left: 0; right: 0; to .bottom-nav. Add padding-bottom: 60px to the main content area to prevent content hiding under the fixed bar.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
switchPage(btn) reads btn.dataset.page to get the page identifier (e.g. 'home', 'search'). It then calls document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active')) to clear all active tab states, and document.querySelectorAll('.page').forEach(p => p.classList.remove('active')) to hide all page panels. Finally it calls btn.classList.add('active') to highlight the clicked tab, and document.getElementById('pg-' + btn.dataset.page).classList.add('active') to show the matching page panel. The data-page attribute on each button acts as a direct reference to the page panel's ID suffix, eliminating any need for a JavaScript mapping object.
Add a new button element inside .bottom-nav with class='nav-item', data-page='yourpage', and onclick='switchPage(this)'. Give it an SVG icon and a label span. Then add a new <div class='page' id='pg-yourpage'> inside .screen .content with your page content. The switchPage() function constructs the target element ID as 'pg-' + btn.dataset.page, so as long as your data-page value matches the ID suffix of your page div, no JavaScript changes are needed.
The badge is a span with class 'notif-badge' inside .icon-wrap. To update it: const badge = document.querySelector('.nav-item[data-page="notif"] .notif-badge'); badge.textContent = newCount. To show or hide it based on whether there are notifications: badge.style.display = newCount > 0 ? 'flex' : 'none'. Call this function whenever your notification count changes — for example, after polling a notifications API or receiving a WebSocket push event.
Yes. Remove the .phone and .screen wrapper divs. Apply position: fixed; bottom: 0; left: 0; right: 0; z-index: 100; to the .bottom-nav element so it stays anchored to the bottom of the viewport. Add padding-bottom: 64px to your main content container to prevent the last content item from being hidden behind the fixed navigation bar. On iOS Safari, also add padding-bottom: max(64px, env(safe-area-inset-bottom)) to respect the iPhone home indicator safe area.