Skip to content

Top Nav

Navigation across the top of an application or marketing site: your brand, links (some opening a short list), and a slot for buttons or the account menu. In a narrow space it becomes a menu button that opens the same links in a sheet. It comes plain, with a line, or as a floating pill.

Blockv0.1.0experimentalWCAG 2.2 AAView spec

A ready-made piece, built from the library's own parts.

A block puts several components together into something you would otherwise assemble by hand. It never fetches data: you give it the data, or answer its callbacks. It is copied into your project like any component, with the parts it uses, so you can change anything.

Basic

Full screen (opens in a new tab)
Loomworks

With dropdowns

Full screen (opens in a new tab)
Loomworks

With your router

Full screen (opens in a new tab)

You are at /customers

Mobile menu

Full screen (opens in a new tab)
Loomworks

Permissions

Full screen (opens in a new tab)
Loomworks

ClassNames

Full screen (opens in a new tab)
Loomworks

Installation

npx rdloom add top-nav

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/top-nav.json

Works, but without upgrade tracking.

Usage

import { Button, TopNav, type NavItem } from "@rdloom/react";

<div className="mx-auto w-full max-w-4xl overflow-hidden rounded-xl border border-[var(--rd-color-border-default)]">
  <TopNav
    brand="Loomworks"
    items={items}
    currentId={current}
    onNavigate={(item) => setCurrent(item.id)}
    variant="bordered"
    actions={
      <>
        <Button variant="ghost" size="sm">
          Sign in
        </Button>
        <Button size="sm">Start free</Button>
      </>
    }
  />
  <div className="h-24" />
</div>

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
brand

Your logo or product name at the start. Make it a link to the home page yourself.

nodenone
itemsrequired

The links as data: { id, label, href?, icon?, badge?, children?, permission? }. An item with children opens a short list instead of going anywhere. An item may carry a permission ("allow", "disabled", "hidden" or { state, reason }): hidden items, and parents left with nothing, are not drawn; disabled items stay as focusable, non-navigating entries (aria-disabled) with the reason in a tooltip.

NavItem[]none
currentId

The id of the page you are on. It is marked as the current page (aria-current); the parent of a current sub-item is marked as the current section.

stringnone
label

Accessible name of the navigation.

string"Main navigation"
onNavigate

Called with the item when one is chosen. Items without an href use this to do their work, and entries in a dropdown use it to go to your router.

(item: NavItem) => voidnone
renderLink

Draw the top-level links and the links in the narrow menu as your router's link. Return the link with the given className and children; call onClick when chosen. Without it, items with an href are plain links.

(props: { item: NavItem; className: string; children: ReactNode; isCurrent: boolean; onClick: () => void }) => ReactNodenone
actions

Buttons or the account menu at the end. They stay visible when the links fold into the menu.

nodenone
variant

plain: no line. bordered: a line under the bar. floating: a rounded pill with a shadow and a margin around it.

"plain" | "bordered" | "floating""plain"
sticky

Keep the bar at the top while the page scrolls.

booleanfalse
menuLabel

Accessible name of the button that opens the links in a narrow space, and the title of its sheet.

string"Menu"
classNames

Class names for the parts, by slot name, added after the built-in ones. Slots: root, brand, nav, list, item, dropdown, actions, menu-button, menu-sheet.

Partial<Record<"root" | "brand" | "nav" | "list" | "item" | "dropdown" | "actions" | "menu-button" | "menu-sheet", string>>none

Accessibility

Role navigation, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves through the links and dropdown buttons, then the actions
  • Enter or Space: follows a link; on a dropdown button, opens its list
  • In a dropdown: arrow keys move, Enter chooses, Escape closes and returns focus to the button
  • In a narrow space: Tab to the menu button, Enter opens the sheet, Escape closes it and returns focus to the button

Screen readers announce

  • "Main navigation, navigation", then each link; the current one reads "Pricing, current page"
  • "Products, menu button, collapsed"
  • "Menu, button" in a narrow space, then the sheet as a dialog titled "Menu"

What your code must do

  • The links are inside one named navigation landmark; only one of the wide bar and the narrow menu is in the page at a time
  • The current page is marked with aria-current="page" and also drawn with a bar under it and a heavier label, never by color alone
  • A parent of the current page is drawn the same way and read as "current section" but is not marked as the page itself
  • A dropdown button announces that it opens a menu and whether it is expanded; the list is a real menu of links
  • The menu button has a name ("Menu") and states whether the sheet is open; choosing a link closes the sheet
  • A disabled link stays focusable (aria-disabled), goes nowhere and says why
  • The wide and narrow layouts follow the space the bar is in, not only the screen

Block contract

Data
The links as plain data: items with an id, label, optional href, icon, badge and children.
Data states
None: it shows what it is given
Permissions
per navigation item
Events
onNavigate
You can replace
variant (plain, bordered, floating); renderLink (your router's link); brand and actions slots; classNames for each part

Guidelines

Use it when

  • A marketing site, documentation or an app with only a few sections
  • A top bar of links with a sign-in or account button at the end

Avoid it when

  • An admin tool with many sections and sub-pages: use Sidebar or DashboardShell
  • Switching between a few views of one page: use Tabs

Don't

  • More than about 6 top-level links: group the rest in a dropdown
  • A dropdown inside a dropdown: the list is one level
  • Two navigation landmarks with the same name on the page: give each its own label
  • UI permission is not security: hiding or disabling a link only changes what people see, so the server must check access again for every request

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-default
  • --rd-color-surface-raised
  • --rd-color-surface-subtle
  • --rd-color-surface-selected
  • --rd-color-border-default
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-action-primary
  • --rd-color-focus-ring
  • --rd-radius-control
  • --rd-elevation-raised
  • --rd-size-control-sm