Skip to main content
Reakit
DocumentationNewsletter
Spectrum
GitHub
GitHub

Menu

Accessible Menu component that follows the WAI-ARIA Menu or Menu bar Pattern. It also includes a MenuDisclosure component that follows the WAI-ARIA Menu Button Pattern.

#Installation

npm install reakit

Learn more in Get started.

#Usage

import {
  useMenuState,
  Menu,
  MenuItem,
  MenuDisclosure,
  MenuSeparator
} from "reakit/Menu";

function Example() {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure {...menu}>Preferences</MenuDisclosure>
      <Menu {...menu} aria-label="Preferences">
        <MenuItem {...menu}>Settings</MenuItem>
        <MenuItem {...menu} disabled>
          Extensions
        </MenuItem>
        <MenuSeparator {...menu} />
        <MenuItem {...menu}>Keyboard shortcuts</MenuItem>
      </Menu>
    </>
  );
}

You can use either onClick or href props on MenuItem to define menu actions.

import {
  useMenuState,
  Menu,
  MenuItem,
  MenuDisclosure,
  MenuSeparator
} from "reakit/Menu";

function Example() {
  const menu = useMenuState();

  return (
    <>
      <MenuDisclosure {...menu}>Menu</MenuDisclosure>
      <Menu {...menu} aria-label="Example">
        <MenuItem
          {...menu}
          onClick={() => {
            menu.hide();
            console.log("clicked on button");
          }}
        >
          Button
        </MenuItem>
        <MenuItem {...menu} as="a" href="#" onClick={menu.hide}>
          Link
        </MenuItem>
      </Menu>
    </>
  );
}

#Initial focus

When opening Menu, focus is usually set on the first MenuItem. You can set the initial focus to be the menu container itself by just passing tabIndex={0} to it. This will be ignored if the menu is opened by using arrow keys.

import { useMenuState, Menu, MenuItem, MenuDisclosure } from "reakit/Menu";

function Example() {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure {...menu}>Preferences</MenuDisclosure>
      <Menu {...menu} tabIndex={0} aria-label="Preferences">
        <MenuItem {...menu}>Settings</MenuItem>
        <MenuItem {...menu}>Extensions</MenuItem>
        <MenuItem {...menu}>Keyboard shortcuts</MenuItem>
      </Menu>
    </>
  );
}

Menu can be used independently or nested within another one.

import React from "react";
import {
  useMenuState,
  Menu,
  MenuItem,
  MenuDisclosure,
  MenuSeparator
} from "reakit/Menu";

const PreferencesMenu = React.forwardRef((props, ref) => {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure ref={ref} {...menu} {...props}>
        Preferences
      </MenuDisclosure>
      <Menu {...menu} aria-label="Preferences">
        <MenuItem {...menu}>Settings</MenuItem>
        <MenuItem {...menu} disabled>
          Extensions
        </MenuItem>
        <MenuSeparator {...menu} />
        <MenuItem {...menu}>Keyboard shortcuts</MenuItem>
      </Menu>
    </>
  );
});

function Example() {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure {...menu}>Code</MenuDisclosure>
      <Menu {...menu} aria-label="Code">
        <MenuItem {...menu}>About Visual Studio Code</MenuItem>
        <MenuItem {...menu}>Check for Updates...</MenuItem>
        <MenuSeparator {...menu} />
        <MenuItem {...menu} as={PreferencesMenu} />
      </Menu>
    </>
  );
}

Reakit is built with composition in mind! You can compose any other component with Menu. You can nest Dialogs inside it by using the same approach described on Submenu.

import React from "react";
import { Button } from "reakit/Button";
import {
  useDialogState,
  Dialog,
  DialogDisclosure,
  DialogBackdrop
} from "reakit/Dialog";
import {
  useMenuState,
  Menu,
  MenuItem,
  MenuDisclosure,
  MenuSeparator
} from "reakit/Menu";

const UpdatesDialog = React.forwardRef((props, ref) => {
  const dialog = useDialogState();
  return (
    <>
      <DialogDisclosure ref={ref} {...dialog} {...props}>
        Check for Updates...
      </DialogDisclosure>
      <Dialog {...dialog} aria-label="Check for Updates">
        <p>There are currently no updates available.</p>
        <Button onClick={dialog.hide}>OK</Button>
      </Dialog>
    </>
  );
});

function Example() {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure {...menu}>Code</MenuDisclosure>
      <Menu {...menu} aria-label="Code">
        <MenuItem {...menu}>About Visual Studio Code</MenuItem>
        <MenuItem {...menu} as={UpdatesDialog} />
        <MenuSeparator {...menu} />
        <MenuItem {...menu}>Preferences</MenuItem>
      </Menu>
    </>
  );
}

You can combine multiple Menus to compose a MenuBar by using the same approach described on Submenu. Each Menu can be used separately or in combination with others.

import React from "react";
import {
  useMenuState,
  useMenuBarState,
  Menu,
  MenuDisclosure,
  MenuItem,
  MenuSeparator,
  MenuBar,
  MenuGroup,
  MenuItemCheckbox,
  MenuItemRadio
} from "reakit/Menu";

// OPEN RECENT
const OpenRecentMenu = React.forwardRef((props, ref) => {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure ref={ref} {...menu} {...props}>
        Open Recent
      </MenuDisclosure>
      <Menu {...menu} aria-label="Open Recent">
        <MenuItem {...menu}>Reopen Closed Editor</MenuItem>
        <MenuSeparator {...menu} />
        <MenuItem {...menu}>More...</MenuItem>
        <MenuSeparator {...menu} />
        <MenuItem {...menu}>Clear Recently Opened</MenuItem>
      </Menu>
    </>
  );
});

// FILE
const FileMenu = React.forwardRef((props, ref) => {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure ref={ref} {...menu} {...props}>
        File
      </MenuDisclosure>
      <Menu {...menu} aria-label="File">
        <MenuItem {...menu}>New File</MenuItem>
        <MenuItem {...menu}>New Window</MenuItem>
        <MenuSeparator {...menu} />
        <MenuItem {...menu}>Open...</MenuItem>
        <MenuItem {...menu}>Open Workspace...</MenuItem>
        <MenuItem {...menu} as={OpenRecentMenu} />
      </Menu>
    </>
  );
});

// EDIT
const EditMenu = React.forwardRef((props, ref) => {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure ref={ref} {...menu} {...props}>
        Edit
      </MenuDisclosure>
      <Menu {...menu} aria-label="Edit">
        <MenuItem {...menu}>Undo</MenuItem>
        <MenuItem {...menu}>Redo</MenuItem>
        <MenuSeparator {...menu} />
        <MenuItem {...menu}>Cut</MenuItem>
        <MenuItem {...menu}>Copy</MenuItem>
        <MenuItem {...menu}>Paste</MenuItem>
      </Menu>
    </>
  );
});

// VIEW
const ViewMenu = React.forwardRef((props, ref) => {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure ref={ref} {...menu} {...props}>
        View
      </MenuDisclosure>
      <Menu {...menu} aria-label="View">
        <MenuGroup {...menu}>
          <MenuItemRadio {...menu} name="windows" value="explorer">
            Explorer
          </MenuItemRadio>
          <MenuItemRadio {...menu} name="windows" value="search">
            Search
          </MenuItemRadio>
          <MenuItemRadio {...menu} name="windows" value="debug">
            Debug
          </MenuItemRadio>
          <MenuItemRadio {...menu} name="windows" value="extensions">
            Extensions
          </MenuItemRadio>
        </MenuGroup>
        <MenuSeparator {...menu} />
        <MenuItemCheckbox {...menu} name="toggles" value="word-wrap">
          Toggle Word Wrap
        </MenuItemCheckbox>
        <MenuItemCheckbox {...menu} name="toggles" value="minimap">
          Toggle Minimap
        </MenuItemCheckbox>
        <MenuItemCheckbox {...menu} name="toggles" value="breadcrumbs">
          Toggle Breadcrumbs
        </MenuItemCheckbox>
      </Menu>
    </>
  );
});

function Example() {
  const menu = useMenuBarState({ orientation: "horizontal" });
  return (
    <MenuBar {...menu}>
      <MenuItem {...menu} as={FileMenu} />
      <MenuItem {...menu} as={EditMenu} />
      <MenuItem {...menu} as={ViewMenu} />
    </MenuBar>
  );
}

#Abstracting

You can build your own Menu component with a different API on top of Reakit.

import React from "react";
import {
  useMenuState,
  Menu as BaseMenu,
  MenuItem,
  MenuDisclosure
} from "reakit/Menu";

function Menu({ disclosure, items, ...props }) {
  const menu = useMenuState();
  return (
    <>
      <MenuDisclosure {...menu}>
        {disclosureProps =>
          React.cloneElement(React.Children.only(disclosure), disclosureProps)
        }
      </MenuDisclosure>
      <BaseMenu {...menu} {...props}>
        {items.map((item, i) => (
          <MenuItem {...menu} key={i}>
            {itemProps =>
              React.cloneElement(React.Children.only(item), itemProps)
            }
          </MenuItem>
        ))}
      </BaseMenu>
    </>
  );
}

function Example() {
  return (
    <Menu
      aria-label="Custom menu"
      disclosure={<button>Custom menu</button>}
      items={[
        <button>Custom item 1</button>,
        <button>Custom item 2</button>,
        <button>Custom item 3</button>
      ]}
    />
  );
}

#Accessibility

  • MenuBar and Menu have either role menu or menubar depending on the value of the orientation option (when it's horizontal it becomes menubar).
  • MenuDisclosure extends the accessibility features of PopoverDisclosure, which means it sets aria-haspopup and aria-expanded attributes accordingly.
  • MenuItem has role menuitem.
  • MenuItem extends the accessibility features of Rover, which means it uses the roving tabindex method to manage focus.
  • MenuItemCheckbox has role menuitemcheckbox.
  • MenuItemRadio has role menuitemradio.
  • Pressing Enter on MenuDisclosure opens its menu (or submenu) and places focus on its first item.
  • Pressing Space on MenuItemCheckbox changes the state without closing Menu.
  • Pressing Space on a MenuItemRadio that is not checked, without closing Menu, checks the focused MenuItemRadio and unchecks any other checked MenuItemRadio in the same group.
  • Pressing any key that corresponds to a printable character moves focus to the next MenuItem in the current Menu or MenuBar whose label begins with that printable character.

Learn more in Accessibility.

#Composition

Learn more in Composition.

#Props

#useMenuBarState

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the rover list.

  • currentId string | null

    The current focused element ID.

  • loop boolean

    If enabled:

    • Jumps to the first item when moving next from the last item.
    • Jumps to the last item when moving previous from the first item.
  • unstable_values { [x: string]: any; }

    Stores the values of radios and checkboxes within the menu.

#useMenuState

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the rover list.

  • currentId string | null

    The current focused element ID.

  • loop boolean

    If enabled:

    • Jumps to the first item when moving next from the last item.
    • Jumps to the last item when moving previous from the first item.
  • unstable_values { [x: string]: any; }

    Stores the values of radios and checkboxes within the menu.

  • visible boolean

    Whether it's visible or not.

  • unstable_animated number | boolean

    If true, animating will be set to true when visible changes. It'll wait for stopAnimation to be called or a CSS transition ends. If it's a number, stopAnimation will be called automatically after given milliseconds.

  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

  • unstable_fixed boolean | undefined

    Whether or not the popover should have position set to fixed.

  • unstable_flip boolean | undefined

    Flip the popover's placement when it starts to overlap its reference element.

  • unstable_shift boolean | undefined

    Shift popover on the start or end of its reference element.

  • unstable_gutter number | undefined

    Offset between the reference and the popover.

  • unstable_preventOverflow boolean | undefined

    Prevents popover from being positioned outside the boundary.

  • unstable_boundariesElement "scrollParent" | "viewport" | "window" | undefined

    Boundaries element used by preventOverflow.

  • modal boolean | undefined

    Toggles Dialog's modal state.

    • Non-modal: preventBodyScroll doesn't work and focus is free.
    • Modal: preventBodyScroll is automatically enabled, focus is trapped within the dialog and the dialog is rendered within a Portal by default.
  • hideOnClickOutside boolean | undefined

    When enabled, user can hide the dialog by clicking outside it.

  • preventBodyScroll boolean | undefined

    When enabled, user can't scroll on body when the dialog is visible. This option doesn't work if the dialog isn't modal.

  • unstable_initialFocusRef RefObject<HTMLElement> | undefined

    The element that will be focused when the dialog shows. When not set, the first tabbable element within the dialog will be used.

  • unstable_finalFocusRef RefObject<HTMLElement> | undefined

    The element that will be focused when the dialog hides. When not set, the disclosure component will be used.

  • unstable_portal boolean | undefined

    Whether or not the dialog should be rendered within Portal. It's true by default if modal is true.

  • unstable_orphan boolean | undefined

    Whether or not the dialog should be a child of its parent. Opening a nested orphan dialog will close its parent dialog if hideOnClickOutside is set to true on the parent. It will be set to false if modal is false.

12 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • visible boolean

    Whether it's visible or not.

  • unstable_animated number | boolean

    If true, animating will be set to true when visible changes. It'll wait for stopAnimation to be called or a CSS transition ends. If it's a number, stopAnimation will be called automatically after given milliseconds.

  • unstable_stopAnimation () => void

    Stops animation. It's called automatically if there's a CSS transition. It's called after given milliseconds if animated is a number.

  • hide () => void

    Changes the visible state to false

  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

  • first () => void

    Moves focus to the first element.

  • last () => void

    Moves focus to the last element.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the rover list.

  • stops Stop[]

    A list of element refs and IDs of the roving items.

  • move (id: string | null) => void

    Moves focus to a given element ID.

  • next () => void

    Moves focus to the next element.

  • previous () => void

    Moves focus to the previous element.

  • size string | number | undefined

    Arrow's size

1 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

5 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the rover list.

  • stops Stop[]

    A list of element refs and IDs of the roving items.

  • move (id: string | null) => void

    Moves focus to a given element ID.

  • next () => void

    Moves focus to the next element.

  • previous () => void

    Moves focus to the previous element.

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

8 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • visible boolean

    Whether it's visible or not.

  • toggle () => void

    Toggles the visible state

  • unstable_referenceRef RefObject<HTMLElement | null>

    The reference element.

  • hide () => void

    Changes the visible state to false

  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

  • first () => void

    Moves focus to the first element.

  • last () => void

    Moves focus to the last element.

  • show () => void

    Changes the visible state to true

No props to show

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • stopId string | undefined

    Element ID.

14 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the rover list.

  • unstable_moves number

    Stores the number of moves that have been made by calling move, next, previous, first or last.

  • currentId string | null

    The current focused element ID.

  • first () => void

    Moves focus to the first element.

  • last () => void

    Moves focus to the last element.

  • stops Stop[]

    A list of element refs and IDs of the roving items.

  • move (id: string | null) => void

    Moves focus to a given element ID.

  • next () => void

    Moves focus to the next element.

  • previous () => void

    Moves focus to the previous element.

  • register (id: string, ref: RefObject<HTMLElement>) => void

    Registers the element ID and ref in the roving tab index list.

  • unregister (id: string) => void

    Unregisters the roving item.

  • visible boolean

    Whether it's visible or not.

  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

  • hide () => void

    Changes the visible state to false

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • value any

    Checkbox's value is going to be used when multiple checkboxes share the same state. Checking a checkbox with value will add it to the state array.

  • checked boolean | undefined

    Checkbox's checked state. If present, it's used instead of state.

  • stopId string | undefined

    Element ID.

  • name string

    MenuItemCheckbox's name as in menu.values.

18 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • state boolean | any[] | "indeterminate"

    Stores the state of the checkbox. If checkboxes that share this state have defined a value prop, it's going to be an array.

  • setState (value: SetStateAction<boolean | any[] | "indet...

    Sets state.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the rover list.

  • unstable_moves number

    Stores the number of moves that have been made by calling move, next, previous, first or last.

  • currentId string | null

    The current focused element ID.

  • first () => void

    Moves focus to the first element.

  • last () => void

    Moves focus to the last element.

  • stops Stop[]

    A list of element refs and IDs of the roving items.

  • move (id: string | null) => void

    Moves focus to a given element ID.

  • next () => void

    Moves focus to the next element.

  • previous () => void

    Moves focus to the previous element.

  • register (id: string, ref: RefObject<HTMLElement>) => void

    Registers the element ID and ref in the roving tab index list.

  • unregister (id: string) => void

    Unregisters the roving item.

  • visible boolean

    Whether it's visible or not.

  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

  • hide () => void

    Changes the visible state to false

  • unstable_values { [x: string]: any; }

    Stores the values of radios and checkboxes within the menu.

  • unstable_update (name: string, value?: any) => void

    Updates checkboxes and radios values within the menu.

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • stopId string | undefined

    Element ID.

  • value any

    Same as the value attribute.

  • checked boolean | undefined

    Same as the checked attribute.

  • name string

    MenuItemRadio's name as in menu.values.

18 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the rover list.

  • currentId string | null

    The current focused element ID.

  • first () => void

    Moves focus to the first element.

  • last () => void

    Moves focus to the last element.

  • stops Stop[]

    A list of element refs and IDs of the roving items.

  • move (id: string | null) => void

    Moves focus to a given element ID.

  • next () => void

    Moves focus to the next element.

  • previous () => void

    Moves focus to the previous element.

  • unstable_moves number

    Stores the number of moves that have been made by calling move, next, previous, first or last.

  • register (id: string, ref: RefObject<HTMLElement>) => void

    Registers the element ID and ref in the roving tab index list.

  • unregister (id: string) => void

    Unregisters the roving item.

  • state any

    The value attribute of the current checked radio.

  • setState (value: any) => void

    Sets state.

  • visible boolean

    Whether it's visible or not.

  • placement "auto-start" | "auto" | "auto-end" | "top-start...

    Actual placement.

  • hide () => void

    Changes the visible state to false

  • unstable_values { [x: string]: any; }

    Stores the values of radios and checkboxes within the menu.

  • unstable_update (name: string, value?: any) => void

    Updates checkboxes and radios values within the menu.

1 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • orientation "horizontal" | "vertical" | undefined

    Separator's orientation.