Morphing Segmented Navbar with Detached Active Pill

A site header whose links are joined into a single dark bar. The active link pulls away from the bar with a gap on each side and becomes its own rounded pill in a bright accent colour, while the links next to it round their facing corners so each remaining piece looks complete. Choosing another link slides the gap along with a short transition. The demo shows the bar in a full header with a wordmark and a button, above a page area that changes its heading, text and three tiles with the selected link. On phones the bar drops to its own centred row under the logo and button.

Code

// Adapted from Kokonut UI (https://github.com/kokonut-labs/kokonutui/blob/main/components/kokonutui/morphic-navbar.tsx)
// Copyright (c) 2025 kokonutUI. MIT License: https://we.inc/components/licenses
import { useState } from "react";

// The pages in the bar, and what the demo page shows for each.
const pages = [
  { id: "home", label: "Home", title: "Furniture that outlives the lease", blurb: "Flat-pack oak pieces made in a workshop outside Aarhus, shipped in boxes one person can carry.", tiles: ["Shelving", "Tables", "Benches"] },
  { id: "work", label: "Work", title: "Forty two pieces since 2019", blurb: "Every design stays in production until we find a better joint. Nine have been retired so far.", tiles: ["Lund shelf", "Havn desk", "Skov stool"] },
  { id: "journal", label: "Journal", title: "Notes from the workshop", blurb: "What we learned about oiling end grain, and why the new bench has three legs instead of four.", tiles: ["On oil", "Three legs", "Offcuts"] },
  { id: "about", label: "About", title: "Six people and a very old saw", blurb: "We started in a rented garage. We still answer every email ourselves, usually within a day.", tiles: ["The team", "The workshop", "Visit us"] },
];

const TILE_COLOURS = ["from-lime-200 to-lime-100", "from-stone-300 to-stone-200", "from-amber-200 to-orange-100"];

export default function MorphingSegmentedNavbarWithDetachedActivePill() {
  const [activeId, setActiveId] = useState("work");
  const activeIndex = pages.findIndex((page) => page.id === activeId);
  const page = pages[activeIndex];

  return (
    <div className="w-full bg-stone-200 px-4 py-10 sm:py-14">
      <div className="mx-auto max-w-4xl overflow-hidden rounded-3xl border border-stone-300 bg-[#faf8f3] shadow-xl shadow-stone-400/30">
        <header className="flex flex-wrap items-center justify-between gap-x-4 gap-y-4 px-5 py-4 sm:px-8">
          <a href="#" className="flex items-center gap-2 rounded-md text-base font-bold tracking-tight text-stone-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-900 focus-visible:ring-offset-2">
            <span className="h-5 w-5 rounded-full bg-stone-900 ring-4 ring-lime-300" aria-hidden="true" />
            Alder Works
          </a>

          {/* Every link is its own dark block. Neighbours touch, so the bar reads as one piece until the active link pulls away from it. */}
          <nav aria-label="Main" className="order-last flex w-full justify-center sm:order-none sm:w-auto">
            <ul className="flex items-center">
              {pages.map((item, i) => {
                const active = i === activeIndex;
                const roundLeft = i === 0 || i - 1 === activeIndex;
                const roundRight = i === pages.length - 1 || i + 1 === activeIndex;
                return (
                  <li key={item.id}>
                    <a
                      href="#"
                      aria-current={active ? "page" : undefined}
                      onClick={(event) => {
                        event.preventDefault();
                        setActiveId(item.id);
                      }}
                      className={`flex h-10 items-center justify-center px-4 text-sm transition-all duration-300 ease-out focus-visible:relative focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-900 focus-visible:ring-offset-2 motion-reduce:transition-none sm:px-5 ${
                        active
                          ? "mx-2 rounded-xl bg-lime-300 font-semibold text-stone-950"
                          : `bg-stone-900 text-stone-100 hover:bg-stone-700 ${roundLeft ? "rounded-l-xl" : ""} ${roundRight ? "rounded-r-xl" : ""}`
                      }`}
                    >
                      {item.label}
                    </a>
                  </li>
                );
              })}
            </ul>
          </nav>

          <a href="#" className="inline-flex h-10 items-center justify-center rounded-xl border border-stone-400 px-4 text-sm font-semibold text-stone-900 transition hover:bg-stone-900 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-900 focus-visible:ring-offset-2">
            Order a sample
          </a>
        </header>

        <div className="border-t border-stone-200 px-5 pb-8 pt-10 sm:px-8 sm:pt-12">
          <p className="text-xs font-semibold uppercase tracking-[0.2em] text-stone-600">{page.label}</p>
          <h2 className="mt-3 max-w-xl text-3xl font-bold tracking-tight text-stone-900 sm:text-4xl">{page.title}</h2>
          <p className="mt-3 max-w-lg text-base leading-relaxed text-stone-700">{page.blurb}</p>
          <ul className="mt-8 grid grid-cols-3 gap-3 sm:gap-4">
            {page.tiles.map((tile, i) => (
              <li key={tile} className={`flex h-28 items-end rounded-2xl bg-gradient-to-br p-3 text-sm font-semibold text-stone-900 sm:h-36 sm:p-4 ${TILE_COLOURS[i]}`}>
                {tile}
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/morphing-segmented-navbar-with-detached-active-pill.json

When to use it

How to customize it

Accessibility

Questions

How does the bar split around the active link?

There is no separate background element. Every link has its own dark background and they sit flush, so they look like one bar. The active link gets a horizontal margin, and the links just before and after it get rounded corners on the facing side. Because margin and radius both transition, the bar appears to stretch and split.

How do I use it with a router?

Replace the `useState` value with the current path from your router, give each item its real `href`, and remove the `preventDefault` click handler. Work out `activeIndex` from the path.

Will the layout shift when the active link changes?

The bar keeps the same total width because exactly one link has margins at any time. Only the position of the gap moves. The bold weight on the active label can change its width by a pixel or two; use the same weight on all links if that matters.

How many links can it hold?

Four fit a 360 pixel phone with the default padding. For five or more, reduce `px-4` to `px-3` or switch to a menu button on small screens.

Can the active pill match the bar colour?

Yes. Give it the same background and text classes as the other links. The split and the bold label then mark the active page by shape alone.

Adapted from Morphic Navbar in Kokonut UI, used under its open-source licence. Licences and credits

Related: Navbar with Centered Links, CTA Button and Mobile Menu, Floating Navbar That Shrinks to a Pill on Scroll, Segmented Control with Spring Pill and Morphing Views.

More navbars or all free UI components. Start for free to have a full site built around it.

More in Navbars

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap