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
Utility functions for advanced usage patterns, context management, and creating isolated menubar instances.
Creates a scoped context for menubar components to avoid conflicts when nesting menubars or using multiple menubar instances.
/**
* Creates a scoped context for menubar components
* @returns Scope hook function for context isolation
*/
function createMenubarScope(): ScopeHook;
type ScopeHook = () => Scope;
interface Scope {
__scopeMenubar?: string;
}Usage Examples:
'use client';
import * as Menubar from "@radix-ui/react-menubar";
import { createMenubarScope } from "@radix-ui/react-menubar";
// Creating isolated menubar scopes
function MultipleMenubars() {
const scope1 = createMenubarScope();
const scope2 = createMenubarScope();
return (
<div>
<Menubar.Root {...scope1()}>
<Menubar.Menu {...scope1()}>
<Menubar.Trigger {...scope1()}>File</Menubar.Trigger>
<Menubar.Portal {...scope1()}>
<Menubar.Content {...scope1()}>
<Menubar.Item {...scope1()}>New</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
<Menubar.Root {...scope2()}>
<Menubar.Menu {...scope2()}>
<Menubar.Trigger {...scope2()}>Edit</Menubar.Trigger>
<Menubar.Portal {...scope2()}>
<Menubar.Content {...scope2()}>
<Menubar.Item {...scope2()}>Cut</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
</div>
);
}
// Nested menubar components with scoping
function NestedMenubarExample() {
const outerScope = createMenubarScope();
const innerScope = createMenubarScope();
return (
<div className="app-container">
{/* Main application menubar */}
<Menubar.Root {...outerScope()}>
<Menubar.Menu {...outerScope()}>
<Menubar.Trigger {...outerScope()}>File</Menubar.Trigger>
<Menubar.Portal {...outerScope()}>
<Menubar.Content {...outerScope()}>
<Menubar.Item {...outerScope()}>New</Menubar.Item>
<Menubar.Item {...outerScope()}>Open</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
{/* Secondary context-specific menubar */}
<div className="editor-panel">
<Menubar.Root {...innerScope()}>
<Menubar.Menu {...innerScope()}>
<Menubar.Trigger {...innerScope()}>Tools</Menubar.Trigger>
<Menubar.Portal {...innerScope()}>
<Menubar.Content {...innerScope()}>
<Menubar.Item {...innerScope()}>Format</Menubar.Item>
<Menubar.Item {...innerScope()}>Validate</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
</div>
</div>
);
}'use client';
import * as React from 'react';
import * as Menubar from "@radix-ui/react-menubar";
// Custom hook for managing menubar state
function useMenubarState(defaultValue?: string) {
const [activeMenu, setActiveMenu] = React.useState(defaultValue ?? "");
const [menuHistory, setMenuHistory] = React.useState<string[]>([]);
const openMenu = React.useCallback((value: string) => {
setActiveMenu(value);
setMenuHistory(prev => [...prev.filter(v => v !== value), value]);
}, []);
const closeMenu = React.useCallback(() => {
setActiveMenu("");
}, []);
const toggleMenu = React.useCallback((value: string) => {
setActiveMenu(prev => prev === value ? "" : value);
}, []);
return {
activeMenu,
menuHistory,
openMenu,
closeMenu,
toggleMenu,
// For controlled menubar
value: activeMenu,
onValueChange: setActiveMenu
};
}
// Usage of custom hook
function StatefulMenubar() {
const menuState = useMenubarState();
return (
<Menubar.Root
value={menuState.value}
onValueChange={menuState.onValueChange}
>
<Menubar.Menu value="file">
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item>New</Menubar.Item>
<Menubar.Item>Open</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu value="edit">
<Menubar.Trigger>Edit</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item>Cut</Menubar.Item>
<Menubar.Item>Copy</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
}'use client';
import * as React from 'react';
import * as Menubar from "@radix-ui/react-menubar";
import { createMenubarScope } from "@radix-ui/react-menubar";
// Factory function for creating configured menubar components
function createMenubar(options: MenubarOptions = {}) {
const scope = createMenubarScope();
const {
defaultLoop = true,
defaultDirection = 'ltr',
className = '',
...defaultProps
} = options;
const ConfiguredRoot = React.forwardRef<
React.ComponentRef<typeof Menubar.Root>,
Omit<React.ComponentProps<typeof Menubar.Root>, 'loop' | 'dir'>
>((props, ref) => (
<Menubar.Root
{...scope()}
ref={ref}
loop={defaultLoop}
dir={defaultDirection}
className={`${className} ${props.className || ''}`.trim()}
{...defaultProps}
{...props}
/>
));
const ConfiguredMenu = React.forwardRef<
React.ComponentRef<typeof Menubar.Menu>,
React.ComponentProps<typeof Menubar.Menu>
>((props, ref) => (
<Menubar.Menu {...scope()} {...props} />
));
const ConfiguredTrigger = React.forwardRef<
React.ComponentRef<typeof Menubar.Trigger>,
React.ComponentProps<typeof Menubar.Trigger>
>((props, ref) => (
<Menubar.Trigger {...scope()} ref={ref} {...props} />
));
// ... configure other components similarly
return {
Root: ConfiguredRoot,
Menu: ConfiguredMenu,
Trigger: ConfiguredTrigger,
// ... other configured components
};
}
interface MenubarOptions {
defaultLoop?: boolean;
defaultDirection?: 'ltr' | 'rtl';
className?: string;
[key: string]: any;
}
// Usage of factory
const MyMenubar = createMenubar({
defaultLoop: false,
defaultDirection: 'rtl',
className: 'my-custom-menubar'
});
function FactoryMenubarExample() {
return (
<MyMenubar.Root>
<MyMenubar.Menu>
<MyMenubar.Trigger>File</MyMenubar.Trigger>
{/* ... */}
</MyMenubar.Menu>
</MyMenubar.Root>
);
}'use client';
import * as Menubar from "@radix-ui/react-menubar";
// Helper functions for accessibility
function getMenubarAccessibilityProps(label: string) {
return {
'aria-label': label,
role: 'menubar'
};
}
function getMenuItemAccessibilityProps(
label: string,
shortcut?: string,
description?: string
) {
return {
'aria-label': shortcut ? `${label} ${shortcut}` : label,
'aria-description': description,
textValue: label
};
}
// Usage with accessibility helpers
function AccessibleMenubar() {
return (
<Menubar.Root {...getMenubarAccessibilityProps("Main menu")}>
<Menubar.Menu>
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item
{...getMenuItemAccessibilityProps(
"New File",
"Ctrl+N",
"Create a new document"
)}
onSelect={handleNew}
>
New File
</Menubar.Item>
<Menubar.Item
{...getMenuItemAccessibilityProps(
"Open File",
"Ctrl+O",
"Open an existing document"
)}
onSelect={handleOpen}
>
Open File
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
}// Utility types
type ScopeHook = () => Scope;
interface Scope {
__scopeMenubar?: string;
}
// Configuration types
interface MenubarOptions {
defaultLoop?: boolean;
defaultDirection?: 'ltr' | 'rtl';
className?: string;
[key: string]: any;
}
// State management types
interface MenubarState {
activeMenu: string;
menuHistory: string[];
openMenu: (value: string) => void;
closeMenu: () => void;
toggleMenu: (value: string) => void;
value: string;
onValueChange: (value: string) => void;
}Install with Tessl CLI
npx tessl i tessl/npm-radix-ui--react-menubar