Babelize Elements
Navigation

Navbar

Full-featured navigation bar with built-in language selector, mobile menu, CTA button, and GitHub link.

Installation

npx shadcn@latest add @babelize/navbar

Usage

"use client";

import { useState } from "react";
import { NavBar } from "@/components/ui/navbar";

const links = [
  { label: "Home", href: "/" },
  { label: "Docs", href: "/docs" },
  { label: "Pricing", href: "/pricing" },
];

export function SiteNav() {
  const [locale, setLocale] = useState("en");

  return (
    <NavBar
      logo={<span className="text-lg font-bold">MyApp</span>}
      links={links}
      locales={[{ code: "en" }, { code: "fr" }, { code: "es" }]}
      value={locale} // omit for uncontrolled, use defaultValue instead
      onValueChange={setLocale}
      cta={{ label: "Get Started", href: "/signup" }}
      showGitHub
      githubUrl="your-org/your-repo" // or a full URL
    />
  );
}

Props

PropTypeRequiredDescription
logoReact.ReactNodeNoLogo element (renders before nav links)
linksNavLink[]NoNavigation links
activeHrefstringNohref of the current page — gets aria-current and a highlight
localesLocale[]NoAvailable locales for language selector
valuestringNoSelected locale code — pass this to control the component
defaultValuestringNoInitial locale code when uncontrolled (default: first locale)
onValueChange(code: string) => voidNoCallback when locale changes
cta{ label: string; href: string }NoCTA button
showGitHubbooleanNoShow the GitHub icon button. Needs githubUrl — without one, nothing renders
githubUrlstringNoFull URL, or "owner" / "owner/repo" shorthand for github.com
stickybooleanNoMake navbar sticky at top (default: true)
variant"default" | "floating"NoFull-width bar or inset floating card (default: "default")
showFlagsbooleanNoShow flags in language selector (default: true)
maxHeightnumber | stringNoMax height of the scrolling language menu (default: "15rem")
linkComponentReact.ElementTypeNoComponent used for internal links (default: "a")
labelsPartial<NavBarLabels>NoOverride the built-in English UI text (see below)
classNamesNavBarClassNamesNoPer-part class overrides (see below)
classNamestringNoAdditional CSS classes

Any other prop is spread onto the underlying <nav> element, and the ref is forwarded to it.

Client-side navigation

Links render as plain <a> elements, so NavBar works in any React app with no router installed. Pass your router's link component to opt into client-side navigation:

import Link from "next/link";

<NavBar links={links} linkComponent={Link} />;

Styling parts

classNames merges extra classes into each part with cn, so your classes win over the defaults:

KeyPart
rootThe <nav> element (same as className)
containerThe bar itself — the full-width row, or the floating card
linksThe desktop link group
linkEach link, on desktop and in the mobile menu
actionsThe desktop group with the language menu, GitHub and CTA
ctaThe call-to-action button
languageTriggerThe language menu triggers
languageContentThe language menu panel
languageItemEach language option
mobileMenuThe mobile menu panel
<NavBar
  links={links}
  classNames={{
    container: "max-w-4xl",
    cta: "rounded-full",
    link: "text-base",
  }}
/>
PropertyTypeDescription
labelstringLink text — required
hrefstringLink URL — required

Locale Object

PropertyTypeDescription
codestringISO 639-1 / BCP 47 code (e.g. "en", "fr-FR") — required
labelstringOverride display name (auto-resolved if omitted)
flagstringOverride flag emoji (auto-detected if omitted)

Examples

With Babelize SDK

Pair NavBar with the Babelize SDK for automatic locale routing:

"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import { NavBar } from "@/components/ui/navbar";
import { useBabelize } from "@babelize/sdk/react";

const links = [
  { label: "Home", href: "/" },
  { label: "Docs", href: "/docs" },
  { label: "Pricing", href: "/pricing" },
];

const locales = [
  { code: "en", label: "English", flag: "🇺🇸" },
  { code: "fr", label: "Français", flag: "🇫🇷" },
  { code: "es", label: "Español", flag: "🇪🇸" },
  { code: "de", label: "Deutsch", flag: "🇩🇪" },
  { code: "ja", label: "日本語", flag: "🇯🇵" },
  { code: "ar", label: "العربية", flag: "🇸🇦" },
];

export default function App() {
  const router = useRouter();
  const { locale, setLocale } = useBabelize();

  return (
    <NavBar
      logo={<span className="font-bold">MyApp</span>}
      links={links}
      locales={locales}
      value={locale}
      onValueChange={(code) => {
        setLocale(code);
        router.push(`/${code}`);
      }}
      cta={{ label: "Get Started", href: "/signup" }}
      showGitHub
      githubUrl="your-org/your-repo"
    />
  );
}

With next-intl

"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import { NavBar } from "@/components/ui/navbar";

export function AppNavBar() {
  const router = useRouter();
  const [locale, setLocale] = useState("en");

  return (
    <NavBar
      logo={<span className="font-bold">MyApp</span>}
      links={[
        { label: "Home", href: "/" },
        { label: "About", href: "/about" },
      ]}
      locales={[{ code: "en" }, { code: "fr" }]}
      value={locale}
      onValueChange={(code) => {
        setLocale(code);
        router.push(`/${code}`);
      }}
    />
  );
}

With react-i18next

"use client";

import { useTranslation } from "react-i18next";
import { NavBar } from "@/components/ui/navbar";

export function AppNavBar() {
  const { i18n } = useTranslation();

  return (
    <NavBar
      logo={<span className="font-bold">MyApp</span>}
      links={[{ label: "Home", href: "/" }]}
      locales={[{ code: "en" }, { code: "fr" }]}
      value={i18n.language}
      onValueChange={(code) => i18n.changeLanguage(code)}
    />
  );
}

Floating

An inset, rounded bar that floats above the page — pair it with activeHref to highlight the current page:

"use client";

import { NavBar } from "@/components/ui/navbar";

export default function App() {
  return (
    <NavBar
      variant="floating"
      logo={<span className="font-bold">MyApp</span>}
      links={[
        { label: "Home", href: "/" },
        { label: "Docs", href: "/docs" },
      ]}
      activeHref="/docs"
      cta={{ label: "Get Started", href: "/signup" }}
    />
  );
}

showGitHub adds an icon button that links to githubUrl. Pass "owner/repo" for a repository, "owner" for a profile or organisation, or any full URL.

<NavBar showGitHub githubUrl="acme/website" />
<NavBar showGitHub githubUrl="https://github.com/acme" />

showGitHub without githubUrl renders no button and logs a warning.

Non-sticky

"use client";

import { NavBar } from "@/components/ui/navbar";

export default function App() {
  return (
    <NavBar
      logo={<span className="font-bold">MyApp</span>}
      links={[{ label: "Home", href: "/" }]}
      sticky={false}
    />
  );
}

Translated UI text

The language menu, mobile menu button and GitHub link carry English accessible labels by default. Pass labels to translate them — any key you leave out keeps its English default.

"use client";

import { NavBar } from "@/components/ui/navbar";

export default function App() {
  return (
    <NavBar
      logo={<span className="font-bold">MyApp</span>}
      locales={[{ code: "en" }, { code: "fr" }]}
      defaultValue="fr"
      labels={{
        listbox: "Choisir la langue",
        current: (name) => `Langue actuelle : ${name}`,
        toggleMenu: "Ouvrir le menu",
      }}
    />
  );
}
KeyDefaultUsed for
listbox"Select language"Language menu accessible label
current(name) => "Current language: " + nameLanguage trigger accessible label
toggleMenu"Toggle menu"Mobile menu button label
github"GitHub"GitHub icon link label

Keyboard support

The language menu follows the WAI-ARIA listbox pattern. With no text field in the menu, arrow keys move real DOM focus between the options instead of tracking a highlight from a search box. The same keys work from the desktop and the mobile bar.

KeyAction
Enter, Space, or clickOpen the menu from the language trigger
ArrowDown / ArrowUp (closed)Open with the first / last locale focused
ArrowDown / ArrowUpMove focus between locales, wrapping at both ends
Home / EndFocus the first / last locale
EnterSelect the focused locale and return focus to the trigger
EscapeClose the menu (and the mobile menu) and return focus to the trigger

The highlight stays on its row while the list scrolls beneath it — a long list never scrolls the page. Pass maxHeight to set how tall the menu gets.

Features

  • Responsive — hamburger menu on mobile, full layout on desktop
  • Built-in language selector — same dropdown component as LanguageSwitcher
  • RTL-aware — auto-detects RTL locales and adjusts layout
  • CTA button — optional call-to-action with customizable text and href
  • GitHub link — optional icon button linking to your repo
  • Sticky/non-sticky — toggle top-of-page stickiness
  • Keyboard accessible — arrow keys, Home / End, Enter, and Escape navigation
  • ARIA labels — aria-expanded, aria-haspopup, aria-selected, and roving focus over the options
  • Themeable — styled with shadcn/ui tokens, so it follows your theme and dark mode
  • Two variants — full-width bar or floating inset card
  • Animated — hover highlight that glides between links, animated mobile menu and language menu; honors prefers-reduced-motion
  • Router-agnostic — pass next/link (or any link) through linkComponent

Dependencies

  • motion — animations
  • lucide-react — icons

Peer dependencies:

  • react >= 18
  • react-dom >= 18
  • tailwindcss >= 3

Credits

Created by sapatmohit.

On this page