Persona

Dropdown Button

Actions · 19 props · Updated Sep 22, 2026

Dropdown Button composes two components you already have. The trigger is a Button, the menu is a Dropdown Menu, and this adds the caret between them and hands on every prop it does not name itself.

  • Button the trigger, and a full Button — variant, size and the rest come straight from it.
  • Caret icon the only thing in the closed state that promises a menu. It arrives as Button's suffixIcon, so passing your own replaces it.
  • Menu container (when open) the surface the actions sit on, anchored under the button.
  • Menu item (when open) one action. menuItems is required, and with none the button does not render either.

A dropdown button collects the actions on one object that cannot all have a place on the surface. The button names what they have in common; the menu holds the rest.

Use a plain Button for a single action, and a Dropdown Menu directly when the trigger is not a button — an avatar, a row’s overflow icon, a table cell. Use Select or Combobox when the items are values the reader is choosing between rather than actions they are running: a menu closes and something has happened, a select closes and something has been set.

A suffix icon replaces the caret rather than joining it

Section titled “A suffix icon replaces the caret rather than joining it”

The caret is a suffixIcon the component passes to Button, and your props are spread after it. So suffixIcon replaces the caret rather than joining it, and the button loses the only thing in its closed state that says a menu is coming. Put an icon on the left with prefixIcon instead.

variant and width arrive the same way: the component asks Button for basic and auto, and anything you pass wins. The variants and the rule about how many of them can be loud at once are on Button.

The menu renders in place, next to the button in the DOM, because Dropdown Menu does not portal by default. Any ancestor with hidden overflow — a card, a table cell, a scrolling panel — will cut the menu off at its edge. Pass menuProps={{ portal: true }} to move it to the end of the document instead. A menu that is drawn behind a neighbor rather than cut off by an ancestor is the other problem, and menuProps={{ zIndex }} is the lever for it.

side picks the edge the menu hangs from. It is aligned to the button’s end edge unless you pass align through menuProps.

menuItems is required, and an empty array takes the button with it — not a button that opens nothing, but nothing at all. The mechanism belongs to Dropdown Menu, which renders the trigger as one of its own children. Where the list is filtered or fetched, render a disabled Button instead of a Dropdown Button with nothing in it.

Everything but the button’s name is already wired

Section titled “Everything but the button’s name is already wired”

aria-haspopup, aria-expanded on the button, role="menu" on the surface and role="menuitem" on each item are all set for you, and they stay in step with the open state on their own. What is left to you is the button’s accessible name, which is its visible text.

Enter, Space and Down Arrow open the menu; Escape closes it and returns focus to the button. The menu is not modal, so the page behind it stays interactive and a click outside both closes the menu and reaches whatever was clicked.

Write the button label as the category the actions share

Section titled “Write the button label as the category the actions share”

“Actions” and “More” are honest when the items have nothing in common, but a button that can say “Export” or “Manage members” has told the reader whether to open it at all. Naming the mechanism instead — “Menu”, “Options” — describes what the caret already shows.

Wording for the items themselves is on Dropdown Menu, and the rules for every label in the product are on Writing.

NameType
menuItemsrequired

ReactNode

Use DropdownMenu.Item components to render the menu items.

menuProps

Partial<Omit<DropdownMenuProps, “trigger”>>

Additional props will be spread to the inner <DropdownMenu> component.

side

left, right, top, bottom

The side of the dropdown menu.

variant

basic, primary, inverse, danger, text, outline

Visual style of the button.

  • primary – filled brand colour, for the primary action on a surface.
  • basic – subtle grey fill, for secondary actions.
  • outline – transparent with a border, alternative secondary style.
  • text – no background or border, for tertiary / inline actions.
  • inverse – inverted fill for use on dark/coloured backgrounds.
  • danger – red fill, for destructive actions.
isPending

boolean

Shows a loading spinner and disables the button. Also triggers automatically when onClick returns a Promise, clearing once the promise settles.

prefixIcon

ReactNode

Icon rendered to the left of the button label. Import from @persona/icons.

prefixIconType

basic, inherit

Controls how the prefix icon inherits sizing.

  • basic – icon is rendered inside a fixed-size container.
  • inherit – icon stretches to fill the surrounding element’s dimensions.
suffixIcon

ReactNode

Icon rendered to the right of the button label. Import from @persona/icons.

size

md, sm

Height and typography scale of the button.

to

To

Changes the button into a link. Pass a relative path to use react-router-dom <Link>, or an absolute https:// URL to render an <a> tag that opens in a new tab.

target

HTMLAttributeAnchorTarget

Overrides the target of the link if to is provided and is an external link.

type

button, reset, submit

Note: we may want to default this to ‘button’ in the future, but many instances of this Button component in dashboard currently rely on the default ‘submit’ behavior that occurs when using this in redux-form so we’ll have to comb through and manually add the type=‘submit’ prop.

width

full, auto, button, icon

Controls the width of the button.

  • full – stretches to fill its container (width: 100%).
  • auto – shrinks to fit its content.
  • button – a fixed medium element width (from design tokens).
  • icon – square, sized to match the button height; use for icon-only buttons.
onClick

MaybePromise

onSecondaryBackground

boolean

In some cases we render a button on a secondary background, which needs an adjustment for certain variants.

hovered

boolean

Visual state override. Forces the :hover styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

focused

boolean

Visual state override. Forces the :focus styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

active

boolean

Visual state override. Forces the :focus-within styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

mixed

boolean

Visual state override. Forces the :indeterminate styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

Also accepts every prop of ComponentPropsWithoutRef<‘button’>.