implement
Menubar

Menubar

A horizontal bar of menus, like an application menu.

import { signal } from "@implementjs/core";
import {
	Menubar,
	MenubarCheckboxItem,
	MenubarContent,
	MenubarItem,
	MenubarMenu,
	MenubarRadioGroup,
	MenubarRadioItem,
	MenubarSeparator,
	MenubarTrigger,
} from "@/lib/components/ui/menubar";

export default function MenubarDemo() {
	const wordWrap = signal(true);
	const theme = signal<string | null>("system");

	return Menubar(
		MenubarMenu(
			{ value: "file" },
			MenubarTrigger("File"),
			MenubarContent(
				{ class: "w-48" },
				MenubarItem({ onSelect: () => console.log("new") }, "New file"),
				MenubarItem({ onSelect: () => console.log("open") }, "Open…"),
				MenubarSeparator(),
				MenubarItem({ onSelect: () => console.log("save") }, "Save"),
				MenubarItem({ disabled: true }, "Save all"),
			),
		),
		MenubarMenu(
			{ value: "edit" },
			MenubarTrigger("Edit"),
			MenubarContent(
				{ class: "w-48" },
				MenubarItem({ onSelect: () => console.log("undo") }, "Undo"),
				MenubarItem({ onSelect: () => console.log("redo") }, "Redo"),
				MenubarSeparator(),
				MenubarItem({ onSelect: () => console.log("find") }, "Find…"),
			),
		),
		MenubarMenu(
			{ value: "view" },
			MenubarTrigger("View"),
			MenubarContent(
				{ class: "w-48" },
				MenubarCheckboxItem({ checked: wordWrap, closeOnSelect: false }, "Word wrap"),
				MenubarSeparator(),
				MenubarRadioGroup(
					{ value: theme },
					MenubarRadioItem({ value: "light" }, "Light"),
					MenubarRadioItem({ value: "dark" }, "Dark"),
					MenubarRadioItem({ value: "system" }, "System"),
				),
			),
		),
	);
}

A menubar is a row of menus — File, Edit, View. Menubar is the bar, each MenubarMenu is one menu identified by a value, with a MenubarTrigger in the bar and a MenubarContent panel. It shares its content, items, and keyboard model with Dropdown Menu and Context Menu.

import {
	Menubar,
	MenubarContent,
	MenubarItem,
	MenubarMenu,
	MenubarTrigger,
} from "@implementjs/primitives";

Menubar(
	MenubarMenu(
		{ value: "file" },
		MenubarTrigger("File"),
		MenubarContent(MenubarItem({ onSelect: () => save() }, "Save")),
	),
	MenubarMenu(
		{ value: "edit" },
		MenubarTrigger("Edit"),
		MenubarContent(MenubarItem({ onSelect: () => undo() }, "Undo")),
	),
);

One open menu

The bar owns which menu is open — value holds the open menu's value, or null. Opening one closes another, and while any menu is open, hovering a different trigger switches to it, the way native application menus feel. Pass a signal as value to control it from outside. While a menu is open, the page behind cannot scroll; pass preventScroll: false on that MenubarMenu to leave it scrollable.

Keyboard

The bar is one Tab stop. Left and Right arrows move between triggers (wrapping unless loop: false); Enter, Space, or ArrowDown opens the focused menu with its first item focused. Inside an open menu the shared model applies — arrows move, typing jumps, Enter activates, Escape closes — and Left/Right close the open menu and open its neighbor.

Items and structure

Panels hold the shared menu set: MenubarItem with onSelect and closeOnSelect, MenubarCheckboxItem, MenubarRadioGroup and MenubarRadioItem, MenubarGroup with MenubarGroupHeading, MenubarSeparator, and nested submenus via MenubarSub, MenubarSubTrigger, and MenubarSubContent — inside a submenu, Left and Right move within it rather than switching menubar menus.

Accessibility and styling

The bar is role="menubar" and each trigger role="menuitem" with aria-haspopup and aria-expanded; panels are role="menu". The primitive does not hide closed panels — style MenubarContent against data-state. Triggers expose data-state and data-highlighted; items expose data-highlighted and data-disabled. Every part sets a data-menubar-* attribute.

API Reference

The bar. Sets role="menubar", owns which menu is open, and moves focus between the triggers. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
valueSignal<string | null> | string | nullnullThe open menu's value, or null while all are closed. Pass a signal to control it from outside.
loopbooleantrueWhether arrow keys wrap from the last trigger back to the first.
Data attributeValue
[data-menubar-root]Present
[data-orientation]"horizontal"

One menu of the bar. Wraps a trigger and its content.

PropTypeDefaultDescription
value*stringIdentifies the menu. Must be unique within the menubar.
preventScrollbooleantrueWhen true, the page behind cannot scroll while this menu is open. The panel can still scroll if you give it overflow.

The menu's button in the bar. Sets role="menuitem"; hovering it switches to this menu while another is open. Renders a Button; extra props are forwarded onto it.

PropTypeDefaultDescription
disabledSignal<boolean> | booleanfalsePrevents opening the menu. Sets disabled and data-disabled.
Data attributeValue
[data-menubar-trigger]Present
[data-state]"open" | "closed"
[data-highlighted]Present while focused
[data-disabled]Present when disabled

The floating panel of items. Sets role="menu". Style it against data-state and data-side; the primitive does not hide it for you. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
side"top" | "bottom" | "left" | "right""bottom"Preferred side of the anchor to place the panel.
align"start" | "center" | "end""start"How the panel aligns along the chosen side.
offsetnumber0Distance in pixels between the anchor and the panel.
loopbooleantrueWhether arrow keys wrap from the last item back to the first.
Data attributeValue
[data-menubar-content]Present
[data-state]"open" | "closed"
[data-side]"top" | "bottom" | "left" | "right"
[data-align]"start" | "center" | "end"

One action. Sets role="menuitem"; focus follows the pointer. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
onSelect() => voidRuns when the item is activated with a click, Enter, or Space.
closeOnSelectbooleantrueWhether selecting the item closes the menu.
disabledSignal<boolean> | booleanfalsePrevents selecting the item; the keyboard skips it.
Data attributeValue
[data-menubar-item]Present
[data-highlighted]Present while focused
[data-disabled]Present when disabled

An item holding a checked state. Sets role="menuitemcheckbox" and aria-checked. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
checkedSignal<boolean> | booleanfalseThe checked state; selecting toggles it. Pass a signal to control it from outside.
closeOnSelectbooleantrueWhether selecting the item closes the menu.
disabledSignal<boolean> | booleanfalsePrevents selecting the item; the keyboard skips it.
Data attributeValue
[data-menubar-checkbox-item]Present
[data-state]"checked" | "unchecked"
[data-highlighted]Present while focused
[data-disabled]Present when disabled

Wraps radio items and owns which one is checked. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
valueSignal<string | null> | string | nullnullThe checked item. Pass a signal to control it from outside.
Data attributeValue
[data-menubar-radio-group]Present

One choice in a radio group. Sets role="menuitemradio" and aria-checked. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
value*stringIdentifies the item. Must be unique within the radio group.
closeOnSelectbooleantrueWhether selecting the item closes the menu.
disabledSignal<boolean> | booleanfalsePrevents selecting the item; the keyboard skips it.
Data attributeValue
[data-menubar-radio-item]Present
[data-state]"checked" | "unchecked"
[data-highlighted]Present while focused
[data-disabled]Present when disabled

A nested menu. Wraps a sub trigger and its sub content inside a parent content.

PropTypeDefaultDescription
openSignal<boolean> | booleanfalseThe open state. Pass a signal to control it from outside; a boolean seeds uncontrolled state.

The item that opens its submenu — on hover after openDelay, ArrowRight, Enter, Space, or click. Sets aria-haspopup and aria-expanded. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
openDelaynumber100How long the pointer must rest on the trigger before the submenu opens, in milliseconds.
disabledSignal<boolean> | booleanfalsePrevents opening the submenu; the keyboard skips the item.
Data attributeValue
[data-menubar-sub-trigger]Present
[data-menubar-item]Present
[data-state]"open" | "closed"
[data-highlighted]Present while focused
[data-disabled]Present when disabled

The submenu's panel, positioned against its trigger. ArrowLeft closes it and returns focus; the keyboard model inside matches the parent content. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
side"top" | "bottom" | "left" | "right""right"Preferred side of the sub trigger to place the panel.
align"start" | "center" | "end""start"How the panel aligns along the chosen side.
offsetnumber0Distance in pixels between the sub trigger and the panel.
loopbooleantrueWhether arrow keys wrap from the last item back to the first.
Data attributeValue
[data-menubar-sub-content]Present
[data-state]"open" | "closed"
[data-side]"top" | "bottom" | "left" | "right"
[data-align]"start" | "center" | "end"

Wraps related items in role="group", labeled by the heading placed inside it. Renders a Div; extra props are forwarded onto it.

Data attributeValue
[data-menubar-group]Present

Names the group it sits in; the group points aria-labelledby at it. Renders a Div; extra props are forwarded onto it.

Data attributeValue
[data-menubar-group-heading]Present

A role="separator" line between sections. Renders a Div; extra props are forwarded onto it.

Data attributeValue
[data-menubar-separator]Present

The core Portal helper, for rendering the panel into another DOM parent to escape overflow and stacking.