WingoUI
ComponentsTemplatesDocsChangelogBlogAI agents
ThemePricing
  1. Home
  2. Blog
  3. Mobile UI in React
  4. React Bottom Navigation Bar in Next.js: Safe Areas, Badges
  1. Blog
  2. Mobile UI in React
  3. React Bottom Navigation Bar in Next.js: Safe Areas, Badges
Mobile UI in React
Mobile UI in React

React Bottom Navigation Bar in Next.js: Safe Areas, Badges

SR
Serban Rusu · Founder of Wingo UI
Oct 9, 2026 · 8 min read

On this page

0%
  1. When does a web app need a bottom navigation bar?
  2. What does a bottom tab bar need to work in a browser?
  3. How should tab bar badges and labels behave?
  4. Should the center button be a tab, an action or a FAB?
  5. Should a bottom navigation bar hide on scroll?
  6. When is a sidebar the better choice?
  7. What breaks a bottom navigation bar in production?
  8. What should you install?
  9. FAQ
    1. How many items should a bottom navigation bar have?
    2. Does shadcn/ui have a bottom navigation component?
    3. Should a bottom navigation bar use role="tablist"?
    4. Should a bottom navigation bar hide on scroll?
    5. Is the Wingo UI Bottom Tab Bar free?

TL;DR

A React bottom navigation bar is a fixed nav of 3 to 5 links at the bottom of the screen, with aria-current="page" on the active link, padding for env(safe-area-inset-bottom) under viewport-fit=cover, and bottom padding on the page equal to the bar's height so the last row stays visible. Keep the labels visible, put badge counts in each link's accessible name, and only hide the bar on scroll if it follows the finger and comes back on scroll up. Use it below 768px with a sidebar above that, and with more than five destinations keep the four most used in the bar and the rest in a drawer.

Published Oct 9, 2026

A React bottom navigation bar is the tab bar that native apps have trained everyone to expect: three to five destinations on the bottom edge, where the thumb already is. On the web it takes more than a fixed <div> of icons. The bar has to clear the iPhone home indicator, keep the last row of the page visible, tell a screen reader that Inbox has 4 unread messages, follow the router, and decide what a big center button and a long scroll should do. This guide covers each with the Wingo UI Bottom Tab Bar, then the cases where a Sidebar is the better choice.

When does a web app need a bottom navigation bar?

When people use it on phones and move between three to five top-level sections many times a session. Android's navigation bar guidance (opens in a new tab) lists the conditions: "Three to five destinations of equal importance", "Compact window sizes" and "Consistent destinations across app screens". A CRM with Home, Clients, Inbox and Profile fits. A marketing site, a docs site or a checkout does not, because nobody hops between their sections.

The bottom edge is the reason to bother. In Steven Hoober's 2013 field study (opens in a new tab) (1,333 observations of people using mobile devices), 49% of those touching the screen held the phone in one hand. A hamburger menu in a top corner sits far from that thumb and hides every destination behind an extra tap. In a bottom tab bar web app, every destination stays visible and one tap away, and the active tab tells people where they are.

What does a bottom tab bar need to work in a browser?

Five jobs that a native tab bar gets from the platform and a web page has to do itself:

ConcernWhat to doIn the Bottom Tab Bar
SemanticsA <nav> of links, aria-current="page" on the active onearia-label="Primary"; tabs without href render as buttons
RoutingThe active tab follows the URL, nested routes includedcurrentPath picks the longest matching href; linkComponent takes next/link
Home indicatorviewport-fit=cover, then pad the inside with env(safe-area-inset-bottom)safeArea, on by default
Covered contentPad the page by the bar's real heightWrites --bottom-bar-offset on <html>
TouchTargets of 44px or moreEach tab fills its slot, 56 or 64px tall

Build the tabs from links. The ARIA tabs pattern (opens in a new tab) describes "layered sections of content, known as tab panels, that display one panel of content at a time". A tab bar loads another page, so role="tablist" would announce a widget that is not there.

Here is a React mobile bottom navigation for the Next.js App Router, in two files:

tsx
// app/(app)/mobile-nav.tsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { House, Inbox, Plus, UserRound, Users } from "lucide-react";
import { BottomTabBar, type BottomTabBarItem } from "@/components/ui/bottom-tab-bar";
const tabs: BottomTabBarItem[] = [
{ label: "Home", href: "/", icon: <House />, activeIcon: <House className="fill-current/20" /> },
{ label: "Clients", href: "/clients", icon: <Users />, activeIcon: <Users className="fill-current/20" /> },
{ label: "Inbox", href: "/inbox", icon: <Inbox />, badge: 4 },
{ label: "Profile", href: "/profile", icon: <UserRound />, dot: true },
];
export function MobileNav() {
return (
<BottomTabBar
className="md:hidden"
items={tabs}
currentPath={usePathname()}
linkComponent={Link}
action={{ label: "New client", icon: <Plus />, href: "/clients/new" }}
/>
);
}
tsx
// app/(app)/layout.tsx
import type { ReactNode } from "react";
import type { Viewport } from "next";
import { MobileNav } from "./mobile-nav";
// without cover, iOS keeps the page inside the safe area and env() reports 0
export const viewport: Viewport = { width: "device-width", initialScale: 1, viewportFit: "cover" };
export default function AppLayout({ children }: { children: ReactNode }) {
return (
<>
<main className="pb-(--bottom-bar-offset)">{children}</main>
<MobileNav />
</>
);
}

The bar is fixed to the viewport and measures itself, safe area included, so the padding on <main> is always exact; hidden by md:hidden on a desktop, it measures 0. A guessed pb-16 is wrong as soon as the inset or the size changes. The safe area guide explains the CSS underneath. Render the bar in the server HTML and hide it with CSS: deciding with a useIsMobile() hook renders nothing on the server, and the bar pops in after hydration, as the useMobile hook guide shows.

Tap Clients, open a client, then tap Clients again: the tab goes back to its list, and one more tap scrolls to the top. The raised Add button opens a sheet instead of switching tabs.
$ npx wingo-ui@latest add bottom-tab-bar
ProBottom Tab Bar docs

How should tab bar badges and labels behave?

A count for things that need action, a dot for something new, both read as part of the tab's name. badge: 4 renders a count (99+ above 99) and dot: true an 8px dot, red by default (badgeTone="danger") because people read a red count as unread. The visible badge is aria-hidden, and the link is named "Inbox, 4 unread" (badgeLabel) or "Profile, new" (dotLabel), so a screen reader never reads a stray number. Apple's tab bar guidelines (opens in a new tab) (updated June 2026) say "Reserve badges for critical information so you don't dilute their impact and meaning": unread messages and pending approvals, not a new feature.

Keep showLabels="always". The same guidelines say "Include tab labels to help with navigation" and "Use single words whenever possible". "active" and "never" exist for icon-heavy consumer apps; with "never", labels stay in the DOM for screen readers and become title tooltips. activeIcon swaps in a second glyph while the tab is active, such as a filled version of the outline icon. Apple goes further and asks you to "Prefer filled symbols or icons for consistency with the platform" on every tab.

Should the center button be a tab, an action or a FAB?

An action, never a tab, and only when one creation action dominates the app. Apple is strict here: "Use a tab bar to support navigation, not to provide actions." We still ship the raised center button, because in an app built around one verb (Post, Sell, Scan) that verb has earned the most reachable spot. The action prop renders it in the middle (with four tabs: two, the action, two), filled with the tone and named by its label. It never takes the active pill, so nobody mistakes it for where they are.

When the app has several things to create, or you follow Apple's rule, leave the bar to navigation and use a FAB. It reads --bottom-bar-offset, so it floats 16px above the tab bar and the home indicator without counting the inset twice:

tsx
import { FileText, UserPlus } from "lucide-react";
import { Fab, type FabAction } from "@/components/ui/fab";
const actions: FabAction[] = [
{ id: "client", label: "New client", icon: <UserPlus />, href: "/clients/new" },
{ id: "invoice", label: "New invoice", icon: <FileText />, href: "/invoices/new" },
];
export function CreateFab() {
return <Fab className="md:hidden" label="Add" actions={actions} />;
}
Tap Add: the page dims and the create actions rise from the button. Scroll the list and the label folds down to the plus.
$ npx wingo-ui@latest add fab
ProFAB docs

Should a bottom navigation bar hide on scroll?

Only on long feeds and reading screens, and only if it comes back the moment the user might need it. Apple warns that "If you hide the tab bar, people can forget which area of the app they're in". Its iOS guidance describes minimizing a tab bar that carries an accessory, such as the MiniPlayer in Music, while people scroll down; tapping a tab or scrolling to the top brings it back.

If you hide it, hideOnScroll shows the behavior to copy:

  • It follows the finger. The bar moves pixel for pixel with the scroll until it has traveled its own height plus the safe area, and snaps to shown or hidden once scrolling stops. A boolean that flips on direction makes the bar jump on a 2px scroll.
  • It comes back on scroll up, at the top, at the end of the content and whenever keyboard focus is inside it.
  • It moves a transform only. The progress lives in a motion value, so scrolling never re-renders the bar or the page. A version that calls setState in the scroll handler renders on every scroll event.
  • Reduced motion turns the slide into a flip after a full bar height of scrolling.
Scroll the page: the floating bar follows your finger out of view and comes back as soon as you scroll up.
$ npx wingo-ui@latest add bottom-tab-bar
ProBottom Tab Bar docs

When is a sidebar the better choice?

When there are more than five top-level destinations, when they nest, or when most sessions happen on a laptop. Apple suggests "a sidebar or a tab bar that adapts to a sidebar as an alternative for an app with a complex information structure" and asks you to "Avoid overflow tabs"; Android recommends the bar for compact windows.

Your appOn a phoneFrom 768px
3 to 5 sections, used daily on phonesBottom tab barSidebar or top navigation
6 or more sections, or nested onesThe 4 most used in the bar, everything in a drawerSidebar
Admin tool used mostly at a deskDrawer, optional tab barSidebar with an icon rail
Content or docs siteTop bar and a menuTop navigation

Most apps need both, so the App Shell renders the Sidebar beside the page from 768px and, below it, the same sidebar in a drawer plus a tab bar for the daily destinations:

tsx
// app/(app)/layout.tsx with the App Shell
import type { ReactNode } from "react";
import { AppShell } from "@/components/ui/app-shell";
import { Sidebar } from "@/components/ui/sidebar";
import { Topbar } from "@/components/ui/topbar";
import { nav, tabs } from "./nav";
export default function AppLayout({ children }: { children: ReactNode }) {
return (
<AppShell sidebar={<Sidebar groups={nav} filter />} topbar={<Topbar title="Summit CRM" />} tabs={tabs}>
{children}
</AppShell>
);
}

nav holds every destination in groups and tabs the four people open every day. The shell renders the tab bar itself and reads the route from a LinkProvider (component={Link}, currentPath={usePathname()}) set once in a client provider; the Next.js admin dashboard tutorial builds that provider and the whole layout. The Admin Shell adds a More tab that opens the drawer.

If you came looking for a shadcn mobile bottom navigation: as of October 2026, the shadcn/ui component list (opens in a new tab) has none, and its Sidebar (opens in a new tab) becomes an 18rem Sheet below 768px, two taps from any destination. Material UI's Bottom Navigation (opens in a new tab) is a fine react bottom navigation component, and the better pick if your app already runs on Material UI. As of October 2026 its docs cover labels, fixed positioning and router links, and leave badges, safe areas and hide on scroll to you.

What breaks a bottom navigation bar in production?

  • Home is active on every page. A hand-rolled pathname.startsWith(href) matches / everywhere. The bar matches / only exactly and picks the longest matching href, so /clients/northwind-tech keeps Clients active.
  • Tapping the active tab does nothing. People expect it to scroll to the top or go back to the tab's first screen. scrollToTopOnReselect does both, and onReselect can refresh a feed.
  • A sixth tab. The bar warns in development above five items. Move the rest into a More tab or the drawer.
  • Safari's floating toolbar. Since iOS 26, Safari's toolbar floats over the bottom of the page in the Compact tab layout, and a September 2025 forum report (opens in a new tab) says Safari would not render fixed content below those controls. Test on a real iPhone in the Compact, Bottom and Top tab layouts.
  • Two bars, two insets. A buy bar above the tab bar must not pad the home indicator again; the mobile-first React components guide shows the stacking.

What should you install?

npx wingo-ui@latest add bottom-tab-bar copies the component into components/ui/ with the Badge, the navigation and motion helpers and the scroll hooks it imports. The Bottom Tab Bar, FAB, Sidebar and App Shell are part of Wingo UI Pro; the Badge is free. More guides like this one live in Mobile UI in React.

Components in this post

  • Bottom Tab Bar

    The phone's primary navigation: 3 to 5 tabs at the thumb with an animated pill, badges, a raised center action and hide on scroll.

    Pro
  • FAB

    The floating action button for a phone screen's main action, with a labelled or radial speed dial and a label that folds while you scroll.

    Pro
  • Sidebar

    The admin sidebar: grouped links with icons, nested sections, badges, a sliding active pill, an icon rail with flyouts and a filter.

    Pro
  • App Shell

    The whole admin layout in one component: a collapsible, resizable sidebar, a top bar and the page; a drawer and a tab bar on phones.

    Pro

FAQ

How many items should a bottom navigation bar have?

Three to five. Android's guidance lists three to five destinations of equal importance, and Apple's tab bar guidelines ask you to avoid overflow tabs. With more sections, keep the four most used in the bar and put everything in a drawer or a sidebar.

Does shadcn/ui have a bottom navigation component?

Not as of October 2026: its component list has no bottom navigation or tab bar, and its Sidebar turns into an 18rem Sheet below 768px. Wingo UI's Bottom Tab Bar, a Pro component, installs the same way, as source code in your project, with npx wingo-ui@latest add bottom-tab-bar.

Should a bottom navigation bar use role="tablist"?

No. The ARIA tabs pattern is for panels that switch inside one page. A bar that changes the page is navigation: a nav element with links, and aria-current="page" on the active one.

Should a bottom navigation bar hide on scroll?

Only on long feeds and reading screens, because a hidden bar also hides where people are in the app. If it hides, it should follow the finger, move with a transform only, and come back on scroll up, at the top and end of the page, and while keyboard focus is inside it.

Is the Wingo UI Bottom Tab Bar free?

No. The Wingo UI Bottom Tab Bar is a Pro component, like the FAB, the Sidebar and the App Shell; the Badge it uses for counts is free.

  • Mobile UI
  • Navigation
  • Tab bar
  • React
  • Next.js

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu is the founder of Wingo UI. He builds the component library, its CLI and its MCP server, and writes about React interfaces that work well on phones and with AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Mobile UI in React

Tailwind Safe Area Insets: Bottom Bars, FABs and Toasts

A Tailwind safe area guide for v4: opt in with viewport-fit=cover, pad bars with env(safe-area-inset-bottom) and keep FABs and toasts off the home indicator.

SRSerban Rusu·Oct 9, 2026·12 min read
Build it: screens and apps

Nextjs Admin Dashboard Tutorial: 5 Screens, Phone-Ready

A nextjs admin dashboard tutorial for the App Router: app shell, KPI home, server-side data tables, settings and team pages, each one usable on a phone.

SRSerban Rusu·Oct 9, 2026·19 min read
Mobile UI in React

100vh Mobile Fix: When to Use dvh, svh or lvh in Tailwind

The 100vh mobile bug explained: why h-screen hides your bottom bar, when to use dvh, svh or lvh in Tailwind, and how to keep a bar above the keyboard.

SRSerban Rusu·Oct 9, 2026·11 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components. Copy the source, make it yours, and let your coding agent build with it.

ComponentsTemplatesPricingBlogTheme

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities
Wingo UI