Menu

Build a horizontal hover navigation bar with animated dropdown panels.

Menu is a client component that renders a horizontal navigation bar with animated dropdown panels, for site headers. Hovering or clicking a MenuItem with children selects it, and that item's children render as the dropdown panel, typically a MenuContent. MenuItem uses next/link for items with a path.

Products
Company

Import#

typescript
import { Menu, MenuItem, MenuContent } from "@reactberry/system/blocks";

Usage#

typescript
import { Menu, MenuItem, MenuContent } from "@reactberry/system/blocks";

const productLinks = [
  { documentId: "analytics", title: "Analytics", path: "/analytics", additionalFields: { description: "Track usage" } },
  { documentId: "billing", title: "Billing", path: "/billing" },
];

export default function SiteNav() {
  return (
    <Menu>
      <MenuItem id={1} title="Products">
        <MenuContent items={productLinks} />
      </MenuItem>
      <MenuItem id={2} title="Pricing" path="/pricing" />
      <MenuItem id={3} title="Docs" path="/docs" />
    </Menu>
  );
}

Examples#

Categories#

Give an item nested items to render it as a category heading with its links below. At the top level, each item gets its own column.

Solutions
Resources

Custom panel#

Pass children instead of items to render any content in the panel.

What's new

API#

PropTypeDefaultDescription
children*
React.ReactNode
—

MenuItem elements. Each direct child's id is matched against the selected id to find the panel to render.

The selection is cleared when the pointer leaves the menu. No other props are accepted.

PropTypeDefaultDescription
id*
string | number
—

Unique id. Matched against the selected id, and used for the title element id shift-tab-{id}.

title*
string
—

Label text.

path
string
—

Renders the title as a Next.js Link to this path.

children
React.ReactNode
—

Dropdown panel shown while this item is selected, usually a MenuContent. Items without children clear the selection on hover.

disabled
boolean
—

Applies the Box disabled style (50% opacity, pointer-events: none) to the item.

A pill highlight with a shared layoutId follows the hovered or selected item. It can be styled through theme.header.public.menu.item.highlight.

PropTypeDefaultDescription
items
MenuItem[]
—

Link data. Items with nested items render as a category heading; others render as links. Takes precedence over children.

children
React.ReactNode
—

Custom panel content, rendered when items is not set.

level
number
0

Nesting level. At level 0 each item gets its own grid column; nested levels use a single column.

Each entry in items has this shape:

PropTypeDefaultDescription
documentId*
string
—

Unique key.

title*
string
—

Link text, or the category heading when items is set.

path
string
—

href of the link. Rendered as a plain a element.

items
MenuItem[]
—

Nested items. Turns the entry into a category heading.

additionalFields
{ description?: string; divider?: boolean }
—

description is shown under the title. divider is not used.

menuAttached
boolean
—

Declared in the type but not used.

order
number
—

Declared in the type but not used.

Accessibility#

  • Navigation is pointer-driven. There is no keyboard handling and no ARIA menu semantics, so panels can't be opened from the keyboard.
  • Titles with a path are links and can be focused with Tab. Titles without a path are plain span elements.
  • Links in MenuContent are native a elements.

Notes#

  • MenuItem and MenuContent must be rendered inside Menu; they read the shared menu context and throw otherwise.
  • The panel is absolutely positioned below the bar (width="40rem"), with a nub that tracks the centre of the selected title. The nub position is not updated when the selected id is 0 or another falsy value.
  • Slide direction between panels compares ids with >, so numeric ids in display order give the expected direction.
  • Hovering a title that has a path clears the selection, so combining path and children on one item does not keep its panel open.
  • disabled sets pointer-events: none on the item, so it can't be hovered or clicked, but a path link can still be reached with the keyboard.
  • MenuContent sets id="overlay-content" on its panel; only one panel is rendered at a time.