Skip to main content

Settings Menu Style

Usage in Gaia

Perfect for settings menus, context menus, and multi-level navigation. Hover-based submenus that feel responsive and natural.

Installation

    Usage

    import { NestedMenu } from "@/components/ui/nested-menu";
    import { HugeiconsIcon, Settings01Icon, BookOpen02Icon } from "@/components/icons";
    import { Button } from "@/components/ui/button";
    
    export default function Example() {
      const menuSections = [
        {
          title: "Resources",
          items: [
            {
              key: "documentation",
              label: "Documentation",
              icon: (props) => <HugeiconsIcon icon={BookOpen02Icon} {...props} />,
              onSelect: () => console.log("Documentation clicked"),
            },
            {
              key: "download",
              label: "Download",
              hasSubmenu: true,
              submenuItems: [
                {
                  key: "mac",
                  label: "macOS",
                  onSelect: () => console.log("Download macOS"),
                },
                {
                  key: "windows",
                  label: "Windows",
                  onSelect: () => console.log("Download Windows"),
                },
              ],
            },
          ],
        },
      ];
    
      return (
        <NestedMenu
          sections={menuSections}
          trigger={<Button>Open Menu</Button>}
          side="bottom"
          align="start"
        />
      );
    }
    

    Examples

    Settings Menu

    Create a comprehensive settings menu with submenus:
    const settingsMenu = [
      {
        title: "Settings",
        items: [
          {
            key: "profile",
            label: "Profile",
            icon: (props) => <HugeiconsIcon icon={UserCircleIcon} {...props} />,
            onSelect: () => navigate("/profile"),
          },
          {
            key: "appearance",
            label: "Appearance",
            icon: (props) => <HugeiconsIcon icon={PaletteIcon} {...props} />,
            hasSubmenu: true,
            submenuItems: [
              {
                key: "theme-light",
                label: "Light Mode",
                onSelect: () => setTheme("light"),
              },
              {
                key: "theme-dark",
                label: "Dark Mode",
                onSelect: () => setTheme("dark"),
              },
              {
                key: "theme-auto",
                label: "System",
                onSelect: () => setTheme("system"),
              },
            ],
          },
        ],
      },
    ];
    

    Context Menu with Sections

    Organize menu items into multiple sections:
    const contextMenu = [
      {
        items: [
          {
            key: "edit",
            label: "Edit",
            icon: EditIcon,
            onSelect: () => handleEdit(),
          },
          {
            key: "duplicate",
            label: "Duplicate",
            icon: CopyIcon,
            onSelect: () => handleDuplicate(),
          },
        ],
        showDivider: true,
      },
      {
        items: [
          {
            key: "delete",
            label: "Delete",
            icon: TrashIcon,
            variant: "danger",
            onSelect: () => handleDelete(),
          },
        ],
      },
    ];
    

    Custom Icon Colors

    Highlight specific menu items with custom icon colors:
    const items = [
      {
        key: "upgrade",
        label: "Upgrade to Pro",
        icon: CircleArrowUpIcon,
        iconColor: "#00bbff",
        className: "text-[#00bbff] font-medium",
        onSelect: () => navigate("/upgrade"),
      },
    ];
    

    With Separators

    Add visual separators between menu items:
    const items = [
      {
        key: "profile",
        label: "Profile",
        onSelect: () => {},
        separator: true, // Adds a divider after this item
      },
      {
        key: "settings",
        label: "Settings",
        onSelect: () => {},
      },
    ];
    

    Controlled State

    Control the menu open state externally:
    const [isOpen, setIsOpen] = useState(false);
    
    return (
      <NestedMenu
        sections={menuSections}
        trigger={<Button>Menu</Button>}
        open={isOpen}
        onOpenChange={setIsOpen}
      />
    );
    

    Custom Positioning

    Control where the menu appears:
    <NestedMenu
      sections={menuSections}
      trigger={<Button>Menu</Button>}
      side="right" // "top" | "right" | "bottom" | "left"
      align="start" // "start" | "center" | "end"
      sideOffset={16}
    />
    

    Using the Hook

    For custom implementations, use the useNestedMenu hook:
    import { useNestedMenu, NestedMenuTooltip } from "@/components/ui/nested-menu";
    
    function CustomMenu() {
      const {
        isOpen,
        setIsOpen,
        itemRef,
        handleMouseEnter,
        handleMouseLeave,
        cancelClose,
      } = useNestedMenu();
    
      return (
        <>
          <button
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
          >
            Hover for submenu
          </button>
          <NestedMenuTooltip
            isOpen={isOpen}
            onOpenChange={setIsOpen}
            itemRef={itemRef}
            menuItems={submenuItems}
            onMouseEnter={cancelClose}
            onMouseLeave={handleMouseLeave}
          />
        </>
      );
    }
    

    Features

    • Hover Activation: Submenus open on hover with smooth transitions
    • Multi-level Support: Nested submenus up to any depth
    • Smart Timing: Delay handling prevents accidental closes
    • Section Support: Organize items into titled sections
    • Icon Support: Add icons to menu items with custom colors
    • Variants: Built-in “danger” variant for destructive actions
    • Separators: Visual dividers between items or sections
    • Custom Styling: Full className support for customization
    • Keyboard Accessible: Proper focus management and keyboard support
    • Controlled/Uncontrolled: Works both ways
    • Position Control: Flexible positioning options

    Props

    NestedMenu

    PropTypeDefaultDescription
    sectionsNestedMenuSectionProps[]-Array of menu sections
    triggerReact.ReactNode-Element that triggers the menu
    iconClassNamestring”h-4 w-4”Size for icons
    arrowIconReact.ComponentType-Custom arrow icon for submenus
    side”top” | “right” | “bottom” | “left""right”Menu placement
    align”start” | “center” | “end""start”Alignment relative to trigger
    sideOffsetnumber8Distance from trigger
    classNamestring-Additional CSS classes
    openboolean-Controlled open state
    onOpenChange(open: boolean) => void-Callback when open state changes

    NestedMenuSection

    PropertyTypeDefaultDescription
    titlestring-Section title
    itemsNestedMenuItem[]-Menu items in this section
    showDividerbooleanfalseShow divider after section

    NestedMenuItem

    PropertyTypeDefaultDescription
    keystring-Unique key for the item
    labelstring-Display text
    iconReact.ComponentType-Icon component
    onSelect() => void-Click handler
    hasSubmenubooleanfalseWhether item has submenu
    submenuItemsNestedMenuItem[]-Submenu items (if hasSubmenu)
    variant”default” | “danger""default”Visual variant
    classNamestring-Additional CSS classes
    iconColorstring-Custom icon color (CSS value)
    separatorbooleanfalseShow divider after this item

    useNestedMenu Hook

    Returns:
    PropertyTypeDescription
    isOpenbooleanWhether submenu is open
    setIsOpen(open: boolean) => voidSet open state
    itemRefHTMLElement | nullReference to trigger element
    handleMouseEnter(e: React.MouseEvent) => voidMouse enter handler
    handleMouseLeave() => voidMouse leave handler
    cancelClose() => voidCancel pending close

    NestedMenuTooltip

    PropTypeDefaultDescription
    isOpenboolean-Whether tooltip is visible
    onOpenChange(open: boolean) => void-Callback when open state changes
    itemRefHTMLElement | null-Reference element for positioning
    menuItemsNestedMenuItem[]-Items to display
    iconClassNamestring”h-4 w-4”Icon size
    classNamestring-Additional CSS classes
    onMouseEnter() => void-Mouse enter handler
    onMouseLeave() => void-Mouse leave handler

    Accessibility

    • Keyboard Navigation: Full keyboard support for navigation
    • Focus Management: Proper focus indicators and management
    • ARIA Attributes: Proper roles and attributes for screen readers
    • Auto Focus Prevention: Prevents unwanted focus stealing
    • Interaction Outside: Handles clicks outside gracefully

    Notes

    • The component uses Radix UI Popover primitive for robust positioning
    • Hover delays are tuned for a natural feel (300ms before closing)
    • Submenus automatically position themselves to avoid viewport edges
    • The component handles cleanup of timers on unmount
    • Works with both controlled and uncontrolled state management

    Build docs developers (and LLMs) love