~/css/mobile-navigation
7 CSS Mobile Navigation Patterns
4 Pure CSS 3 with JS Published 2026-10-05 Free · no sign-up
Seven navigation patterns designed for phones: a tab bar with a raised centre button, a menu in a bottom sheet, a full-screen menu with big links, top tabs you can swipe, a back header with title and action, a segmented control and a floating pill-shaped bottom nav.
Filter demos
01. Tab Bar with Centre Action
Mobile Tab bar
A five-slot bottom tab bar with a raised round button in the middle for the main action, such as posting. Every tab keeps a text label, the centre button has an accessible name, and the bar sits in a phone-shaped frame here.
Copy HTML Copy CSS Open in editor →
View code
HTML CSS
<div class="mn1"><div class="scr"></div><nav aria-label="App"><a href="#home" aria-current="page"><span aria-hidden="true">⌂</span>Home</a><a href="#explore"><span aria-hidden="true">◎</span>Explore</a><button aria-label="New post">+</button><a href="#inbox"><span aria-hidden="true">✉</span>Inbox</a><a href="#me"><span aria-hidden="true">☺</span>Me</a></nav></div>.mn1 { display: grid; grid-template-rows: 1fr auto; width: 260px; max-width: 100%; height: 260px; border: 1px solid #e4e7ec; border-radius: 24px; overflow: hidden; background: #f9fafb; font: 600 11px Inter, system-ui, sans-serif; }
.mn1 nav { display: grid; grid-template-columns: repeat(5, 1fr); align-items: end; padding: 6px 4px 10px; background: #fff; border-top: 1px solid #e4e7ec; }
.mn1 a { display: grid; justify-items: center; gap: 2px; color: #475467; text-decoration: none; } .mn1 a span { font-size: 18px; line-height: 1; }
.mn1 a[aria-current] { color: #7f56d9; }
.mn1 button { justify-self: center; width: 50px; height: 50px; margin-top: -24px; border: 4px solid #fff; border-radius: 50%; background: #7f56d9; color: #fff; font-size: 26px; cursor: pointer; box-shadow: 0 6px 14px -6px #7f56d9; }
.mn1 :focus-visible { outline: 3px solid #d6bbfb; outline-offset: 1px; border-radius: 8px; } .mn1 button:focus-visible { border-radius: 50%; }
02. Menu in a Bottom Sheet
Mobile Bottom sheet
On phones the menu opens as a sheet sliding up from the bottom, where thumbs can reach every item. It is a modal dialog, so focus stays inside and Escape or the Close button dismisses it, and the sheet's slide is a transform with @starting-style.
Copy HTML Copy CSS Copy JS Open in editor →
View code
HTML CSS JS
<div class="mn2"><button class="open">Menu</button><dialog aria-label="Menu"><span class="grab" aria-hidden="true"></span><nav aria-label="Main"><a href="#home">Home</a><a href="#bookings">Bookings</a><a href="#messages">Messages</a><a href="#settings">Settings</a></nav><button class="close">Close</button></dialog></div>.mn2 { font: 600 15px Inter, system-ui, sans-serif; }
.mn2 .open { padding: 10px 16px; border: 0; border-radius: 10px; background: #f8fafc; color: #0f172a; font: inherit; cursor: pointer; }
.mn2 dialog { position: fixed; inset: auto 0 0 0; width: 100%; max-width: 100%; margin: 0; padding: 10px 18px 18px; box-sizing: border-box; border: 0; border-radius: 20px 20px 0 0; background: #151b2e; color: #fff; transform: translateY(100%); transition: transform .35s cubic-bezier(.2,.8,.2,1), overlay .35s allow-discrete, display .35s allow-discrete; }
.mn2 dialog[open] { transform: none; } @starting-style { .mn2 dialog[open] { transform: translateY(100%); } }
.mn2 dialog::backdrop { background: rgba(0,0,0,.5); }
.mn2 .grab { display: block; width: 40px; height: 5px; margin: 0 auto 12px; border-radius: 3px; background: #475569; }
.mn2 nav { display: grid; margin-bottom: 12px; }
.mn2 nav a { padding: 14px 4px; border-bottom: 1px solid #263048; color: #f1f5f9; text-decoration: none; }
.mn2 .close { width: 100%; padding: 12px; border: 0; border-radius: 12px; background: #263048; color: #fff; font: inherit; cursor: pointer; }
.mn2 :focus-visible { outline: 2px solid #93c5fd; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .mn2 dialog { transition: none; } }var d = document.querySelector('.mn2 dialog');
document.querySelector('.mn2 .open').onclick = function () { d.showModal(); };
d.querySelector('.close').onclick = function () { d.close(); };
03. Full-Screen Menu with Big Links
Mobile Full screen
A mobile menu panel that fills the frame with large, well-spaced links and secondary links at the bottom. Large type makes the menu fast to scan and easy to tap, and the current page is marked with aria-current and an arrow.
Copy HTML Copy CSS Open in editor →
View code
HTML CSS
<div class="mn3"><nav aria-label="Main"><a href="#home" aria-current="page">Home</a><a href="#shop">Shop</a><a href="#stories">Stories</a><a href="#about">About</a></nav><nav class="sub" aria-label="Secondary"><a href="#help">Help</a><a href="#account">Account</a></nav></div>.mn3 { display: grid; align-content: space-between; width: 260px; max-width: 100%; height: 300px; padding: 22px; box-sizing: border-box; border-radius: 22px; background: #fef3c7; font-family: Inter, system-ui, sans-serif; }
.mn3 nav { display: grid; gap: 4px; }
.mn3 a { color: #451a03; font-size: 30px; font-weight: 900; letter-spacing: -.02em; text-decoration: none; }
.mn3 a[aria-current]::after { content: " →"; color: #b45309; }
.mn3 .sub { display: flex; gap: 16px; } .mn3 .sub a { font-size: 14px; font-weight: 600; color: #92400e; }
.mn3 a:focus-visible { outline: 3px solid #b45309; outline-offset: 2px; border-radius: 4px; }
04. Swipeable Top Tabs
Mobile Tabs
Top tabs that scroll sideways when there are more than fit, with the selected tab underlined and scrolled into view when chosen. It uses the ARIA tabs pattern, so left and right arrows move between tabs on a keyboard.
Copy HTML Copy CSS Copy JS Open in editor →
View code
HTML CSS JS
<div class="mn4"><div role="tablist" aria-label="Feed"><button role="tab" aria-selected="true" aria-controls="mn4p">For you</button><button role="tab" aria-selected="false" aria-controls="mn4p" tabindex="-1">Following</button><button role="tab" aria-selected="false" aria-controls="mn4p" tabindex="-1">Design</button><button role="tab" aria-selected="false" aria-controls="mn4p" tabindex="-1">Engineering</button><button role="tab" aria-selected="false" aria-controls="mn4p" tabindex="-1">Product</button></div><div role="tabpanel" id="mn4p" tabindex="0" aria-label="For you">Posts picked for you.</div></div>.mn4 { width: 260px; max-width: 100%; border-radius: 16px; overflow: hidden; background: #111727; font: 600 14px Inter, system-ui, sans-serif; }
.mn4 [role=tablist] { display: flex; overflow-x: auto; scrollbar-width: none; border-bottom: 1px solid #263048; }
.mn4 [role=tablist]::-webkit-scrollbar { display: none; }
.mn4 [role=tab] { flex: none; padding: 12px 14px; border: 0; border-bottom: 2px solid transparent; background: none; color: #94a3b8; font: inherit; cursor: pointer; }
.mn4 [aria-selected=true] { border-bottom-color: #38bdf8; color: #fff; }
.mn4 [role=tabpanel] { padding: 18px 14px; color: #cbd5e1; font-weight: 400; }
.mn4 :focus-visible { outline: 2px solid #38bdf8; outline-offset: -2px; }var tabs = document.querySelectorAll('.mn4 [role=tab]'), p = document.getElementById('mn4p');
function sel(i) { tabs.forEach(function (x, j) { x.setAttribute('aria-selected', i === j); x.tabIndex = i === j ? 0 : -1; }); p.textContent = tabs[i].textContent + ' posts.'; p.setAttribute('aria-label', tabs[i].textContent); tabs[i].focus(); tabs[i].scrollIntoView({ block: 'nearest', inline: 'center', behavior: 'smooth' }); }
tabs.forEach(function (t, i) { t.onclick = function () { sel(i); }; t.onkeydown = function (e) { var k = { ArrowRight: 1, ArrowLeft: -1 }[e.key]; if (k) { e.preventDefault(); sel((i + k + tabs.length) % tabs.length); } }; });
05. Back Header with Title and Action
Mobile Header
The header of a detail screen in a mobile web app: a back link on the left, the screen title centred and a single action on the right. The title is the page heading, and the back link names its destination rather than just saying Back.
Copy HTML Copy CSS Open in editor →
View code
HTML CSS
<header class="mn5"><a href="#orders" aria-label="Back to Orders"><span aria-hidden="true">‹</span> Orders</a><h3>Order #1042</h3><button>Share</button></header>.mn5 { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; width: 300px; max-width: 100%; box-sizing: border-box; padding: 10px 12px; border-bottom: 1px solid #e4e7ec; background: #fff; font: 15px Inter, system-ui, sans-serif; }
.mn5 a { justify-self: start; color: #2563eb; font-weight: 600; text-decoration: none; } .mn5 a span { font-size: 22px; line-height: 0; vertical-align: -2px; }
.mn5 h3 { margin: 0; color: #101828; font-size: 16px; white-space: nowrap; }
.mn5 button { justify-self: end; padding: 6px 10px; border: 0; border-radius: 8px; background: none; color: #2563eb; font: 600 15px Inter, system-ui, sans-serif; cursor: pointer; }
.mn5 :focus-visible { outline: 3px solid #84adff; outline-offset: 2px; border-radius: 6px; }
06. Segmented Control
Mobile Segmented
The iOS-style segmented control for switching views, built from radio buttons so only one segment can be on and arrow keys move between them. The selected segment's raised look comes from :has(:checked) on its label.
Copy HTML Copy CSS Open in editor →
View code
HTML CSS
<fieldset class="mn6"><legend class="sr">Show</legend><label><input type="radio" name="mn6" checked> Upcoming</label><label><input type="radio" name="mn6"> Past</label><label><input type="radio" name="mn6"> Drafts</label></fieldset>.mn6 { display: flex; width: 280px; max-width: 100%; min-inline-size: 0; margin: 0; padding: 3px; box-sizing: border-box; border: 0; border-radius: 10px; background: #1e293b; font: 600 13px Inter, system-ui, sans-serif; }
.mn6 label { flex: 1; padding: 8px 6px; border-radius: 8px; color: #94a3b8; text-align: center; cursor: pointer; transition: background .2s, color .2s; }
.mn6 input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.mn6 label:has(:checked) { background: #f8fafc; color: #0f172a; box-shadow: 0 1px 3px rgba(0,0,0,.4); }
.mn6 label:has(:focus-visible) { outline: 2px solid #93c5fd; outline-offset: 1px; }
.mn6 .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
@media (prefers-reduced-motion: reduce) { .mn6 label { transition: none; } }
07. Floating Bottom Nav Pill
Mobile Floating
A bottom navigation that floats as a dark pill above the content, with the current item expanding to show its label while the others show only icons with hidden labels. The expanding label is a width transition on a span.
Copy HTML Copy CSS Copy JS Open in editor →
View code
HTML CSS JS
<div class="mn7"><nav aria-label="App"><a href="#home" aria-current="page"><span class="i" aria-hidden="true">⌂</span><span class="l">Home</span></a><a href="#search"><span class="i" aria-hidden="true">⌕</span><span class="l">Search</span></a><a href="#saved"><span class="i" aria-hidden="true">♡</span><span class="l">Saved</span></a><a href="#profile"><span class="i" aria-hidden="true">☺</span><span class="l">Profile</span></a></nav></div>.mn7 { display: grid; align-items: end; justify-items: center; width: 280px; max-width: 100%; height: 220px; padding-bottom: 14px; box-sizing: border-box; border-radius: 22px; background: linear-gradient(#f2f4f7, #e4e7ec); font: 600 13px Inter, system-ui, sans-serif; }
.mn7 nav { display: flex; gap: 2px; padding: 6px; border-radius: 999px; background: #101828; box-shadow: 0 10px 24px -10px rgba(16,24,40,.6); }
.mn7 a { display: flex; align-items: center; gap: 6px; padding: 9px 12px; border-radius: 999px; color: #98a2b3; text-decoration: none; }
.mn7 .i { font-size: 17px; line-height: 1; }
.mn7 .l { max-width: 0; overflow: hidden; white-space: nowrap; transition: max-width .3s ease; }
.mn7 a[aria-current] { background: #fff; color: #101828; } .mn7 a[aria-current] .l { max-width: 70px; }
.mn7 a:not([aria-current]) .l { position: absolute; width: 1px; height: 1px; clip: rect(0 0 0 0); }
.mn7 a:focus-visible { outline: 2px solid #84adff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .mn7 .l { transition: none; } }var links = document.querySelectorAll('.mn7 a');
links.forEach(function (a) { a.onclick = function (e) { e.preventDefault(); links.forEach(function (x) { if (x === a) x.setAttribute('aria-current', 'page'); else x.removeAttribute('aria-current'); }); }; });
About these CSS Mobile Navigation Patterns
Put actions where thumbs reach
On large phones the bottom of the screen is easiest to reach. Bottom tab bars and sheets keep the most used destinations in the comfortable zone.
Labels with icons
Icon-only tabs are harder to learn. Keep short labels under tab bar icons; if a design hides inactive labels, keep them in the accessible name.
Name where Back goes
A back link that says Orders, not just Back, tells people where they will land and gives screen reader users a useful name.
Leave room for the device
Use env(safe-area-inset-bottom) padding on bottom bars so they clear the home indicator on phones without a home button.
Frequently asked questions
Tab bar or hamburger? A tab bar for three to five top-level destinations people switch between often; a menu for everything else.
Should mobile tabs use the ARIA tabs pattern? Only when they switch panels on the same page. Tab bars that change pages are navigation links.
Do these work on desktop? They are designed for small screens; pair them with a different layout on wide screens.