MenuChevron Down
Dropdown - Docs - Artefact

Dropdown

Overlays
Auto-interactive

Introduction

A list of actions or options that appears when triggered. Supports custom placements with automatic viewport-overflow flipping, an optional arrow with center-pointing geometry, checkbox/radio/group items, cascading submenus, and click / hover / context menu trigger modes.

Usage

Basic Dropdown

import { Dropdown, Button } from "../components/ui";

export default function MyPage() {
  return (
    <Dropdown
      trigger={<Button>Open Dropdown</Button>}
      items={[
        { type: "item", label: "Edit", value: "edit" },
        { type: "separator" },
        { type: "checkbox", label: "Bold", value: "bold", checked: true },
        {
          type: "radio-group",
          value: "theme",
          label: "Theme",
          items: [
            { type: "radio", label: "Light", value: "light" },
            { type: "radio", label: "Dark", value: "dark" },
          ],
        },
      ]}
    />
  );
}

Button with Dropdown Menu

import { Dropdown } from "../components/ui";

export default function Page() {
  return (
    <Dropdown.Button
      type="primary"
      items={[{ type: "item", label: "Submit & Close", value: "close" }]}
      onClick={() => console.log("Primary click!")}
    >
      Submit Action
    </Dropdown.Button>
  );
}

Custom styling & semantic classNames

<Dropdown
  trigger={<Button>Styled Dropdown</Button>}
  classNames={{
    content: "custom-menu-card",
    item: "custom-menu-item"
  }}
  styles={{
    content: { boxShadow: "0 4px 20px rgba(0,0,0,0.15)" }
  }}
  items={[{ type: "item", label: "Custom Styled Item", value: "styled" }]}
/>

Center-pointing Arrow

<Dropdown
  trigger={<Button>Center Arrow</Button>}
  arrow={{ pointAtCenter: true }}
  placement="bottomRight"
  items={[{ type: "item", label: "Item 1", value: "1" }]}
/>

Groups and cascading submenus

<Dropdown
  trigger={<Button>Open Dropdown</Button>}
  items={[
    {
      type: "group",
      label: "File",
      items: [
        { type: "item", label: "New", value: "new" },
        { type: "item", label: "Open", value: "open" },
      ],
    },
    { type: "separator" },
    {
      type: "submenu",
      label: "Share",
      items: [
        { type: "item", label: "Email", value: "email" },
        { type: "item", label: "Link", value: "link" },
      ],
    },
  ]}
/>

Custom placement, arrow, and hover trigger

<Dropdown
  trigger={<Button>Hover Me</Button>}
  placement="bottomRight"
  triggerMode="hover"
  arrow={true}
  mouseEnterDelay={100}
  mouseLeaveDelay={150}
  items={[
    { type: "item", label: "Profile", value: "profile" },
    { type: "item", label: "Settings", value: "settings" },
    { type: "separator" },
    { type: "item", label: "Logout", value: "logout" },
  ]}
/>

Context menu

A trigger wired for "contextMenu" opens on right-click, anchored at the pointer, instead of behaving like a button:

<Dropdown
  trigger={<div>Right-click this area</div>}
  trigger={"contextMenu"}
  items={[
    { type: "item", label: "Copy", value: "copy" },
    { type: "item", label: "Paste", value: "paste" },
  ]}
/>

Controlled open state

const [open, setOpen] = useState(false);

<Dropdown
  open={open}
  onOpenChange={setOpen}
  trigger={<Button>Open Dropdown</Button>}
  items={[{ type: "item", label: "Edit", value: "edit" }]}
/>;

CMS Page Builder

This component is available as a menu block in the Page Builder (content/pages/*.json) — the CMS block is labelled "Menu" but renders through this Dropdown component:

{
  "type": "menu",
  "triggerText": "Open Dropdown",
  "items": [
    { "type": "item", "label": "Edit", "value": "edit" },
    { "type": "separator" },
    { "type": "item", "label": "Delete", "value": "delete" }
  ]
}

Props

Dropdown

PropTypeDescriptionDefault
triggerJSX.ElementElement that opens the menu when activated.-
itemsDropdownItem[]The menu items to render.-
openbooleanWhether the menu is open (controlled).-
defaultOpenbooleanWhether the menu is open by default (uncontrolled).false
disabledbooleanDisables every trigger mode and renders the trigger inert.false
interactivebooleanForces hydration as an island. Defaults to true.true

| arrow | `boolean \ | { pointAtCenter?: boolean }` | Show a pointer arrow pointing from the menu to the trigger. Can align exactly with the trigger's center. | false |

| placement | string | Menu placement: "top" \ | "topLeft" \ | "topRight" \ | "bottom" \ | "bottomLeft" \ | "bottomRight" \ | "left" \ | "leftTop" \ | "leftBottom" \ | "right" \ | "rightTop" \ | "rightBottom". Dash-case aliases are also accepted. | "bottomLeft" |

| trigger | `("click" \ | "hover" \ | "contextMenu" \ | "contextDropdown")[] \ | string` | Trigger interaction modes to open/close the menu. (Aliased as triggerMode). | ["click"] |

| mouseEnterDelay | number | Delay in ms before opening when trigger includes "hover". | 150 | | mouseLeaveDelay | number | Delay in ms before closing when trigger includes "hover". | 100 | | closeOnEscape | boolean | Close when Escape is pressed. | true |

| onOpenChange | `(open: boolean, info?: { source: 'trigger' \ | 'menu' }) => void` | Called when the menu opens or closes. source specifies what triggered the action. | - |

| onSelect | (value: string) => void | Called with an item's value when it is activated. | - |

| size | `"xs" \ | "sm" \ | "md" \ | "lg" \ | "xl"` | The size of the menu. | "md" |

| class | string | Custom CSS classes for the root element. | - | | contentClass | string | Custom CSS classes for the content element. | - | | positionerClass | string | Custom CSS classes for the positioner element. | - | | destroyOnHidden | boolean | Whether to destroy/unmount the popup content from DOM when hidden. (Aliased as destroyPopupOnHide). | false | | popupRender | (menu: JSX.Element) => JSX.Element | Customize/wrap the popup content. (Aliased as dropdownRender). | - | | classNames | Record<string, string> | Custom CSS classes for each semantic structure component inside the Dropdown. | - | | styles | Record<string, any> | Custom inline styles for each semantic structure component inside the Dropdown. | - |

classNames and styles slots:

  • root (or positioner): The overlay absolute positioning container.
  • content: The popup card containing list items.
  • item: Individual list item (including checkbox and radio items).
  • trigger: The main trigger button/wrapper.
  • arrow: The outer arrow wrapper.
  • arrowTip: The styled inner diamond.

DropdownItem

PropertyTypeDescription

| type | `"item" \ | "separator" \ | "checkbox" \ | "radio" \ | "radio-group" \ | "submenu" \ | "group"` | The kind of menu entry. |

| label | string | Display text (for item, checkbox, radio, submenu, and optionally group). | | value | string | Unique value (for item, checkbox, radio, radio-group). | | checked | boolean | Checked state (for checkbox, radio). | | icon | JSX.Element | Leading icon (for item, checkbox, radio, submenu). | | indicator | JSX.Element | Custom trailing indicator element (for item). | | items | DropdownItem[] | Nested items (for radio-group, submenu, group). Submenu/group items may themselves be any DropdownItem, including further submenus. | | disabled | boolean | Whether the item (or, for submenu, the whole nested menu) is disabled. | | class | string | Custom CSS classes for the item. |


Dropdown.Button (DropdownButton)

A Button with a dropdown menu, rendered as a continuous attached button group.

PropTypeDescriptionDefault

| type | `"default" \ | "primary" \ | "dashed" \ | "link" \ | "text" \ | "solid" \ | "outline" \ | "subtle" \ | "plain" \ | "surface"` | The type of buttons to render. | "outline" |

| danger | boolean | Renders buttons with danger visual theme. | false | | disabled | boolean | Disables both the primary button and the dropdown trigger. | false | | loading | boolean | Renders the primary button in loading/busy state. | false | | onClick | (e: MouseEvent) => void | Click event handler for the left/primary button. | - | | icon | JSX.Element | Icon for the right/trigger button. | <EllipsisIcon /> | | buttonsRender | (buttons: JSX.Element[]) => JSX.Element[] | Custom render function to customize both buttons. | - |

Supports all common Dropdown props such as items, placement, arrow, classNames, styles, etc.


Note

Please ensure that trigger accepts onMouseEnter, onMouseLeave, onFocus, and onClick — the trigger element is cloned in place with these (and the relevant ARIA/data-*) attributes attached, rather than wrapped.

Limitations

The interactive island positions the menu relative to its trigger's own wrapper (position: absolute, not a portal): it flips to the opposite side when the requested placement would overflow the viewport, and clamps the cross axis so the menu never renders off-screen. It does not track scroll containers or resize observers the way Floating UI does — repositioning only re-runs on window resize while open, and scrolling the page moves the menu along with the trigger for free. A menu opened from deep inside a scrolling or overflow: hidden container can still be visually clipped by that container, the same tradeoff Popover and Tooltip make.

Context menus and submenus are the exception: since they aren't anchored to a trigger's box (a context menu opens at the pointer; a submenu opens beside a menu item), they're positioned with position: fixed and hand-rolled coordinates instead, and are re-run on Escape/scroll/resize like the rest of the menu.

Each submenu is its own nested interactive island, scoped via a data-overlay-root marker so a parent menu's positioning and outside-click logic don't reach into a submenu's own content (and vice versa). Selecting a regular item closes every open menu level it bubbles through; toggling a checkbox/radio item inside any level keeps the whole stack open.

The _closed exit animation (slide-fade-out) plays before the menu is actually removed from layout — closing doesn't hide it instantly.