Navbar
Full-featured navigation bar with built-in language selector, mobile menu, CTA button, and GitHub link.
Installation
npx shadcn@latest add @babelize/navbarUsage
"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
| Prop | Type | Required | Description |
|---|---|---|---|
logo | React.ReactNode | No | Logo element (renders before nav links) |
links | NavLink[] | No | Navigation links |
activeHref | string | No | href of the current page — gets aria-current and a highlight |
locales | Locale[] | No | Available locales for language selector |
value | string | No | Selected locale code — pass this to control the component |
defaultValue | string | No | Initial locale code when uncontrolled (default: first locale) |
onValueChange | (code: string) => void | No | Callback when locale changes |
cta | { label: string; href: string } | No | CTA button |
showGitHub | boolean | No | Show the GitHub icon button. Needs githubUrl — without one, nothing renders |
githubUrl | string | No | Full URL, or "owner" / "owner/repo" shorthand for github.com |
sticky | boolean | No | Make navbar sticky at top (default: true) |
variant | "default" | "floating" | No | Full-width bar or inset floating card (default: "default") |
showFlags | boolean | No | Show flags in language selector (default: true) |
maxHeight | number | string | No | Max height of the scrolling language menu (default: "15rem") |
linkComponent | React.ElementType | No | Component used for internal links (default: "a") |
labels | Partial<NavBarLabels> | No | Override the built-in English UI text (see below) |
classNames | NavBarClassNames | No | Per-part class overrides (see below) |
className | string | No | Additional 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:
| Key | Part |
|---|---|
root | The <nav> element (same as className) |
container | The bar itself — the full-width row, or the floating card |
links | The desktop link group |
link | Each link, on desktop and in the mobile menu |
actions | The desktop group with the language menu, GitHub and CTA |
cta | The call-to-action button |
languageTrigger | The language menu triggers |
languageContent | The language menu panel |
languageItem | Each language option |
mobileMenu | The mobile menu panel |
<NavBar
links={links}
classNames={{
container: "max-w-4xl",
cta: "rounded-full",
link: "text-base",
}}
/>NavLink Object
| Property | Type | Description |
|---|---|---|
label | string | Link text — required |
href | string | Link URL — required |
Locale Object
| Property | Type | Description |
|---|---|---|
code | string | ISO 639-1 / BCP 47 code (e.g. "en", "fr-FR") — required |
label | string | Override display name (auto-resolved if omitted) |
flag | string | Override 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" }}
/>
);
}GitHub link
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",
}}
/>
);
}| Key | Default | Used for |
|---|---|---|
listbox | "Select language" | Language menu accessible label |
current | (name) => "Current language: " + name | Language 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.
| Key | Action |
|---|---|
Enter, Space, or click | Open the menu from the language trigger |
ArrowDown / ArrowUp (closed) | Open with the first / last locale focused |
ArrowDown / ArrowUp | Move focus between locales, wrapping at both ends |
Home / End | Focus the first / last locale |
Enter | Select the focused locale and return focus to the trigger |
Escape | Close 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) throughlinkComponent
Dependencies
motion— animationslucide-react— icons
Peer dependencies:
react>= 18react-dom>= 18tailwindcss>= 3
Credits
Created by sapatmohit.
Locale Picker
Region picker with locale-derived defaults — currency, date and number formats, and first day of week. Searchable, keyboard accessible, and RTL-aware.
Phone Input
International phone number input with country selector, flag emojis, and dial code search. Supports 50 countries with built-in formatting and validation.