Skip to main content

Menu

The Menu component renders a lightweight, accessible dropdown that can be opened via click or hover and positioned relative to its trigger. It’s composed, headless-friendly, and easy to customize.


Anatomy

The Menu is a composition of small primitives:

  • Menu.Menu: The container and state provider.
  • Menu.Target: The trigger button that opens the dropdown.
  • Menu.DropDown: The positioned panel that contains the items.
  • Menu.Item: A clickable action row.
  • Menu.Label: A non-interactive label row.
  • Menu.Divider: A horizontal separator.
import { Menu } from "@kousta-ui/components";
import { Button } from "@kousta-ui/components";

export default function Example() {
return (
  <Menu.Menu>
    <Menu.Target>
      <Button variant="primary-light">Open menu</Button>
    </Menu.Target>
    <Menu.DropDown>
      <Menu.Item>Profile</Menu.Item>
      <Menu.Item>Settings</Menu.Item>
      <Menu.Divider />
      <Menu.Item>Log out</Menu.Item>
    </Menu.DropDown>
  </Menu.Menu>
);
}

Props

NameTypeDefaultProvider?Description
type"hover" | "click""click"YesHow the menu opens.
closeOnClickbooleantrueYesIf true, clicking any Menu.Item closes the menu by default.
positionMenuPosition"Bottom-Start"YesWhere the dropdown appears relative to the trigger.
offsetnumber4YesGap (in px) between the trigger and dropdown.
NameTypeDefaultProvider?Description
closeMenuOnClickbooleanundefinedYesPer-item override for the container's closeOnClick behavior.
disabledbooleanfalseNoDisables the item and prevents interaction.
leftSectionReactNodeNoOptional element/icon rendered on the left.
rightSectionReactNodeNoOptional element/icon rendered on the right.

Note Props marked with Yes in the Provider? column can be configured globally using the ComponentPropsProvider.


Usage

Trigger Modes

Click (Default)

The menu opens when the Menu.Target is clicked.

import { Menu, Button } from "@kousta-ui/components";

export default function Example() {
return (
  <Menu.Menu type="click">
    <Menu.Target>
      <Button>Click me</Button>
    </Menu.Target>
    <Menu.DropDown>
      <Menu.Item>Item 1</Menu.Item>
      <Menu.Item>Item 2</Menu.Item>
    </Menu.DropDown>
  </Menu.Menu>
);
}

Hover

Set type="hover" to open the menu on mouse enter and close on mouse leave.

import { Menu, Button } from "@kousta-ui/components";

export default function Example() {
return (
  <Menu.Menu type="hover">
    <Menu.Target>
      <Button variant="primary-light">Hover me</Button>
    </Menu.Target>
    <Menu.DropDown>
      <Menu.Item>Item 1</Menu.Item>
      <Menu.Item>Item 2</Menu.Item>
    </Menu.DropDown>
  </Menu.Menu>
);
}

Close Behavior

By default, clicking any item closes the menu (closeOnClick={true}). You can override this globally on the container or per-item with closeMenuOnClick.

import { Menu, Button } from "@kousta-ui/components";

export default function Example() {
return (
  <Menu.Menu closeOnClick={false}>
    <Menu.Target>
      <Button variant="neutral-outline">Bulk actions</Button>
    </Menu.Target>
    <Menu.DropDown>
      <Menu.Item>Pin</Menu.Item>
      <Menu.Item closeMenuOnClick>Share</Menu.Item>
      <Menu.Item>Archive</Menu.Item>
    </Menu.DropDown>
  </Menu.Menu>
);
}

Positioning

The dropdown can be placed on any side and alignment relative to the trigger. Use the interactive controls below to see how position and offset work.

Preview (interactive)

With Icons, Labels & Dividers

Compose the menu with Menu.Label, Menu.Divider, and leftSection/rightSection props on items for a richer UI.

import { Menu, Button } from "@kousta-ui/components";
import { LuUser, LuSettings, LuLogOut } from "react-icons/lu";

export default function Example() {
return (
  <Menu.Menu>
    <Menu.Target>
      <Button variant="primary-light">Account</Button>
    </Menu.Target>
    <Menu.DropDown>
      <Menu.Label>Profile</Menu.Label>
      <Menu.Item leftSection={<LuUser />}>View profile</Menu.Item>
      <Menu.Item leftSection={<LuSettings />}>Preferences</Menu.Item>
      <Menu.Divider />
      <Menu.Label>Session</Menu.Label>
      <Menu.Item leftSection={<LuLogOut />} closeMenuOnClick>
        Log out
      </Menu.Item>
    </Menu.DropDown>
  </Menu.Menu>
);
}

Disabled Items

Pass the disabled prop to any Menu.Item to make it non-interactive.

import { Menu, Button } from "@kousta-ui/components";

export default function Example() {
return (
  <Menu.Menu>
    <Menu.Target>
      <Button>Move</Button>
    </Menu.Target>
    <Menu.DropDown>
      <Menu.Item>Project A</Menu.Item>
      <Menu.Item disabled>Project B (locked)</Menu.Item>
      <Menu.Item>Project C</Menu.Item>
    </Menu.DropDown>
  </Menu.Menu>
);
}

Styles & customization

Runtime classes

You can target these classes with CSS to customize the Menu appearance:

  • kui-menu: The main container (Menu.Menu).
  • kui-menu-target: The trigger button (Menu.Target).
  • kui-menu-dropdown: The dropdown panel (Menu.DropDown).
  • kui-menu-item: A menu item (Menu.Item).
  • kui-disabled: Added to a kui-menu-item when it is disabled.
  • kui-menu-label: A label (Menu.Label).
  • kui-menu-divider: A divider (Menu.Divider).

Customization with kui-classnames

You can use these class names to style Menu components:

/* Customize the dropdown panel */
.kui-menu-dropdown {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* Style menu items on hover */
.kui-menu-item:hover {
background-color: var(--kui-primary-100);
}

/* Customize disabled items */
.kui-menu-item.kui-disabled {
opacity: 0.5;
cursor: not-allowed;
}

/* Style the divider */
.kui-menu-divider {
border-color: var(--kui-neutral-300);
margin: 0.5rem 0;
}

Tokens used by the default styles

  • Colors: --kui-neutral-*
  • Spacing: --kui-spacing-sm
  • Rounding: --kui-rounded

Component Props Provider

You can set global defaults for Menu components using the ComponentPropsProvider:

import { Menu, ComponentPropsProvider } from "@kousta-ui/components";

export default function Example() {
return (
  <ComponentPropsProvider
    menu={{
      menu: {
        type: "click",
        closeOnClick: true,
        position: "Bottom-Start",
        offset: 8,
      },
      menuItem: {
        closeMenuOnClick: true,
      },
    }}
  >
    <Menu.Menu>
      <Menu.Target>Open Menu</Menu.Target>
      <Menu.DropDown>
        <Menu.Item>Item 1</Menu.Item>
        <Menu.Item closeMenuOnClick={false}>Item 2 (stays open)</Menu.Item>
      </Menu.DropDown>
    </Menu.Menu>
  </ComponentPropsProvider>
);
}

See the ComponentPropsProvider documentation for more details.


Types (reference)

import { ReactNode } from "react";

export type MenuOpenPosition = "Top" | "Bottom" | "Left" | "Right";
export type MenuOpenLocation = "Start" | "End" | "Center";

export type MenuPosition = \`\${MenuOpenPosition}-\${MenuOpenLocation}\`;

// Valid values:
// 'Top-Start' | 'Top-Center' | 'Top-End' |
// 'Bottom-Start' | 'Bottom-Center' | 'Bottom-End' |
// 'Left-Start' | 'Left-Center' | 'Left-End' |
// 'Right-Start' | 'Right-Center' | 'Right-End'

export type MenuProps = {
type?: "hover" | "click";
closeOnClick?: boolean;
position?: MenuPosition;
offset?: number;
};

export type MenuItemProps = {
closeMenuOnClick?: boolean;
disabled?: boolean;
leftSection?: ReactNode;
rightSection?: ReactNode;
};