Skip to content
txbrendan/ui

Navbar

beta

The bar across the top of a site: brand, links, and actions, with a menu on narrow screens.

Built from Button and Popover.

Preview

flat links, with the current page marked

floating: a pill once the page scrolls

no actions

narrow container: press the menu button

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the website component. It also adds every component it uses. Its own files go in your components folder, and they are yours to edit.

    npx shadcn@latest add @txbrendan/navbar

Use it for

  • The top of a marketing site, docs site, or app, with up to six main links.
  • Group related pages into a menu, with `items`, like Product or Who it's for. Give its items a short `description` when the names alone don't say enough.
  • Mark the page you are on with `current: true`, so it gets a soft fill and aria-current.
  • Pass your router's link through `renderLink`, like next/link, so links stay client-side.
  • Put a sign-in and a main call to action in `actions`. On narrow screens they move into the menu.
  • Set `collapseBelow` to the smallest width your links and actions fit in. More links need a bigger one.
  • Set `floating` on a landing page, so the bar stays in reach as a pill while people scroll.

Avoid it for

  • More than six entries in the bar. Group the rest into a menu, or move them into the footer.
  • A menu with one item, or more than about eight. Link straight to the one page, or split the list.
  • A menu inside a menu. On narrow screens each menu is a heading with its links under it, one level deep.
  • Links inside an app's own screens, like settings sections. Use Tabs, or a sidebar.
  • A trail of where you are. Use Breadcrumb.

Props

  • brand

    Your logo or name. It links to brandHref.

    ReactNode

  • brandHref

    Where the brand links.

    string · default "/"

  • links

    The main links, in order. Each href must be unique, since it is the key. A menu, with `items`, opens a list of links instead.

    ({ href: string; label: string; current?: boolean } | NavbarMenu)[]

  • NavbarMenu

    Type. A menu in the bar. It opens on hover after a short pause, or on a click or Enter. Items with a description show it under the label, two columns from four items. An icon needs a description. Labels must be unique.

    { label: string; items: { href: string; label: string; description?: ReactNode; icon?: ReactNode; current?: boolean }[] }

  • actions

    Buttons on the right, like Sign in and Create a team. On narrow screens they sit at the bottom of the menu.

    ReactNode

  • renderLink

    Draws the brand and each link. Spread the props onto your router's link: (props) => <Link {...props} />.

    (props: NavbarLinkProps) => ReactNode · default a plain <a>

  • label

    The name screen readers give the nav.

    string · default "Main"

  • menuLabel

    The name of the menu button on narrow screens.

    string · default "Menu"

  • collapseBelow

    The navbar's own width below which links fold into the menu: 36, 42, 48, or 56rem. Pick the smallest your links and actions fit in.

    "sm" | "md" | "lg" | "xl" · default "lg"

  • floating

    Pins the navbar to the top. Once the page scrolls, the bar lifts into a rounded pill with a shadow, and the menu opens as a rounded panel.

    boolean · default false

  • className

    Extra classes on the header, which holds the container query. Add sticky top-0 z-40 here to pin it without the pill.

    string

Accessibility

  • The links sit in a nav landmark named by `label`. Only one copy of the links is on screen at a time, so screen readers hear one nav.
  • The current link has aria-current="page".
  • On narrow screens a menu button opens the links. It says whether it is open with aria-expanded.
  • Escape closes the menu and puts focus back on its button. A press outside the bar closes it too.
  • Links in the menu are at least 44px tall for touch.
  • On narrow screens `actions` render a second time, inside the menu. Do not give them an id.
  • A menu's trigger is a button with aria-expanded. Its list opens as a non-modal popup named by the menu's label, so Tab moves on past it and closes it.
  • Escape closes a menu and puts focus back on its trigger. Picking an item closes it too.
  • The trigger also opens on hover, but never only on hover: a click, Enter, or Space opens it as well.
  • In the narrow menu each menu's links sit in a list named by its heading, so nothing nests.

Examples

A marketing site

<Navbar
  brand={<span className="font-semibold">Workspace</span>}
  links={[
    { href: "/features", label: "Features" },
    { href: "/pricing", label: "Pricing", current: true },
    { href: "/docs", label: "Docs" },
  ]}
  actions={
    <>
      <Button emphasis="ghost" size="sm">Sign in</Button>
      <Button tone="brand" size="sm">Create a team</Button>
    </>
  }
/>

A floating pill on a landing page

<Navbar
  floating
  brand="Workspace"
  links={[{ href: "/features", label: "Features" }, { href: "/pricing", label: "Pricing" }]}
  actions={<Button tone="brand" emphasis="outline" size="sm">Request access</Button>}
/>

With menus

<Navbar
  brand="Workspace"
  links={[
    {
      label: "Product",
      items: [
        { href: "/plan", label: "Planning", description: "Set up the week, with who is coming." },
        { href: "/agendas", label: "Agendas", description: "Run saved agendas and track goals." },
      ],
    },
    { href: "/pricing", label: "Pricing" },
    { label: "More", items: [{ href: "/blog", label: "Blog" }, { href: "/about", label: "About" }] },
  ]}
/>

With next/link, pinned to the top

<Navbar
  className="sticky top-0 z-40 border-b"
  brand="Workspace"
  links={[{ href: "/", label: "Home", current: true }, { href: "/blog", label: "Blog" }]}
  renderLink={(props) => <Link {...props} />}
/>