Menubar
Desktop command bar with nested menus, shortcuts, and separators.
Composition
The preferred shape for composing the installed primitive from its exported parts.
Parts
Exported compound parts from the installed source.
Menubar ├── MenubarMenu ├── MenubarTrigger ├── MenubarContent ├── MenubarGroup ├── MenubarItem ├── MenubarLabel ├── MenubarSeparator ├── MenubarShortcut ├── MenubarSub ├── MenubarSubTrigger └── MenubarSubContent
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/menubar.tsx. Install it on its own with the command above, or inspect the source below.
npx shadcn@latest add https://control-ui.dev/r/menubar.jsonKnobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-button-* · 17 knobsHow the cascade resolves--cui-button-radius<length-percentage>var(--radius-control)--cui-button-gap<length-percentage>0.375rem--cui-button-icon<length>1rem--cui-button-bg<color>transparent--cui-button-bg-image*none--cui-button-foreground<color>var(--muted-foreground)--cui-button-hover-bg<color>oklch(from var(--foreground) l c h / 0.06)--cui-button-hover-foreground<color>var(--foreground)--cui-button-press-bg<color>var(--cui-button-hover-bg)--cui-button-press-scale<number>0.98--cui-button-active-bg<color>oklch(from var(--foreground) l c h / 0.08)--cui-button-active-foreground<color>var(--foreground)--cui-button-active-hover-bg<color>var(--cui-button-active-bg)--cui-button-shadow*0 0 transparent--cui-button-hover-shadow*var(--cui-button-shadow)--cui-button-press-shadow*var(--cui-button-hover-shadow)--cui-button-active-shadow*var(--cui-button-shadow)--cui-popup-* · 16 knobsHow the cascade resolves--cui-popup-radius*var(--radius-popover)--cui-popup-background<color>var(--popover)--cui-popup-foreground<color>var(--popover-foreground)--cui-popup-border-color<color>var(--border)--cui-popup-border-width<length>1px--cui-popup-shadow*var(--shadow-pop)--cui-popup-backdrop-filter*blur(var(--backdrop-blur-popover))--cui-popup-backdrop-background<color>oklch(from var(--foreground) l c h / var(--overlay-opacity))--cui-popup-item-radius<length-percentage>var(--radius-popup-item)--cui-popup-item-foreground<color>var(--foreground)--cui-popup-item-highlight-background<color>oklch(from var(--foreground) l c h / 0.06)--cui-popup-item-highlight-foreground<color>var(--cui-popup-item-foreground)--cui-popup-item-highlight-muted-foreground<color>var(--muted-foreground)--cui-popup-item-disabled-opacity<number>0.4--cui-popup-separator-color<color>var(--border)--cui-popup-shortcut-foreground<color>var(--muted-foreground)Installed dependencies
Support files installed with this primitive — shared ones arrive once with your first Control UI component. Public dependencies stay linked to their own pages.
src/registry/sources/control-ui/surface-variants.tsSupportsrc/registry/sources/control-ui/control-variants.tsSupportRaw code
This primitive's source and the support files it installs with
"use client";
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
import type { ComponentProps, CSSProperties, MouseEvent, ReactNode } from "react";
import type { OpenChangeEventDetails } from "@/components/control-ui/control-props";
import { buttonGapClass, controlSize } from "@/components/control-ui/control-variants";
import type { ButtonKnobStyle } from "@/components/control-ui/knob-contracts/button-knobs";
import type { PopupKnobStyle } from "@/components/control-ui/knob-contracts/popup-knobs";
import { cn } from "@/components/control-ui/lib/cn";
import { skinEffects, skinId } from "@/components/control-ui/skin";
import { popupItemStructureClasses } from "@/components/control-ui/surface-variants";
export type MenubarProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle } & {
modal?: boolean;
loopFocus?: boolean;
disabled?: boolean;
orientation?: "horizontal" | "vertical";
};
export type MenubarMenuProps = {
children?: ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean, eventDetails: OpenChangeEventDetails) => void;
};
export type MenubarTriggerProps = Omit<ComponentProps<"button">, "style"> & { style?: CSSProperties & ButtonKnobStyle };
export type MenubarContentProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle };
export type MenubarGroupProps = ComponentProps<"div"> & { style?: CSSProperties & PopupKnobStyle };
export type MenubarItemProps = Omit<Omit<ComponentProps<"div">, "onClick">, "style"> & { style?: CSSProperties & PopupKnobStyle } & {
disabled?: boolean;
onClick?: (event: MouseEvent) => void;
};
export type MenubarSeparatorProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle };
export type MenubarLabelProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle } & {
inset?: boolean;
};
export type MenubarShortcutProps = Omit<ComponentProps<"span">, "style"> & { style?: CSSProperties & PopupKnobStyle };
export type MenubarSubProps = {
children?: ReactNode;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean, eventDetails: OpenChangeEventDetails) => void;
};
export type MenubarSubTriggerProps = Omit<Omit<ComponentProps<"div">, "onClick">, "style"> & { style?: CSSProperties & PopupKnobStyle } & {
disabled?: boolean;
inset?: boolean;
};
export type MenubarSubContentProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle };
// row of independent Menu.Root menus — each trigger drops its own full menu.
const popupClasses = "min-w-[11rem]";
const itemClasses = cn("group/mbi relative", popupItemStructureClasses, "px-[calc(var(--padding-x)*0.5)] py-1");
export function Menubar({ className, ...props }: MenubarProps) {
return (
<MenubarPrimitive
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="root"
data-control-family="popup"
data-popup-part="bar"
className={cn("inline-flex items-center gap-0.5 p-1", className)}
{...props}
/>
);
}
export function MenubarMenu(props: MenubarMenuProps) {
return <MenuPrimitive.Root {...props} />;
}
export function MenubarTrigger({ className, ...props }: MenubarTriggerProps) {
return (
<MenuPrimitive.Trigger
data-control-ui="menubar"
data-control-family="button"
data-slot="trigger"
data-control="true"
data-cursor="default"
data-size="sm"
data-variant="ghost"
data-tone="neutral"
data-shape="default"
className={cn("inline-flex select-none items-center", controlSize({ size: "sm" }), buttonGapClass, className)}
{...props}
/>
);
}
export function MenubarContent({ className, children, ...props }: MenubarContentProps) {
return (
<MenuPrimitive.Portal>
{/* portal escapes every token-scoped ancestor, so scope is re-asserted here */}
<MenuPrimitive.Positioner
data-skin={skinId()}
data-effects={skinEffects()}
side="bottom"
align="start"
sideOffset={6}
className="z-[80]"
>
<MenuPrimitive.Popup
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="content"
data-surface="floating"
data-control-family="popup"
data-popup-part="list-surface"
className={cn(popupClasses, className)}
{...props}
>
{children}
</MenuPrimitive.Popup>
</MenuPrimitive.Positioner>
</MenuPrimitive.Portal>
);
}
export function MenubarGroup({ className, ...props }: MenubarGroupProps) {
return (
<MenuPrimitive.Group
data-control-ui="menubar"
data-control-family="popup"
data-popup-kind="menubar"
data-slot="group"
className={className}
{...props}
/>
);
}
export function MenubarItem({ className, ...props }: MenubarItemProps) {
return (
<MenuPrimitive.Item
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="item"
data-control-family="popup"
data-popup-part="item"
className={cn(itemClasses, className)}
{...props}
/>
);
}
export function MenubarLabel({ className, inset = false, ...props }: MenubarLabelProps) {
return (
<MenuPrimitive.GroupLabel
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="label"
data-control-family="popup"
data-popup-part="label"
className={cn("px-[calc(var(--padding-x)*0.5)] py-1", inset && "ps-8", className)}
{...props}
/>
);
}
export function MenubarSeparator({ className, ...props }: MenubarSeparatorProps) {
return (
// bleeds rule through popup padding so it spans edge to edge while rows keep their inset
<MenuPrimitive.Separator
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="separator"
data-control-family="popup"
data-popup-part="separator"
className={cn("-mx-[var(--popover-padding)] my-1 h-px", className)}
{...props}
/>
);
}
export function MenubarShortcut({ className, ...props }: MenubarShortcutProps) {
return (
<span
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="shortcut"
data-control-family="popup"
data-popup-part="shortcut"
className={cn("ms-auto ps-6", className)}
{...props}
/>
);
}
export function MenubarSub(props: MenubarSubProps) {
return <MenuPrimitive.SubmenuRoot {...props} />;
}
export function MenubarSubTrigger({ className, inset = false, children, ...props }: MenubarSubTriggerProps) {
return (
<MenuPrimitive.SubmenuTrigger
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="sub-trigger"
data-control-family="popup"
data-popup-part="item"
className={cn(itemClasses, inset && "ps-8", className)}
{...props}
>
{children}
<span
data-control-ui="menubar"
data-popup-kind="menubar"
data-control-family="popup"
data-slot="sub-trigger-indicator"
aria-hidden="true"
className="ms-auto ps-4"
>
›
</span>
</MenuPrimitive.SubmenuTrigger>
);
}
export function MenubarSubContent({ className, children, ...props }: MenubarSubContentProps) {
return (
<MenuPrimitive.Portal>
<MenuPrimitive.Positioner
data-skin={skinId()}
data-effects={skinEffects()}
side="right"
align="start"
sideOffset={-4}
alignOffset={-5}
className="z-[80]"
>
<MenuPrimitive.Popup
data-control-ui="menubar"
data-popup-kind="menubar"
data-slot="sub-content"
data-surface="floating"
data-control-family="popup"
data-popup-part="list-surface"
className={cn(popupClasses, className)}
{...props}
>
{children}
</MenuPrimitive.Popup>
</MenuPrimitive.Positioner>
</MenuPrimitive.Portal>
);
}