Bottom Navigation — Free HTML CSS JS Mobile Snippet

Mobile Bottom Nav · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

switchPage() reads data-page attribute for routing without URL changes
Removes .active from all nav items and pages, then adds to clicked pair
Active page uses fadeIn CSS animation for smooth page transition
Phone mockup: border-radius: 28px; overflow: hidden clips content
Notification badge: position absolute red circle with count number
Bottom nav sticky inside phone container
data-page attribute maps buttons to page IDs — no JS map needed
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons
Live split-pane editor — preview updates as you type

About this UI Snippet

Mobile Bottom Navigation — data-page Routing, Notification Badge & iOS Phone Mockup

Screenshot of the Mobile Bottom Nav snippet rendered live

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:

text
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

  1. 1
    Click 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.
  2. 2
    Update 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'.
  3. 3
    Add 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.
  4. 4
    Add 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.
  5. 5
    Update 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.
  6. 6
    Export 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

Mobile app prototype navigation
The primary pattern for prototyping iOS and Android-style bottom navigation. Test tab count, icon choices, label length, and active state before building in React Native or Swift. The phone mockup wrapper makes it presentation-ready.
Progressive Web App primary navigation
PWAs targeting mobile users should use bottom navigation rather than hamburger menus or top nav bars. Thumb-reach zones on mobile make bottom tabs significantly easier to tap than top navigation elements.
Learn data-attribute routing and active state management
switchPage() reads data-page to route without URL changes. The querySelectorAll + forEach pattern removes all active states then adds to the clicked element. This same pattern appears in tabs, segmented controls, and any exclusive-selection UI.
Mobile UI design client presentations
Use the phone mockup frame when presenting mobile designs to clients in a browser. Replace page content with screenshots, mockups, or iframe previews of your actual design to demonstrate navigation flow.
E-commerce mobile app navigation
Standard mobile e-commerce tabs: Home (browse), Search, Cart (with badge showing item count), Wishlist, Profile. The badge dynamically shows the cart item count. The active page content renders in the scrollable screen area.
Social and community app tab structure
Feed, Explore, Create (centre prominent tab), Notifications (with badge), Profile — the standard five-tab structure of social apps from Instagram to TikTok. The centre Create tab can be larger and elevated for emphasis.

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.