KQ-1284 · BRIDGE/MENU
Bridge Menu
A navigation bar with one mega-menu panel for every trigger: moving from one trigger to the next, the same panel slides across and resizes to the new content on springs.glide while that content crossfades in from the side the pointer came from, and a notch on the panel's edge and a pill behind the trigger move on springs.snap so they arrive first. A safe triangle from the last point on the open trigger to the panel's top edge (the bridge) holds the menu open while the pointer heads diagonally across other triggers, until it reaches the panel, leaves the triangle or rests, and bridge="show" draws it. The triggers are disclosure buttons in one tab stop: Left and Right move along the bar with the open panel following, Enter or Space opens, Down moves into the panel and Escape comes back to the trigger.
menu closed · hover a trigger
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/bridge-menu
Props
- items
- BridgeMenuItem[]
- default defaultBridgeMenuItems
- The bar's items in order. An item with sections (and an optional feature card) or custom content has a panel; an item with only an href is a plain link in the bar.
- value / defaultValue
- string | null
- default null
- The open menu's id, or null when closed, controlled or initial.
- onValueChange
- (id: string | null) => void
- default —
- Fires from the hover, press or key that opened, switched or closed a menu.
- onSelect
- (link: BridgeMenuLink, itemId: string, event: React.MouseEvent) => void
- default —
- A link in a panel (or a plain link in the bar) was chosen; the menu closes after it. Call event.preventDefault() to stay on the page.
- label
- string
- default "Main"
- The navigation landmark's accessible name.
- travel
- number
- default 16
- How far the content slides in the direction of travel as it crossfades, in px. 0 is a plain crossfade.
- bridge
- "off" | "on" | "show"
- default "on"
- The safe triangle that keeps a menu open while the pointer crosses other triggers on its way to the panel; show draws it as a translucent wedge.
- delay
- number
- default 120
- How long the pointer rests on a trigger before a closed menu opens, and inside the bridge before it gives way, in ms.
- closeDelay
- number
- default 260
- How long a menu stays open after the pointer leaves the bar and the panel, in ms.
- align
- "center" | "start"
- default "center"
- The panel centred under its trigger, or starting at it; either way it is held inside the bar.
- offset
- number
- default 6
- The gap between the bar and the panel, in px.
- notch
- boolean
- default true
- Draw a notch on the panel's top edge pointing at the open trigger.
- scrim
- boolean
- default true
- Dim the page under the bar while a menu is open.
- size
- "sm" | "md" | "lg"
- default "md"
- Bar height 32, 40 or 48px, with trigger text to match.
- accent
- string
- default "var(--accent-bright)"
- Any CSS colour for the open trigger's rule, the bridge and link icons on hover.
- start
- React.ReactNode
- default —
- Content before the triggers, such as a brand mark.
- end
- React.ReactNode
- default —
- Content at the bar's end, such as sign-in actions.
- children
- React.ReactNode
- default —
- The page under the bar. With it, the panel floats over the page, its height is capped to the room there, and it never leaves the component's box.
- sound
- boolean
- default false
- A swish when a press or a key opens or closes a menu, and a tick when a key moves the panel between triggers or within it. Hover-driven changes are silent, so a pointer passing over the bar never starts audio.
- disabled
- boolean
- default false
- Shows the bar closed and ignores hovers, presses and keys.
- className
- string
- default —
- Classes for the root element.
| Prop | Type | Default | Description |
|---|---|---|---|
| items | BridgeMenuItem[] | defaultBridgeMenuItems | The bar's items in order. An item with sections (and an optional feature card) or custom content has a panel; an item with only an href is a plain link in the bar. |
| value / defaultValue | string | null | null | The open menu's id, or null when closed, controlled or initial. |
| onValueChange | (id: string | null) => void | — | Fires from the hover, press or key that opened, switched or closed a menu. |
| onSelect | (link: BridgeMenuLink, itemId: string, event: React.MouseEvent) => void | — | A link in a panel (or a plain link in the bar) was chosen; the menu closes after it. Call event.preventDefault() to stay on the page. |
| label | string | "Main" | The navigation landmark's accessible name. |
| travel | number | 16 | How far the content slides in the direction of travel as it crossfades, in px. 0 is a plain crossfade. |
| bridge | "off" | "on" | "show" | "on" | The safe triangle that keeps a menu open while the pointer crosses other triggers on its way to the panel; show draws it as a translucent wedge. |
| delay | number | 120 | How long the pointer rests on a trigger before a closed menu opens, and inside the bridge before it gives way, in ms. |
| closeDelay | number | 260 | How long a menu stays open after the pointer leaves the bar and the panel, in ms. |
| align | "center" | "start" | "center" | The panel centred under its trigger, or starting at it; either way it is held inside the bar. |
| offset | number | 6 | The gap between the bar and the panel, in px. |
| notch | boolean | true | Draw a notch on the panel's top edge pointing at the open trigger. |
| scrim | boolean | true | Dim the page under the bar while a menu is open. |
| size | "sm" | "md" | "lg" | "md" | Bar height 32, 40 or 48px, with trigger text to match. |
| accent | string | "var(--accent-bright)" | Any CSS colour for the open trigger's rule, the bridge and link icons on hover. |
| start | React.ReactNode | — | Content before the triggers, such as a brand mark. |
| end | React.ReactNode | — | Content at the bar's end, such as sign-in actions. |
| children | React.ReactNode | — | The page under the bar. With it, the panel floats over the page, its height is capped to the room there, and it never leaves the component's box. |
| sound | boolean | false | A swish when a press or a key opens or closes a menu, and a tick when a key moves the panel between triggers or within it. Hover-driven changes are silent, so a pointer passing over the bar never starts audio. |
| disabled | boolean | false | Shows the bar closed and ignores hovers, presses and keys. |
| className | string | — | Classes for the root element. |