Skip to content
TopClass UI

Dropdown menu

A menu of actions and links behind one button. Link rows are real anchors, so open in a new tab and middle-click work.

client - size not measured yet

Dropdown menu

client - size not measured yet

A menu of actions and links behind one button. Link rows are real anchors, so open in a new tab and middle-click work.

Install

Try it

placement
tone
delay
closeDelay

The code

dropdown.tsx
import { copy } from 'topclassui/icons/copy'
import { gear } from 'topclassui/icons/gear'
import { trash } from 'topclassui/icons/trash'
import { user } from 'topclassui/icons/user'
import { Button } from 'topclassui/button'
import { Svg } from 'topclassui/svg'
import { DropdownItem, DropdownPopup, DropdownRoot, DropdownSeparator, DropdownTrigger } from 'topclassui/dropdown'

<DropdownRoot>
  <DropdownTrigger openOnHover render={<Button variant="outline">Actions</Button>} />
  <DropdownPopup>
    <DropdownItem icon={<Svg icon={user} />} shortcut="Ctrl P">Profile</DropdownItem>
    <DropdownItem icon={<Svg icon={gear} />}>Settings</DropdownItem>
    <DropdownItem icon={<Svg icon={copy} />}>Duplicate</DropdownItem>
    <DropdownSeparator />
    <DropdownItem icon={<Svg icon={trash} />}>Delete</DropdownItem>
  </DropdownPopup>
</DropdownRoot>

href and Server Action items work from a server page; onSelect is a plain function and needs a client file.

Every state

4 states

Import from the package

topclassui/dropdown
import { DropdownRoot, DropdownGroup, DropdownGroupLabel, DropdownItem, DropdownLinkItem, DropdownMenu, DropdownPopup, DropdownSeparator, DropdownTrigger } from 'topclassui/dropdown'

Install once:npm i topclassui@beta zod

Examples

  • For a short list of actions or links behind one button. Anything that is not a row goes in a Popover.
  • Flat parts and no bare Dropdown: DropdownRoot, DropdownTrigger, DropdownPopup, DropdownItem, DropdownLinkItem, DropdownGroup, DropdownGroupLabel, DropdownSeparator.
  • DropdownMenu builds the same menu from items: each is an href link, a Server Action action, or an onSelect function. Pass your framework's Link as link.
  • A row takes icon before the label and shortcut after it; a long label ends in an ellipsis.
  • The menu is as wide as its longest row, 192 to 320 px, and shows about seven rows before it scrolls inside itself.
  • Every row is mounted: keep a menu to tens of rows.
basic
groups
danger-item
with-links
data-driven
placements
sixty-items
in-dialog
In Arabic, right to left

Props

DropdownRoot

The props of DropdownRoot
PropTypeDefaultLimitsDescription
childrenReactNode--The trigger and the popup. With no trigger, hold open in a client file.

DropdownGroup

No props of its own: it takes the attributes of the element it renders.

DropdownGroupLabel

No props of its own: it takes the attributes of the element it renders.

DropdownItem

The props of DropdownItem
PropTypeDefaultLimitsDescription
aria-labelstring--
childrenReactNode--
iconReactNode--Drawn before the label. An element, such as <Svg icon={trash} />: it is not a name.
shortcutstring--Shown at the end of the row, such as Ctrl P. It is read out after the label.
toneneutral | danger"neutral"-DropdownItem, DropdownLinkItem: danger colours a destructive row; its label still has to say so.

DropdownLinkItem

The props of DropdownLinkItem
PropTypeDefaultLimitsDescription
aria-labelstring--
childrenReactNode--
iconReactNode--Drawn before the label. An element, such as <Svg icon={trash} />: it is not a name.
shortcutstring--Shown at the end of the row, such as Ctrl P. It is read out after the label.
toneneutral | danger"neutral"-DropdownItem, DropdownLinkItem: danger colours a destructive row; its label still has to say so.

DropdownMenu

The props of DropdownMenu
PropTypeDefaultLimitsDescription
items*readonly MenuItem[]--
title*string--The name of the menu, which labels its group of items.
trigger*ReactElement--The element that opens the menu, such as a Button.
defaultOpenboolean--
linkReactElement--The element every href item is cloned from, such as link={<Link href="" />}; its href is replaced with the item's.
onOpenChange((open: boolean) => void)--Client only, like onSelect.
openboolean--
placementtop | top-start | top-end | bottom | bottom-start | bottom-end | start | start-top | start-bottom | end | end-top | end-bottom"bottom-start"-DropdownPopup, DropdownMenu: the side and alignment it opens at; start and end mirror in Arabic.

DropdownPopup

The props of DropdownPopup
PropTypeDefaultLimitsDescription
placementtop | top-start | top-end | bottom | bottom-start | bottom-end | start | start-top | start-bottom | end | end-top | end-bottom"bottom-start"-DropdownPopup, DropdownMenu: the side and alignment it opens at; start and end mirror in Arabic.

DropdownSeparator

No props of its own: it takes the attributes of the element it renders.

DropdownTrigger

The props of DropdownTrigger
PropTypeDefaultLimitsDescription
closeDelaynumber, min 0, max 100000min 0, max 10000DropdownTrigger, with openOnHover: milliseconds it stays open after the pointer leaves, clamped.
delaynumber, min 0, max 10000100min 0, max 10000DropdownTrigger, with openOnHover: milliseconds of hover before it opens, clamped.

Keyboard and accessibility

The keyboard of Dropdown
KeysWhat it does
EnterSpaceArrowDownOn the trigger, opens the menu on the first row.
ArrowUpOn the trigger, opens the menu on the last row.
ArrowDownArrowUpMove between rows, and loop at both ends.
HomeEndJump to the first or the last row.
A-ZA letter goes to the row that starts with it.
EnterSpaceRuns the row and closes the menu.
EscapeCloses the menu. Focus returns to the trigger.
TabCloses the menu and goes on to the next control of the page.
  • tone="danger" only colours the row: write what it does in the label, add an icon, and put a separator above it.
  • A row with an icon alone needs aria-label, and the types ask for it.
  • A disabled row is announced as unavailable, and the arrow keys still stop on it.

Right to left

bottom-start lines up the right edges, the icon sits at the right and the shortcut at the left. The menu takes its direction from the nearest TopClassProvider.

Server and client

A client component: it renders on the server and hydrates in the browser.

Files: index.tsx, client.tsx.

On the chip: client - size not measured yet.