Open demo

CSS code

HTML
<nav class="bnav" aria-label="Main">
  <ul>
    <li><a href="#" aria-current="page"><span class="bnav-ic" aria-hidden="true">🏠</span>Home</a></li>
    <li><a href="#"><span class="bnav-ic" aria-hidden="true">🔍</span>Search</a></li>
    <li class="bnav-fab-item"><button class="bnav-fab" type="button" aria-label="New post"><span aria-hidden="true">+</span></button></li>
    <li><a href="#"><span class="bnav-ic" aria-hidden="true">💬</span>Inbox</a></li>
    <li><a href="#"><span class="bnav-ic" aria-hidden="true">👤</span>Profile</a></li>
  </ul>
</nav>
CSS
/* Bottom tab bar: a list of links with aria-current on the active tab and a raised action button in the middle. */
.bnav{background:#fff;border-top:1px solid #efeaf8;padding:10px 8px calc(12px + env(safe-area-inset-bottom))}
.bnav ul{display:flex;list-style:none;margin:0;padding:0}
.bnav li{flex:1;display:flex}
.bnav a{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;font-size:.64rem;font-weight:700;color:#6b6190;text-decoration:none;position:relative;padding:5px 0;border-radius:10px}
.bnav-ic{font-size:1.25rem;transition:transform .18s}
.bnav a[aria-current="page"]{color:#7c3aed}
.bnav a[aria-current="page"] .bnav-ic{transform:translateY(-2px)}
.bnav a[aria-current="page"]::after{content:"";position:absolute;top:-10px;width:34px;height:3px;border-radius:99px;background:#7c3aed}
.bnav li.bnav-fab-item{flex:none}
.bnav-fab{width:52px;height:52px;border:0;border-radius:50%;background:#7c3aed;color:#fff;font-size:1.4rem;line-height:1;cursor:pointer;margin:-26px 10px 0;box-shadow:0 10px 22px -6px rgba(124,58,237,.55);display:grid;place-items:center}
.bnav a:focus-visible,.bnav-fab:focus-visible{outline:2px solid #7c3aed;outline-offset:2px}
@media (prefers-reduced-motion:reduce){.bnav-ic{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<nav class="bnav" aria-label="Main">
  <ul class="nav nav-fill flex-nowrap">
    <li class="nav-item"><a class="nav-link active" href="#" aria-current="page"><span class="bnav-ic" aria-hidden="true">🏠</span>Home</a></li>
    <li class="nav-item"><a class="nav-link" href="#"><span class="bnav-ic" aria-hidden="true">🔍</span>Search</a></li>
    <li class="nav-item bnav-fab-item"><button class="btn bnav-fab" type="button" aria-label="New post"><span aria-hidden="true">+</span></button></li>
    <li class="nav-item"><a class="nav-link" href="#"><span class="bnav-ic" aria-hidden="true">💬</span>Inbox</a></li>
    <li class="nav-item"><a class="nav-link" href="#"><span class="bnav-ic" aria-hidden="true">👤</span>Profile</a></li>
  </ul>
</nav>
CSS
/* Bootstrap .nav.nav-fill as a bottom tab bar. The active tab is .active with aria-current="page";
   the raised action is a .btn with its colours set through --bs-btn-* variables. */
.bnav{background:#fff;border-top:1px solid #efeaf8;padding:10px 8px calc(12px + env(safe-area-inset-bottom))}
.bnav .nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:5px;--bs-nav-link-font-size:.64rem;--bs-nav-link-font-weight:700;--bs-nav-link-color:#6b6190;--bs-nav-link-hover-color:#6b6190}
.bnav .nav-link{display:flex;flex-direction:column;align-items:center;gap:3px;position:relative;border-radius:10px;line-height:normal}
.bnav-ic{font-size:1.25rem;transition:transform .18s}
.bnav .nav-link.active{color:#7c3aed}
.bnav .nav-link.active .bnav-ic{transform:translateY(-2px)}
.bnav .nav-link.active::after{content:"";position:absolute;top:-10px;width:34px;height:3px;border-radius:99px;background:#7c3aed}
.bnav .nav-fill .bnav-fab-item{flex:none}
.bnav .bnav-fab{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-font-size:1.4rem;--bs-btn-line-height:1;--bs-btn-border-width:0;--bs-btn-color:#fff;--bs-btn-bg:#7c3aed;
  --bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6d28d9;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6d28d9;
  width:52px;height:52px;border-radius:50%;margin:-26px 10px 0;box-shadow:0 10px 22px -6px rgba(124,58,237,.55);display:grid;place-items:center}
.bnav .nav-link:focus-visible,.bnav .bnav-fab:focus-visible{outline:2px solid #7c3aed;outline-offset:2px;box-shadow:none}
@media (prefers-reduced-motion:reduce){.bnav-ic{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<nav class="bnav border-t border-[#efeaf8] bg-white px-2 pt-2.5 pb-[calc(12px+env(safe-area-inset-bottom))]" aria-label="Main">
  <ul class="flex">
    <li class="flex flex-1"><a class="group relative flex flex-1 flex-col items-center gap-[3px] rounded-[10px] py-[5px] text-[.64rem] font-bold text-bnav-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-bnav-accent aria-[current=page]:text-bnav-accent aria-[current=page]:after:absolute aria-[current=page]:after:-top-2.5 aria-[current=page]:after:h-[3px] aria-[current=page]:after:w-[34px] aria-[current=page]:after:rounded-full aria-[current=page]:after:bg-bnav-accent aria-[current=page]:after:content-['']" href="#" aria-current="page"><span class="text-[1.25rem] transition-transform duration-[180ms] group-aria-[current=page]:-translate-y-0.5 motion-reduce:transition-none" aria-hidden="true">🏠</span>Home</a></li>
    <li class="flex flex-1"><a class="group relative flex flex-1 flex-col items-center gap-[3px] rounded-[10px] py-[5px] text-[.64rem] font-bold text-bnav-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-bnav-accent aria-[current=page]:text-bnav-accent aria-[current=page]:after:absolute aria-[current=page]:after:-top-2.5 aria-[current=page]:after:h-[3px] aria-[current=page]:after:w-[34px] aria-[current=page]:after:rounded-full aria-[current=page]:after:bg-bnav-accent aria-[current=page]:after:content-['']" href="#"><span class="text-[1.25rem] transition-transform duration-[180ms] group-aria-[current=page]:-translate-y-0.5 motion-reduce:transition-none" aria-hidden="true">🔍</span>Search</a></li>
    <li class="flex flex-none"><button class="mx-2.5 -mt-[26px] grid size-[52px] cursor-pointer place-items-center rounded-full bg-bnav-accent text-[1.4rem] leading-none text-white shadow-[0_10px_22px_-6px_rgba(124,58,237,.55)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-bnav-accent" type="button" aria-label="New post"><span aria-hidden="true">+</span></button></li>
    <li class="flex flex-1"><a class="group relative flex flex-1 flex-col items-center gap-[3px] rounded-[10px] py-[5px] text-[.64rem] font-bold text-bnav-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-bnav-accent aria-[current=page]:text-bnav-accent aria-[current=page]:after:absolute aria-[current=page]:after:-top-2.5 aria-[current=page]:after:h-[3px] aria-[current=page]:after:w-[34px] aria-[current=page]:after:rounded-full aria-[current=page]:after:bg-bnav-accent aria-[current=page]:after:content-['']" href="#"><span class="text-[1.25rem] transition-transform duration-[180ms] group-aria-[current=page]:-translate-y-0.5 motion-reduce:transition-none" aria-hidden="true">💬</span>Inbox</a></li>
    <li class="flex flex-1"><a class="group relative flex flex-1 flex-col items-center gap-[3px] rounded-[10px] py-[5px] text-[.64rem] font-bold text-bnav-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-bnav-accent aria-[current=page]:text-bnav-accent aria-[current=page]:after:absolute aria-[current=page]:after:-top-2.5 aria-[current=page]:after:h-[3px] aria-[current=page]:after:w-[34px] aria-[current=page]:after:rounded-full aria-[current=page]:after:bg-bnav-accent aria-[current=page]:after:content-['']" href="#"><span class="text-[1.25rem] transition-transform duration-[180ms] group-aria-[current=page]:-translate-y-0.5 motion-reduce:transition-none" aria-hidden="true">👤</span>Profile</a></li>
  </ul>
</nav>
CSS
@theme {
  --color-bnav-muted: #6b6190;
  --color-bnav-accent: #7c3aed;
}

About this set

A bottom tab bar for mobile web apps, shown inside a phone frame. Four tabs (Home, Search, Inbox and Profile) sit either side of a round violet New post button that rises above the bar. Each tab stacks an icon over a small label; the current tab turns violet, its icon lifts by two pixels and a short bar appears above it. Use it for the three to five top-level destinations of a phone-first app, with the raised button for the one action people take most often. The tabs are links in a list inside a nav labelled Main, and the current one carries aria-current=”page”, which the CSS uses to draw the active state. The raised button is a real button with an accessible name, since it performs an action instead of going to a page. Bottom padding includes env(safe-area-inset-bottom) so the bar clears the home indicator on notched phones. The Bootstrap version builds the bar from .nav.nav-fill with a .btn for the action, and the Tailwind version styles the current tab with aria-[current=page] variants.

What’s included

  • Four tabs plus a raised circular action button
  • Current tab styled from aria-current, with a top indicator bar
  • Safe-area padding for phones with a home indicator
  • Labels darkened to 4.5:1 against the white bar
  • Phone frame that shrinks to fit a 375px screen

Accessibility

The tabs are links in a list inside a nav labelled Main, so screen readers announce the list size and which tab is the current page. The raised button is named New post and its plus glyph is hidden from assistive technology. Every tab and the button show a 2px violet outline on keyboard focus, and the icon lift is not animated under prefers-reduced-motion.

Customise it

Change the accent in the aria-current rules and .bnav-fab (plain CSS), the .active rules and --bs-btn-bg on .bnav-fab (Bootstrap) or the --color-bnav-accent and --color-bnav-muted theme colours (Tailwind). Swap the emoji for your own icons inside .bnav-ic.