Dropdown Menu

Dropdown Menu is the menu a control opens, with items, separators and labels.

Installation

Nothing to add. This component ships as source in your download, so it is already in your project.

Filesrc/vui/dropdown-menu.tsx
Import from@viliha/vui-react/dropdown-menu

The exported names are at the top of that file. The import path is an alias the download's tsconfig.json points at src/vui/, so you can move that folder and change one line rather than every file that imports from it.

Usage

dropdown-menu-demo.tsx
import {
  Dropdown,
  DropdownItem,
  DropdownSeparator,
  DropdownLabel,
  StaticOverlays,
} from "@viliha/vui-react/dropdown-menu";

Exports

Everything this family exports. They are read from the source, so this list is what the package actually ships.

PropTypeDefault
Dropdowncomponent—
DropdownItemcomponent—
DropdownSeparatorcomponent—
DropdownLabelcomponent—
StaticOverlayscomponent—
PropTypeDefaultWhat it does
labelstring—
icon?React.ReactNode—
align?"start" | "end""start"
childrenReact.ReactNode | ((close: () => void) => React.ReactNode)—The panel's contents. A function receives a `close` callback.
ariaLabel?string—Accessible name for icon-only triggers (when label is empty).
active?boolean—Render the trigger as a compact toolbar button (default) or a plain one.
labelClassName?string—Extra classes on the label text — e.g. `hidden sm:inline` to hide it on mobile so the trigger collapses to an icon-only button.
trigger?React.ReactNode | ((open: boolean) => React.ReactNode)—Replace the trigger's contents entirely. The default trigger is a toolbar button: an icon and a truncated label. An account menu is not that.
panelClassName?string—Extra classes on the panel, for a menu that is not a list of plain items.
offset?number0The gap between the trigger's bottom and the panel's top, in pixels. **Zero by default: a menu attaches to its trigger's border**.
placement?"bottom" | "right""bottom"Which side of the trigger the panel opens on. `"bottom"` by default.
title?string—The trigger's native `title`, for a hover hint. `ariaLabel` names the control for assistive tech and is invisible to a mouse.
bare?boolean—Whether the trigger renders as a button at all. A bare slot for an avatar row wants no chrome.
triggerClassName?string—The bare trigger's own classes. `bare` says "not a toolbar button"; it cannot also know what the caller's trigger *is*.
staticId?string—The id the static edition pairs this trigger with its panel by. Rendered as `data-vui-menu` on the trigger and used by `scripts/page-templates.mjs` to find the panel that belongs to it.
onOpenChange?(open: boolean) => void—Called when the menu opens or closes.
PropTypeDefaultWhat it does
childrenReact.ReactNode—
onSelect?() => void—
checked?boolean—
icon?React.ReactNode—
disabled?boolean—The action exists here but cannot be taken right now.
title?string—Why it is unavailable. Pair it with `disabled` rather than leaving a reader to guess.
trailing?React.ReactNode—A mark at the row's far edge, after the label.
variant?"destructive"—`destructive` for a row that deletes, revokes or closes something. **The absence of this is why twelve kebab menus were hand-rolled**.
PropTypeDefault
childrenReact.ReactNode—

Customize

Every value this component draws comes from the theme: the colours, the radius, the control height and the type scale. Change a token and this moves with everything else, which is the difference between theming the product and overriding a component. See Theming for the token contract, and Swapping defaults for replacing the markup itself.