CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-radix-ui--react-menubar

An accessible React menubar component that provides keyboard navigation, submenus, and customizable styling while maintaining semantic HTML structure and screen reader compatibility.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

utilities.mddocs/

Utilities

Utility functions for advanced usage patterns, context management, and creating isolated menubar instances.

Capabilities

createMenubarScope

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>
  );
}

Advanced Usage Patterns

Custom Hook for Menubar State

'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>
  );
}

Menubar Component Factory

'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>
  );
}

Accessibility Helpers

'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>
  );
}

Type Definitions

// 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

docs

core-components.md

index.md

interactive-items.md

menu-items.md

submenus.md

utilities.md

tile.json