Navbar

Top navigation bar containing site branding, tab items, and search actions.

The Navbar component displays site logos, navigation links, documentation tabs, and search triggers.

Composable Navbar ExampleLink

Using the Navbar primitive and its subcomponents, you can design a fully custom navigation bar. The following example shows how to build a header with active tab matching, search triggers, social integrations, and theme toggling:

tsx
// docs/components/CustomNavbar.tsx
import React, { useState } from 'react'
import { Navbar } from 'boltdocs/primitives'
import { useNavbar, useTheme, useSearch } from 'boltdocs/client'

export default function CustomNavbar() {
  const { links, theme, logo, title } = useNavbar()
  const { toggleTheme } = useTheme()
  const { openSearch } = useSearch()
  const [mobileOpen, setMobileOpen] = useState(false)

  return (
    <Navbar className="bg-white/80 dark:bg-black/80 backdrop-blur-md border-b border-subtle">
      <Navbar.Content>
        {/* Left Section: Branding */}
        <Navbar.Left>
          {logo && <Navbar.Logo src={logo} alt={title} href="/" />}
          <Navbar.Title className="font-bold text-lg">{title}</Navbar.Title>
        </Navbar.Left>

        {/* Center Section: Main Navigation Links */}
        <Navbar.Center>
          {links.map((link) => (
            <Navbar.Link
              key={link.href}
              href={link.href}
              label={link.label}
              className={link.active ? 'text-primary font-medium' : 'text-muted'}
            />
          ))}
        </Navbar.Center>

        {/* Right Section: Actions */}
        <Navbar.Right>
          {/* Custom Search Dialog Trigger */}
          <Navbar.SearchTrigger onPress={openSearch} />

          {/* Theme Toggle Button */}
          <Navbar.Theme
            theme={theme}
            onThemeChange={(isDark) => toggleTheme()}
          />

          {/* Github Social Button */}
          <Navbar.Socials icon="github" link="https://github.com/myorg/myrepo" />

          {/* Mobile Drawer Trigger (More Vertical Icon) */}
          <Navbar.More onPress={() => setMobileOpen(true)} className="lg:hidden" />
        </Navbar.Right>
      </Navbar.Content>

      {/* Responsive Mobile Drawer Menu */}
      <Navbar.MobileMenu isOpen={mobileOpen} onClose={() => setMobileOpen(false)}>
        {links.map((link) => (
          <Navbar.MobileLink
            key={link.href}
            href={link.href}
            label={link.label}
            onPress={() => setMobileOpen(false)}
            className="text-lg py-2"
          />
        ))}
      </Navbar.MobileMenu>
    </Navbar>

Real-World Production ReferenceLink

Below is a complete, production-ready reference implementation demonstrating how to build a fully-featured navigation bar (similar to the built-in theme navbar). It integrates version-switching dropdowns, language translation selectors, responsive sidebar toggling, sub-link menus, theme modes, and a responsive mobile menu:

tsx
// docs/components/ProductionNavbar.tsx
import React, { useState } from 'react'
import { Navbar, Menu, Button } from 'boltdocs/primitives'
import {
  useNavbar,
  useTheme,
  useSearch,
  useVersion,
  useI18n,
  useUI,
  useLocation,
  useLocalizedTo
} from 'boltdocs/client'
import { Menu as MenuIcon, X, ChevronDown, Languages } from 'lucide-react'

export default function ProductionNavbar() {
  const { links, title, logo, logoProps, github, social, config } = useNavbar()
  const { theme, toggleTheme } = useTheme()
  const { openSearch } = useSearch()
  const { isSidebarOpen, toggleSidebar } = useUI()
  const { pathname } = useLocation()
  const [mobileOpen, setMobileOpen] = useState(false)

  // 1. Version Switch Dropdown Menu
  const { currentVersionLabel, availableVersions, handleVersionChange } = useVersion()
  const VersionDropdown = availableVersions.length > 0 && (
    <Menu.Trigger>
      <Button className="flex items-center gap-1.5 px-3 py-1.5 border rounded-xl text-xs bg-surface font-semibold text-body hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-all">
        <span>{currentVersionLabel}</span>
        <ChevronDown size={14} className="opacity-60" />
      </Button>
      <Menu.Root className="w-40 bg-white dark:bg-zinc-900 border border-subtle rounded-xl p-1.5 shadow-md flex flex-col gap-0.5">
        <Menu.Section items={availableVersions}>
          {(version) => (
            <Menu.Item
              key={version.value}
              onPress={() => handleVersionChange(version.value)}
              className="px-3 py-2 text-xs rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer outline-none"
            >
              {version.label}
            </Menu.Item>
          )}
        </Menu.Section>
      </Menu.Root>
    </Menu.Trigger>
  )

  // 2. Language/i18n Dropdown Selector
  const { currentLocale, availableLocales, handleLocaleChange } = useI18n()
  const LocaleDropdown = availableLocales.length > 0 && (
    <Menu.Trigger>
      <Button className="flex items-center gap-1.5 px-3 py-1.5 border border-subtle bg-surface rounded-xl text-xs font-semibold text-body hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-all">
        <Languages size={14} className="text-primary-500" />
        <span className="uppercase">{currentLocale}</span>
        <ChevronDown size={14} className="opacity-60" />
      </Button>
      <Menu.Root className="w-40 bg-white dark:bg-zinc-900 border border-subtle rounded-xl p-1.5 shadow-md flex flex-col gap-0.5">
        <Menu.Section items={availableLocales}>
          {(locale) => (
            <Menu.Item
              key={locale.value}
              onPress={() => handleLocaleChange(locale.value)}
              className="px-3 py-2 text-xs rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer outline-none"
            >
              {locale.label}
            </Menu.Item>
          )}
        </Menu.Section>
      </Menu.Root>
    </Menu.Trigger>
  )

  return (
    <Navbar className="bg-main/80 backdrop-blur-md border-b border-subtle">
      <Navbar.Content>
        
        {/* Left Section: Mobile sidebar toggler, Logo, Title and Version Selection */}
        <Navbar.Left>
          {pathname.startsWith('/docs') && (
            <Button
              onPress={toggleSidebar}
              className="mr-2 lg:hidden p-1.5 text-muted hover:text-body rounded-lg hover:bg-surface transition-colors"
            >
              {isSidebarOpen ? <X size={20} /> : <MenuIcon size={20} />}
            </Button>
          )}

          {logo && <Navbar.Logo src={logo} alt={logoProps?.alt || title} href="/" />}
          <Navbar.Title href="/">{title}</Navbar.Title>
          
          <div className="hidden sm:block">
            {VersionDropdown}
          </div>
        </Navbar.Left>

        {/* Center Section: Search dialog trigger bar */}
        <Navbar.Center>
          <Navbar.SearchTrigger onPress={openSearch} />
        </Navbar.Center>

        {/* Right Section: Navigation Links, i18n switcher, theme toggle, Github stars and socials */}
        <Navbar.Right>
          {/* Main Desktop Links */}
          <Navbar.Links>
            {links.map((link) => {
              const localizedHref = useLocalizedTo(link.href || '')
              const active = pathname === localizedHref || pathname.startsWith(localizedHref + '/')
              const hasItems = link.items && link.items.length > 0

              if (hasItems) {
                return (
                  <Navbar.Dropdown
                    key={link.href}
                    label={
                      <span className={`px-2 py-1 font-medium transition-colors cursor-pointer ${
                        active ? 'text-primary' : 'text-muted hover:text-body'
                      }`}>
                        {link.label}
                      </span>
                    }
                  >
                    {link.items?.map((sub) => (
                      <Navbar.DropdownItem
                        key={sub.href}
                        href={useLocalizedTo(sub.href || '')}
                        label={sub.label}
                      />
                    ))}
                  </Navbar.Dropdown>
                )
              }

              return (
                <Navbar.Link
                  key={link.href}
                  href={localizedHref}
                  label={link.label}
                  active={active}
                  className={active ? 'text-primary font-semibold' : 'text-muted hover:text-body'}
                />
              )
            })}
          </Navbar.Links>

          {/* Desktop utility settings */}
          <div className="hidden sm:flex items-center gap-2">
            {LocaleDropdown}
            <Navbar.Split className="bg-subtle" />
          </div>

          <Navbar.Theme theme={theme} onThemeChange={toggleTheme} />

          {/* Social connections (e.g., GitHub link button) */}
          {github && (
            <Navbar.Socials icon="github" link={github} />
          )}

          {/* "More" trigger for mobile drawers */}
          <Navbar.More onPress={() => setMobileOpen(true)} className="lg:hidden" />
        </Navbar.Right>
      </Navbar.Content>

      {/* Mobile drawer menu overlay */}
      <Navbar.MobileMenu isOpen={mobileOpen} onClose={() => setMobileOpen(false)}>
        <div className="flex flex-col gap-1">
          {links.map((link) => {
            const localizedHref = useLocalizedTo(link.href || '')
            const active = pathname === localizedHref

            return (
              <Navbar.MobileLink
                key={link.href}
                href={localizedHref}
                label={link.label}
                active={active}
                onPress={() => setMobileOpen(false)}
                className="text-lg py-2"
              />
            )
          })}
        </div>

        {/* Unified version and language selection list in mobile view */}
        <div className="mt-6 border-t border-subtle pt-6 flex flex-col gap-4">
          <div className="flex justify-between items-center px-4">
            <span className="text-xs text-muted font-semibold uppercase">Version</span>
            {VersionDropdown}
          </div>
          <div className="flex justify-between items-center px-4">
            <span className="text-xs text-muted font-semibold uppercase">Language</span>
            {LocaleDropdown}
          </div>
        </div>
      </Navbar.MobileMenu>
    </Navbar>
  )
}

Composable SubcomponentsLink

The Navbar primitive provides the following subcomponents for structure customization:

ComponentHTML TagDescriptionProps
Navbar.Root / Navbar<header>Main outer navbar container.ComponentBaseProps
Navbar.Content<div>Centered layout container holding the inner flex elements.ComponentBaseProps
Navbar.Left / Navbar.Center / Navbar.Right<div>Flex row alignment zones for logos, navigation links, and action buttons.ComponentBaseProps
Navbar.Logo<img> wrapped in <a>Renders a brand image logo wrapped in a client Router link.Navbar.Logo Props
Navbar.Title<span>Renders a plain text typographic brand label.ComponentBaseProps
Navbar.Link<a>Renders an optimized anchor link to a page route.Navbar.Link Props
Navbar.Dropdown<div>Collapsible trigger with an open/close state, optionally controlled.Navbar.Dropdown Props
Navbar.DropdownItem<a>A single item rendered inside a dropdown panel.Navbar.Link Props
Navbar.SearchTrigger<button>A fully accessible button to activate the search dialog.Navbar.SearchTrigger Props
Navbar.Theme<button>Renders an icon button to toggle light/dark mode.Navbar.Theme Props
Navbar.Socials<a>An icon social link shortcut (e.g., GitHub, Discord, Twitter).Navbar.Socials Props
Navbar.MobileMenu<div> (Modal)Responsive sliding modal drawer overlay for mobile screens.Navbar.MobileMenu Props
Navbar.MobileLink<a>Sized navigation items designed for mobile drawer menus.Navbar.MobileLink Props

Component PropsLink

ComponentBaseProps (Common)Link

PropertyTypeDefaultDescription
childrenReactNodeundefinedChild content elements.
classNamestringundefinedCustom utility CSS classes.
styleCSSPropertiesundefinedInline style settings.
PropertyTypeDefaultDescription
srcstringRequiredPath to the logo image file.
altstringRequiredAccessibility alternative text.
hrefstring'/'Target path when clicking the logo.
widthnumber24Image width dimension.
heightnumber24Image height dimension.
PropertyTypeDefaultDescription
labelReactNodeRequiredLabel content text to display.
hrefstringRequiredRouting destination path.
to'internal' | 'external''internal'Whether the navigation is internal routing or an external link.
PropertyTypeDefaultDescription
labelReactNodeRequiredThe visible trigger text.
childrenReactNodeRequiredThe dropdown panel content, e.g. one or more <Navbar.DropdownItem> rows.
iconReactNode(down chevron)Custom indicator icon that replaces the default chevron.
iconClassNamestringundefinedExtra classes for the indicator wrapper.
openbooleanundefinedControlled open state. When omitted, the dropdown manages its own state.
onOpenChange(open: boolean) => voidundefinedCallback fired when the dropdown open state changes.
triggerClassNamestringundefinedExtra classes for the dropdown trigger row.
panelClassNamestringundefinedExtra classes for the absolute-positioned popup wrapper.
menuClassNamestringundefinedExtra classes for the styled dropdown panel.

PropertyTypeDefaultDescription
onPress() => voidRequiredPress trigger action callback.
PropertyTypeDefaultDescription
theme'dark' | 'light'RequiredActive theme selection.
onThemeChange(isSelected: boolean) => voidRequiredTheme toggle change callback.
PropertyTypeDefaultDescription
icon'github' | 'discord' | 'twitter'RequiredTarget social network graphic icon.
linkstringRequiredURL link path.
PropertyTypeDefaultDescription
isOpenbooleanfalseActive sliding visibility state.
onClose() => voidRequiredClose trigger callback.
childrenReactNodeRequiredMobile drawer menu content cells.

Inherits all Navbar.Link props plus:

PropertyTypeDefaultDescription
onPress() => voidundefinedCustom click callback (e.g., to auto-close drawers).

Customization & statesLink

The Navbar primitive is style-neutral: it ships the header structure and interactive behavior, and every visual decision (colors, borders, spacing, sizes) belongs to the site theme through its className slots. It bakes in structural layout — the root <header> is sticky top-0 and Navbar.Content is a centered flex row — but no colors or borders. Interactive states are styled from CSS.

State / SlotMechanism
Navbar (root)<header> ships the sticky top-0 z-50 w-full layout; height, background, and border via className
Navbar.ContentBaked-in centered flex row (justify-between); spacing via className
Active linkNavbar.Link emits no data-* attribute — compute it in the theme with useLocalizedTo/useLocation and apply className conditionally
Navbar.ThemeThe RAC ToggleButton emits data-selected when dark mode is active
Navbar.More / Navbar.SearchTriggerThe RAC Button emits data-pressed while pressed
Navbar.MobileMenuThe RAC modal overlay mounts only while open and emits data-entering/data-exiting during transitions
tsx
const { pathname } = useLocation()
const href = useLocalizedTo('/docs/guides')
const active = pathname === href || pathname.startsWith(href + '/')

<Navbar className="border-b border-subtle bg-main/80 backdrop-blur-md">
  <Navbar.Link
    href={href}
    label="Guides"
    className={active ? 'text-primary font-semibold' : 'text-muted hover:text-body'}
  />
  <Navbar.Theme
    theme={theme}
    onThemeChange={toggleTheme}
    className="data-selected:text-primary-500"
  />
  <Navbar.More onPress={() => setMobileOpen(true)} className="data-pressed:bg-soft lg:hidden" />
</Navbar>
Last updated on October 18, 2026

Was this page helpful?