Menu

Menu allows to create a dropdown list of options.

Source
Theme Source
pnpm dlx dreamy add menu

Basic usage of Menu.

<Menu.Root placement="bottom-start">
    <Menu.Trigger>
        <Button>Open Menu</Button>
    </Menu.Trigger>
    <Menu.Content>
        <Menu.Item icon={<IoAdd />} command="{actionKey} n">Add new</Menu.Item>
        <Menu.Item icon={<LuAlarmClock />} command="{actionKey} a">Set alarm</Menu.Item>
        <Menu.Item icon={<LuBattery />} command="{actionKey} b">Battery</Menu.Item>
        <Menu.Item icon={<LuTrash />} command="{actionKey} d">Delete</Menu.Item>
    </Menu.Content>
</Menu.Root>

You can change where the Menu is placed by using the placement prop. Use icon prop to place an icon on the beginning or the item and comamnd to show keybinding to the item. Using {actionKey} in command will automatically replace it with the action key of the user's operating system.

<Menu.Root placement="bottom-start">
    <Menu.Trigger>
        <Button>Open Menu</Button>
    </Menu.Trigger>
    <Menu.Content>
        <Menu.Item icon={<IoAdd />} command="{actionKey} n">Add new</Menu.Item>
        <Menu.Item icon={<LuAlarmClock />} command="{actionKey} a">Set alarm</Menu.Item>
        <Menu.Item icon={<LuBattery />} command="{actionKey} b">Battery</Menu.Item>
        <Menu.Item icon={<LuTrash />} command="{actionKey} d">Delete</Menu.Item>
    </Menu.Content>
</Menu.Root>

Menu comes with 4 different sizes.

{["xs", "sm", "md", "lg"].map((size) => (
    <Menu.Root key={size} size={size}>
        <Menu.Trigger>
            <Button>Open Menu</Button>
        </Menu.Trigger>
        <Menu.Content>
            <Menu.Item icon={<IoAdd />} command="{actionKey} n">Add new</Menu.Item>
            <Menu.Item icon={<LuAlarmClock />} command="{actionKey} a">Set alarm</Menu.Item>
            <Menu.Item icon={<LuBattery />} command="{actionKey} b">Battery</Menu.Item>
            <Menu.Item icon={<LuTrash />} command="{actionKey} d">Delete</Menu.Item>
        </Menu.Content>
    </Menu.Root>
))}

Use variant prop to set the variant of the menu.

export function VariantMenus() {
    return (
        <Flex
            wrapped
            gap={5}
        >
            {(
                ["plain", "stretched"]
            ).map((variant) => (
                <VariantMenu key={variant} variant={variant} />
            ))}
        </Flex>
    );
}
 
export function VariantMenu({ variant }: { variant: string }) {
    return (
        <Menu.Root variant={variant as any}>
            <Menu.Trigger>
                <Button w={"fit-content"}>Open Menu</Button>
            </Menu.Trigger>
            <Menu.Content>
                <Menu.Item
                    icon={<LuWarehouse />}
                    command="{actionKey} h"
                    as={<Link to="/" />}
                >
                    Homepage
                </Menu.Item>
                <Menu.Item
                    icon={<IoAdd />}
                    command="{actionKey} n"
                >
                    Add new
                </Menu.Item>
                <Menu.Item
                    icon={<LuAlarmClock />}
                    command="{actionKey} a"
                >
                    Set alarm
                </Menu.Item>
                <Menu.Item
                    icon={<LuBattery />}
                    command="{actionKey} b"
                >
                    Battery
                </Menu.Item>
                <Menu.Item
                    icon={<LuTrash />}
                    command="{actionKey} d"
                >
                    Delete
                </Menu.Item>
            </Menu.Content>
        </Menu.Root>
    );
}

You can use a custom link component with menu item but polymorphic props like as, asComp and asChild.

<Menu.Root placement="bottom-start">
    <Menu.Trigger>
        <Button w={"fit-content"}>Open Menu</Button>
    </Menu.Trigger>
    <Menu.Content>
        <Menu.Item 
            icon={<LuWarehouse />} 
            command="{actionKey} h"
            as={<Link to="/" />}
        >
            Homepage
        </Menu.Item>
        <Menu.Item icon={<IoAdd />} command="{actionKey} n">Add new</Menu.Item>
        <Menu.Item icon={<LuAlarmClock />} command="{actionKey} a">Set alarm</Menu.Item>
        <Menu.Item icon={<LuBattery />} command="{actionKey} b">Battery</Menu.Item>
        <Menu.Item icon={<LuTrash />} command="{actionKey} d">Delete</Menu.Item>
    </Menu.Content>
</Menu.Root>

You can control the menu with isOpen, onOpen and onClose props with useControllable hook.

export function ControlledMenu() {
    const { isOpen, onClose, onOpen } = useControllable();
 
    return (
        <Menu.Root
            isOpen={isOpen}
            onOpen={onOpen}
            onClose={onClose}
        >
            <Text>{isOpen ? "Open" : "Closed"}</Text>
            <Menu.Trigger>
                <Button>Open Menu</Button>
            </Menu.Trigger>
            <Menu.Content>
                <Menu.Item
                    icon={<LuWarehouse />} 
                    command="{actionKey} h"
                    as={<Link to="/" />}
                >
                    Homepage
                </Menu.Item>
                <Menu.Item icon={<IoAdd />} command="{actionKey} n">Add new</Menu.Item>
                <Menu.Item icon={<LuAlarmClock />} command="{actionKey} a">Set alarm</Menu.Item>
                <Menu.Item icon={<LuBattery />} command="{actionKey} b">Battery</Menu.Item>
                <Menu.Item icon={<LuTrash />} command="{actionKey} d">Delete</Menu.Item>
            </Menu.Content>
        </Menu.Root>
    );
}

command props does not provide any logic. You can use useEventListener hook to add logic to the commands.

export function InteractiveMenu() {
    const navigate = useNavigate();
    
    useEventListener("keydown", (event) => {
        if (!event[getActionKeyCode()]) return; // Making sure the ctrl/cmd key is pressed
 
        switch (event.key) {
            case "h": {
                event.preventDefault();
                navigate("/");
                alert("Homepage");
                break;
            }
            case "n": {
                event.preventDefault();
                alert("New");
                break;
            }
            case "a": {
                event.preventDefault();
                alert("Alarm");
                break;
            }
            case "b": {
                event.preventDefault();
                alert("Battery");
                break;
            }
            case "d": {
                event.preventDefault();
                alert("Delete");
                break;
            }
        }
    });
 
    return (
        <Menu.Root>
            <Menu.Trigger>
                <Button>Open Menu</Button>
            </Menu.Trigger>
            <Menu.Content>
                <Menu.Item
                    icon={<LuWarehouse />}
                    command="{actionKey} h"
                    as={<Link to="/" />}
                >
                    Homepage
                </Menu.Item>
                <Menu.Item
                    icon={<IoAdd />}
                    command="{actionKey} n"
                >
                    Add new
                </Menu.Item>
                <Menu.Item
                    icon={<LuAlarmClock />}
                    command="{actionKey} a"
                >
                    Set alarm
                </Menu.Item>
                <Menu.Item
                    icon={<LuBattery />}
                    command="{actionKey} b"
                >
                    Battery
                </Menu.Item>
                <Menu.Item
                    icon={<LuTrash />}
                    command="{actionKey} d"
                >
                    Delete
                </Menu.Item>
            </Menu.Content>
        </Menu.Root>
    );
}

You can add right content to the item with rightContent prop.

<Menu.Root>
    <Menu.Trigger>
        <Button>Open Menu</Button>
    </Menu.Trigger>
    <Menu.Content>
        <Menu.Item rightContent={<Icon as={LuFileWarning} color="warning" boxSize={4} />}>
            Select File
        </Menu.Item>
    </Menu.Content>
</Menu.Root>