Skip to content

Dropdown Menu

Open a menu of actions or options from a button.

import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, MenuPopup } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use Dropdown Menu for actions such as open, rename or delete, and for view settings. Use Select to choose a value, or Command to search a long list. You can also offer the same actions in a Context Menu, while keeping the dropdown as a visible way to reach them.

import { useState } from 'react'
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger } from '@youj/react'
import { MoreHorizontalIcon, PencilIcon, TrashIcon } from '@youj/icons'

export function ProjectActions() {
  const [open, setOpen] = useState(false)
  return (
    <DropdownMenu open={open} onOpenChange={setOpen}>
      <DropdownMenuTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Project actions" />}>
        <MoreHorizontalIcon active={open} />
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={rename}>
          <PencilIcon />
          Rename
          <DropdownMenuShortcut>⌘R</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="danger" onClick={remove}>
          <TrashIcon />
          Delete project
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

MoreHorizontalIcon is a state icon: driven by the menu's open state, its dots become a close mark while the menu is open and return when it closes. Run an item's action from onClick. It fires for pointer and keyboard alike, then the menu closes. A leading icon is muted until its row is highlighted. Youj icons also play their choreography then, because a menu item is their trigger. DropdownMenuShortcut shows a row's shortcut, trailing and muted. It's a label only, so bind the keys yourself.

Reference

Propscontainer, presentation, inset… (4)

Props

DropdownMenuContent props
PropType
containerHTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null>

Where the portal renders. Default: document.body.

–
presentationOverlayPresentation
'auto''sheet''popover'

auto (the default) is a popover under a pointer and a bottom sheet on touch; force either one.

default 'auto'

'auto'
DropdownMenuItem props
PropType
insetboolean

Indents the label to line up with rows that have an icon.

–
variant'default' | 'danger'

danger tints the row red: deleting, revoking, leaving.

default 'default'

'default'
onSelect'onSelect is the DOM text-selection event and never fires on a menu item: use onClick'–
Other props pass through: extends MenuPrimitive.Item.Props.

DropdownMenu takes Base UI's root props (open, defaultOpen, onOpenChange, modal, which is on by default), and DropdownMenuTrigger takes the trigger's (openOnHover, delay, render). DropdownMenuCheckboxItem takes checked, defaultChecked and onCheckedChange, and DropdownMenuRadioGroup takes value, defaultValue and onValueChange. Rows and labels take inset, rows take disabled, and closeOnClick changes whether choosing an item closes the menu (on for items, off for checkbox and radio items). DropdownMenuSubContent takes the same placement props as the content, opening on inline-end by default.

MotionOpen, close, highlight… · reduced motion

Motion

Motion tokens
MomentTokens
Openscale .96 → 1 and fade, out of the trigger (yj-pop)
duration.fastease.enter

140ms · cubic-bezier(0.16, 1, 0.3, 1)

140mscubic-bezier(0.16, 1, 0.3, 1)
Close
duration.instantease.exit

80ms · cubic-bezier(0.4, 0, 1, 1)

80mscubic-bezier(0.4, 0, 1, 1)
Highlightone SlidingIndicator per popup glides between rows
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Highlight across a separatorholds its row this long before it lets go
duration.fast

140ms

140ms
Destructive washred tint on the highlighted row
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Checkdraws in when a row turns on
duration.slowease.emphasized

340ms · cubic-bezier(0.6, 0, 0.1, 1)

340mscubic-bezier(0.6, 0, 0.1, 1)
Keyboard open, Escape closeBase UI sets data-instant
no token

0ms

0ms

Reduced motion

The menu appears at once, the highlight jumps from row to row, and checks appear already drawn.

Rows paint nothing themselves on hover. A single highlight per popup follows Base UI's highlighted row, so moving quickly down a menu reads as one traveling surface, and a mid-glide change of direction retargets it with its velocity. In forced-colors mode the fill is dropped and the highlighted row gets an outline.

Reduced motion

The menu opens and closes at once and the highlight moves without gliding. Checks are drawn as soon as a row turns on. Rows, keyboard behavior and focus handling don't change.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • The trigger has aria-haspopup="menu" and aria-expanded, and the popup is a menu. Rows are menuitem, menuitemcheckbox or menuitemradio, with aria-checked where they hold state.
  • A label inside a group names the group. Radio items sit in a group, so the choice is announced with its name.
  • Focus moves into the menu when it opens and returns to the trigger when it closes. While open, the menu is modal: the page doesn't scroll and outside presses only close it.
  • The popup itself is a tab stop, so a menu long enough to scroll is reachable from the keyboard. Tab closes the menu.
KeyAction
Enter / Space / ↓Opens the menu from the trigger and focuses the first row.
↑Opens the menu from the trigger and focuses the last row.
↑ / ↓Moves between rows.
Home / EndJumps to the first or last row.
LettersJump to the next row that starts with them (typeahead).
Enter / SpaceRuns the row, or toggles a checkbox or radio row.
→ / ←Opens a submenu on its first row, or closes it back onto its row. Mirrored in right-to-left layouts.
EscapeCloses the menu and returns focus to the trigger.