Dropdown

A menu hung off a button — icons, shortcuts, checkable rows, radio sets and submenus.

Source
1 file
Dependencies
framer-motion
Playground
no

Examples

Every variant of the shipped component, live. Switch the layout to read them as a table or one at a time, and open Code for the file behind them.

01Basic
Placement
02Side
03Align
04Offset
05Width
Rows
06Icon
07Checkable
SidebarStatus bar
08One of a set
09Disabled
Nesting
10Submenus

Installation

One package for the whole library — run this once and every component on this site is importable. Adding the tokens to your stylesheet is the other half; the installation guide covers it.

npm install kipui
import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator, } from "kipui";
Peer dependencies

react 19

react-dom 19

tailwindcss 4

Bundled dependencies

framer-motion

Usage

dropdown-example.tsx
import {
  Dropdown,
  DropdownTrigger,
  DropdownContent,
  DropdownItem,
  DropdownSeparator,
} from "kipui";

<Dropdown>
  <DropdownTrigger>Options</DropdownTrigger>
  <DropdownContent align="end">
    <DropdownItem icon={<CopyIcon />} shortcut="⌘C" onSelect={copy}>
      Copy link
    </DropdownItem>
    <DropdownItem onSelect={rename}>Rename</DropdownItem>
    <DropdownSeparator />
    <DropdownItem variant="destructive" onSelect={remove}>
      Delete
    </DropdownItem>
  </DropdownContent>
</Dropdown>
  • It opens and closes on the same machinery as Select — measured position, flip when there is no room, tracked while the page scrolls — so a menu near the fold behaves the way the listbox next to it does.
  • `side` and `align` are preferences. A menu that cannot fit on the side you asked for is flipped to the other one, and the side it lands on is then held while you scroll rather than flipping back and forth.
  • Focus stays on the menu itself and the active row is named by `aria-activedescendant` — so arrow keys, Home/End, Enter, Space and typing the first letters all work without focus ever landing somewhere you cannot see.
  • Checkable rows leave the menu open and radio rows close it: toggles come in runs, a choice out of a set is one decision. Both are overridable with `closeOnSelect`.
  • `asChild` makes an existing element the trigger — a `Button`, an avatar, an icon button in a table row. It expects exactly one element, and merges its own ref with whatever ref that element already had.
  • Submenus open on hover after a short delay and close after a longer one, so crossing a row on the way elsewhere does not open it and travelling to the panel does not close it. → and ← do the same thing from the keyboard.

API reference

Transcribed from the component's own types. Props with no default are required.

Dropdown

PropDefaultDescription
openbooleanrequiredControlled open state. Leave unset to let the menu own it.
defaultOpenbooleanfalseUncontrolled seed.
onOpenChange(open: boolean) => voidrequiredFires whenever the menu opens or closes, however it was triggered.
disabledbooleanfalseBlocks the trigger, and closes the menu if it is open.
portalTargetHTMLElement | nullrequiredWhere every surface is portalled. Defaults to the body.

DropdownTrigger

PropDefaultDescription
asChildbooleanfalseUse the child element as the trigger instead of a button of our own. Expects exactly one element.
showChevronbooleantrueThe chevron after the label. Ignored under `asChild`.

DropdownContent

PropDefaultDescription
side"top" | "bottom" | "left" | "right""bottom"Preferred side. Flipped only when it does not fit and the other one does.
align"start" | "center" | "end""start"Which edge of the menu lines up with the trigger.
sideOffsetnumber6Gap between trigger and menu, in px.
alignOffsetnumber0Shift along the alignment axis, in px. Negative moves towards the start.
matchTriggerWidthbooleanfalseMenu is at least as wide as its trigger.

DropdownItem

PropDefaultDescription
onSelect() => voidrequiredRuns on click, Enter and Space.
closeOnSelectbooleantrueSet false for a row that leaves the menu open — a filter toggle, say.
iconReactNoderequiredLeading element. An icon at `size-4` reads best.
shortcutReactNoderequiredTrailing hint. A plain string is rendered in the shortcut style.
descriptionReactNoderequiredSecond line under the label, for rows that need explaining.
variant"default" | "destructive""default"`destructive` recolours the row and its icon.
disabledbooleanfalseSkipped by the keyboard and by the pointer.
insetbooleanfalseIndents past the indicator column, to line up with checkable rows.

DropdownCheckboxItem

PropDefaultDescription
checkedbooleanfalseWhether the row shows its check.
onCheckedChange(checked: boolean) => voidrequiredFires with the value the row is moving to.
closeOnSelectbooleanfalseDefaults to staying open — checkable rows are usually toggled in runs.
iconReactNoderequiredLeading element, after the check column.
shortcutReactNoderequiredTrailing hint.
descriptionReactNoderequiredSecond line under the label.
variant"default" | "destructive""default"`destructive` recolours the row and its icon.
disabledbooleanfalseSkipped by the keyboard and by the pointer.

DropdownRadioItem

PropDefaultDescription
valuestringrequiredRequired. Reported to the enclosing `DropdownRadioGroup`.
closeOnSelectbooleantrueDefaults to closing: picking one of a set is a decision, not a toggle.
indicator"dot" | "check""dot"The mark on the chosen row. A dot is the menu-radio convention; a check reads better for a list of places you switch between — a workspace, an account, a branch.
iconReactNoderequiredLeading element, after the radio column.
shortcutReactNoderequiredTrailing hint.
descriptionReactNoderequiredSecond line under the label.
variant"default" | "destructive""default"`destructive` recolours the row and its icon.
disabledbooleanfalseSkipped by the keyboard and by the pointer.

DropdownSubContent

PropDefaultDescription
side"top" | "bottom" | "left" | "right""right"Preferred side. Flipped to the other one when there is no room.
align"start" | "center" | "end""start"Which edge of the submenu lines up with its row.
sideOffsetnumber2Gap between the row and the submenu, in px.
alignOffsetnumber-5Pulls the submenu back across the panel's padding and border, so its first row sits on the same line as the row that opened it.