An accessible React menubar component that provides keyboard navigation, submenus, and customizable styling while maintaining semantic HTML structure and screen reader compatibility.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Components for creating nested submenus and additional visual elements like arrows, enabling multi-level menu hierarchies.
Container for submenu functionality with controllable open state, managing the visibility and behavior of nested menu content.
/**
* Container for submenu with controllable open state
* @param props - Submenu container props
* @returns JSX element representing the submenu container
*/
function MenubarSub(props: MenubarSubProps): React.ReactElement;
interface MenubarSubProps {
/** Child components (typically SubTrigger and SubContent) */
children?: React.ReactNode;
/** Controlled open state */
open?: boolean;
/** Default open state for uncontrolled usage (default: false) */
defaultOpen?: boolean;
/** Callback fired when open state changes */
onOpenChange?: (open: boolean) => void;
}Usage Examples:
'use client';
import * as Menubar from "@radix-ui/react-menubar";
// Basic submenu
<Menubar.Sub>
<Menubar.SubTrigger>
Export
<ChevronRightIcon />
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>Export as PDF</Menubar.Item>
<Menubar.Item>Export as Image</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
// Controlled submenu
function ControlledSubmenu() {
const [isOpen, setIsOpen] = React.useState(false);
return (
<Menubar.Sub open={isOpen} onOpenChange={setIsOpen}>
<Menubar.SubTrigger>
Tools {isOpen ? "▼" : "▶"}
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>Format Document</Menubar.Item>
<Menubar.Item>Check Spelling</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
);
}Trigger that opens a submenu when hovered or activated, typically displaying an arrow indicator.
/**
* Trigger that opens a submenu on hover or activation
* @param props - Sub-trigger props
* @returns JSX element representing the submenu trigger
*/
function MenubarSubTrigger(props: MenubarSubTriggerProps): React.ReactElement;
interface MenubarSubTriggerProps extends React.ComponentPropsWithoutRef<'div'> {
/** Whether the trigger is disabled */
disabled?: boolean;
/** Text value for accessibility */
textValue?: string;
}Usage Examples:
// Basic sub-trigger with arrow
<Menubar.SubTrigger>
Recent Files
<ChevronRightIcon className="sub-arrow" />
</Menubar.SubTrigger>
// Sub-trigger with custom content
<Menubar.SubTrigger>
<FileIcon />
<span>Export Options</span>
<ArrowIcon />
</Menubar.SubTrigger>
// Disabled sub-trigger
<Menubar.SubTrigger disabled>
Advanced Tools
<ChevronRightIcon />
</Menubar.SubTrigger>Content container for submenu items, positioned relative to the sub-trigger.
/**
* Content container for submenu items
* @param props - Sub-content props
* @returns JSX element representing the submenu content
*/
function MenubarSubContent(props: MenubarSubContentProps): React.ReactElement;
interface MenubarSubContentProps extends React.ComponentPropsWithoutRef<'div'> {
/** Alignment relative to trigger */
align?: 'start' | 'center' | 'end';
/** Side preference for positioning */
side?: 'top' | 'right' | 'bottom' | 'left';
/** Distance from trigger in pixels */
sideOffset?: number;
/** Alignment offset in pixels */
alignOffset?: number;
/** Whether content should avoid collisions with the boundary */
avoidCollisions?: boolean;
/** Element or area to constrain positioning within */
collisionBoundary?: Element | null | Array<Element | null>;
/** Padding from collision boundary in pixels */
collisionPadding?: number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>;
/** Whether content should stick to trigger when boundary is reached */
sticky?: 'partial' | 'always';
/** Callback fired when content loses focus */
onCloseAutoFocus?: (event: Event) => void;
/** Callback fired when escape key is pressed */
onEscapeKeyDown?: (event: KeyboardEvent) => void;
/** Callback fired when pointer moves outside */
onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;
/** Callback fired when focus moves outside the content */
onFocusOutside?: (event: FocusOutsideEvent) => void;
/** Callback fired when interaction occurs outside the content */
onInteractOutside?: (event: InteractOutsideEvent) => void;
}Usage Examples:
// Basic sub-content
<Menubar.SubContent>
<Menubar.Item>Export as PDF</Menubar.Item>
<Menubar.Item>Export as PNG</Menubar.Item>
<Menubar.Item>Export as SVG</Menubar.Item>
</Menubar.SubContent>
// Sub-content with custom positioning
<Menubar.SubContent
side="right"
align="start"
sideOffset={2}
>
<Menubar.Item>Option 1</Menubar.Item>
<Menubar.Item>Option 2</Menubar.Item>
</Menubar.SubContent>
// Sub-content with nested submenus
<Menubar.SubContent>
<Menubar.Item>Quick Export</Menubar.Item>
<Menubar.Sub>
<Menubar.SubTrigger>
Advanced Export
<ChevronRightIcon />
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>With Metadata</Menubar.Item>
<Menubar.Item>Compressed</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
</Menubar.SubContent>Key Implementation Details:
data-radix-menubar-subtrigger="" attributedata-radix-menubar-content="" attribute--radix-menubar-content-transform-origin: Transform origin for animations--radix-menubar-content-available-width: Available width for content--radix-menubar-content-available-height: Available height for content--radix-menubar-trigger-width: Width of the triggering element--radix-menubar-trigger-height: Height of the triggering elementOptional arrow pointing from trigger to content, providing visual connection between trigger and dropdown.
/**
* Optional arrow pointing from trigger to content
* @param props - Arrow props
* @returns JSX element representing the arrow
*/
function MenubarArrow(props: MenubarArrowProps): React.ReactElement;
interface MenubarArrowProps extends React.ComponentPropsWithoutRef<'svg'> {
/** Width of the arrow in pixels (default: 10) */
width?: number;
/** Height of the arrow in pixels (default: 5) */
height?: number;
}Usage Examples:
// Basic arrow
<Menubar.Portal>
<Menubar.Content>
<Menubar.Arrow />
<Menubar.Item>Menu Item</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
// Custom sized arrow
<Menubar.Portal>
<Menubar.Content>
<Menubar.Arrow width={12} height={6} />
<Menubar.Item>Menu Item</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
// Styled arrow
<Menubar.Portal>
<Menubar.Content>
<Menubar.Arrow className="custom-arrow" />
<Menubar.Item>Menu Item</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>function MultiLevelMenu() {
return (
<Menubar.Content>
<Menubar.Item>New File</Menubar.Item>
<Menubar.Item>Open</Menubar.Item>
<Menubar.Sub>
<Menubar.SubTrigger>
Recent Files
<ChevronRightIcon />
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>document1.txt</Menubar.Item>
<Menubar.Item>project.json</Menubar.Item>
<Menubar.Sub>
<Menubar.SubTrigger>
More Files
<ChevronRightIcon />
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>old-project.js</Menubar.Item>
<Menubar.Item>backup.sql</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>
Export
<ChevronRightIcon />
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>Export as PDF</Menubar.Item>
<Menubar.Item>Export as HTML</Menubar.Item>
<Menubar.Sub>
<Menubar.SubTrigger>
Export as Image
<ChevronRightIcon />
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>PNG</Menubar.Item>
<Menubar.Item>JPEG</Menubar.Item>
<Menubar.Item>SVG</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
</Menubar.Content>
);
}function DynamicSubmenu() {
const [recentFiles, setRecentFiles] = React.useState([
"document1.txt",
"project.json",
"notes.md"
]);
return (
<Menubar.Sub>
<Menubar.SubTrigger>
Recent Files ({recentFiles.length})
<ChevronRightIcon />
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
{recentFiles.length > 0 ? (
recentFiles.map((file, index) => (
<Menubar.Item
key={file}
onSelect={() => openFile(file)}
>
{file}
</Menubar.Item>
))
) : (
<Menubar.Label>No recent files</Menubar.Label>
)}
{recentFiles.length > 0 && (
<>
<Menubar.Separator />
<Menubar.Item onSelect={() => setRecentFiles([])}>
Clear Recent Files
</Menubar.Item>
</>
)}
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
);
}// Element reference types
type MenubarSubTriggerElement = HTMLDivElement;
type MenubarSubContentElement = HTMLDivElement;
type MenubarArrowElement = SVGSVGElement;
// Event types
interface PointerDownOutsideEvent {
target: HTMLElement;
preventDefault(): void;
}
interface FocusOutsideEvent {
target: HTMLElement;
preventDefault(): void;
}
interface InteractOutsideEvent {
target: HTMLElement;
preventDefault(): void;
}Install with Tessl CLI
npx tessl i tessl/npm-radix-ui--react-menubar