MenuTrigger

The MenuTrigger serves as a wrapper around a Menu and its associated trigger, linking the Menu's open state with the trigger's press state.

installyarn add @react-spectrum/menu
version3.0.0-alpha.1
usageimport {MenuTrigger} from '@react-spectrum/menu'

Example#


<MenuTrigger>
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>

Content#


The MenuTrigger accepts exactly two children: the Menu and the element which triggers the opening of the Menu. The trigger must be the first child passed into the MenuTrigger and should be an element that supports press events.

If the Menu is open it will close on blur or scroll events.

The defaultSelectedKeys prop can be used to set the default state of the Menu (uncontrolled). Alternatively, the selectedKeys prop can be used to make the selected state of the Menu controlled.

<MenuTrigger>
  <ActionButton>
      Menu Button Controlled
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name" selectedKeys={['Kangaroo']}>
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>
<MenuTrigger>
  <ActionButton>
      Menu Button Uncontrolled
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name" defaultSelectedKeys={['Kangaroo']}>
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>

The selectionMode prop can be used to switch from the default selection of a single menu item to multiple menu items or none.

function Example() {
  let [state, setState] = React.useState(['Kangaroo', 'Snake']);

  let onAction = (value) => {
    if (state.includes(value)) {
      setState(state.filter((item) => item !== value));
    } else {
      let curState = state;
      curState.push(value);
      setState(curState);
    }
  };

  return (
    <div>
      <MenuTrigger>
        <ActionButton>
            Menu Button Multiple
        </ActionButton>
        <Menu
          items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]}
          itemKey="name"
          onAction={onAction}
          defaultSelectedKeys={state}
          selectionMode="multiple">
          {item => <Item>{item.name}</Item>}
        </Menu>
      </MenuTrigger>
      <MenuTrigger>
        <ActionButton>
            Menu Button None
        </ActionButton>
        <Menu
          items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]}
          itemKey="name"
          selectionMode="none">
          {item => <Item>{item.name}</Item>}
        </Menu>
      </MenuTrigger>
    </div>
  );
}

Sections#

Menu's may have Sections with titles and children items.

<MenuTrigger>
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Section 1', children: [{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]}, {name: 'Section 2', children: [{name: 'Koala'}]}]} itemKey="name">
    {item => (
      <Section items={item.children} title={item.name}>
        {item => <Item>{item.name}</Item>}
      </Section>
    )}
  </Menu>
</MenuTrigger>

Sematic Elements#

A Menu item's content may be any renderable node, not just strings.

View guidelines

<MenuTrigger>
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu
    itemKey="name"
    items={[
      {name: 'Copy', icon: 'Copy', shortcut: '⌘C'},
      {name: 'Cut', icon: 'Cut', shortcut: '⌘X'},
      {name: 'Paste', icon: 'Paste', shortcut: '⌘V'}
    ]}>
    {item => {
      let iconMap = {
        Copy,
        Cut,
        Paste
      };
      let Icon = iconMap[item.icon];
      return (
        <Item childItems={item.children} textValue={item.name}>
          <Icon size="S" />
          <Text>{item.name}</Text>
          <Keyboard>{item.shortcut}</Keyboard>
        </Item>
      );
    }}
  </Menu>
</MenuTrigger>

Accessibility#

The MenuTrigger uses the prop aria-controls to point at the menu, which uses the prop aria-labelledby to reference the trigger. Other aria props of the MenuTrigger are aria-haspopup and aria-expanded.

Menu sections without labels use the prop aria-label.

<MenuTrigger>
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Section 1', children: [{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]}, {name: 'Section 2', children: [{name: 'Koala'}]}]} itemKey="name">
    {item => (
      <Section items={item.children} aria-label={item.name}>
        {item => <Item>{item.name}</Item>}
      </Section>
    )}
  </Menu>
</MenuTrigger>

Events#


MenuTrigger accepts an onOpenChange handler which is triggered whenever the Menu is opened or closed.

The example below uses onOpenChange to update a separate span element with the current open state of the Menu.

function Example() {
  let [state, setState] = React.useState(false);

  return (
    <div>
      <MenuTrigger onOpenChange={(isOpen) => setState(isOpen)}>
        <ActionButton>
            Menu Button
        </ActionButton>
        <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
          {item => <Item>{item.name}</Item>}
        </Menu>
      </MenuTrigger>
      <span style={{'margin-left': '8px'}}>Current open state: {state.toString()}</span>
    </div>
  );
}

Menu accepts an onAction handler which is triggered whenever a Menu item is selected.

The example below uses onAction to tell the user which Menu item was selected.

function Example() {
  let onAction = (value) => alert('Menu item selected: ' + value);

  return (
    <div>
      <MenuTrigger>
        <ActionButton>
            Menu Button
        </ActionButton>
        <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name" onAction={onAction}>
          {item => <Item>{item.name}</Item>}
        </Menu>
      </MenuTrigger>
    </div>
  );
}

Props#


NameTypeDefaultDescription
childrenReactElement[]

The contents of the MenuTrigger, a trigger and a Menu. See the MenuTrigger Content section for more information on what to provide as children.

alignAlignmentWhere the Menu aligns with it's trigger.
direction'bottom''top'Where the Menu opens relative to it's trigger.
closeOnSelectbooleanWhether the Menu closes when a selection is made.
isOpenbooleanWhether the Menu loads open (controlled).
defaultOpenbooleanWhether the Menu loads open (uncontrolled).
shouldFlipboolean

Whether the element should flip its orientation when there is insufficient room for it to render completely.

Events
NameTypeDefaultDescription
onOpenChange(isOpen: boolean) => voidHandler that is called when the Menu opens or closes.
NameTypeDefaultDescription

Visual Options#


Align and Direction#

View guidelines

<MenuTrigger align="start" direction="bottom">
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>
<MenuTrigger align="start" direction="top">
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>
<MenuTrigger align="end" direction="bottom">
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>
<MenuTrigger align="end" direction="top">
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>

Autofocus#

Applying autoFocus to the Menu of the MenuTrigger sets focus to the Menu upon opening.

<MenuTrigger closeOnSelect={false}>
  <ActionButton>
      Menu Button Autofocus
  </ActionButton>
  <Menu
    items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]}
    itemKey="name"
    autoFocus>
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>
<MenuTrigger closeOnSelect={false}>
  <ActionButton>
      Menu Button Autofocus Last
  </ActionButton>
  <Menu
    items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]}
    itemKey="name"
    autoFocus="last">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>

Closes Menu onSelect#

<MenuTrigger closeOnSelect={false}>
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>

Disabled Trigger#

<MenuTrigger>
  <ActionButton isDisabled>
      Menu Button
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>

Disabled Menu Items#

<MenuTrigger>
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu
    items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]}
    itemKey="name"
    disabledKeys={['Aardvark', 'Snake']}>
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>
<MenuTrigger>
  <ActionButton>
      Menu Button
  </ActionButton>
  <Menu
    items={[{name: 'Aardvark', key: 1}, {name: 'Kangaroo', key: 2}, {name: 'Snake', key: 3}]}
    itemKey="name"
    disabledKeys={['1', '3']}>
    {item => <Item key={item.key}>{item.name}</Item>}
  </Menu>
</MenuTrigger>

Flipping#

Applying shouldFlip to the MenuTrigger makes the Menu attempt to flip on its main axis in situations where the original placement would cause it to render out of view.

<MenuTrigger shouldFlip>
  <ActionButton>
      Menu Button shouldFlip
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>
<MenuTrigger shouldFlip={false}>
  <ActionButton>
      Menu Button shouldFlip=false
  </ActionButton>
  <Menu items={[{name: 'Aardvark'}, {name: 'Kangaroo'}, {name: 'Snake'}]} itemKey="name">
    {item => <Item>{item.name}</Item>}
  </Menu>
</MenuTrigger>

Open#

The isOpen and defaultOpen props control whether the MenuTrigger is open by default. They apply controlled and uncontrolled behavior on the MenuTrigger respectively.