MenuTrigger and Menu
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.
The Menu allow users to choose from a list of options which can change based
on the content. Menus are used to display transient content such as options,
additional actions, and more. They stand out visually through stroke and drop
shadow and float on top of the interface.
| install | yarn add @react-spectrum/menu |
|---|---|
| version | 3.0.0-alpha.1 |
| usage | import {Menu, MenuTrigger} from '@react-spectrum/menu' |
Menu Example#
<Menu width="200px">
<Item>Cut</Item>
<Item>Copy</Item>
<Item>Paste</Item>
</Menu>Content#
The Menu accepts Items and Sections as children. Items can be
statically populated (initial example above) or dynamically (below). The dynamic
method would be better suited to use if the actions within a Menu came from a
data object such as values returned from an API call. The uniqueKey prop needs
to be set on an Item when statically defining Items and the itemKey prop in
the Menu when its Items are dynamically populated.
<Menu
items=[{name: 'Cut'} {name: 'Copy'} {name: 'Paste'}]
itemKey="name"
width="200px">
item => <Item>itemname</Item>
</Menu>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.
<MenuTrigger>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item>Cut</Item>
<Item>Copy</Item>
<Item>Paste</Item>
</Menu>
</MenuTrigger>If the Menu is open within a MenuTrigger it will close on blur or scroll events.
Selection#
The Menu's selected Item key is propagated as the selection via the event onAction.
The defaultSelectedKeys prop can be used to preselect Menu Items in the Menu
placing selection of a Menu in an uncontrolled state. Alternatively, the
selectedKeys prop preselects an Item in the Menu placing Item selection in a
controlled state.
<div>Edit (Controlled)</div>
<Menu
selectionMode="single"
selectedKeys=['copy']
width="200px">
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
<div>Edit (Uncontrolled)</div>
<Menu
selectionMode="single"
defaultSelectedKeys=['paste']
width="200px">
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>The selectionMode prop specifies how many Menu Items can be selected, with
the options of a single Menu Item, multiple Menu Items, or disabling selection
entirely (default).
<div>Show (multiple)</div>
<Menu
selectionMode="multiple"
defaultSelectedKeys=['Sidebar' 'Console']
width="200px">
<Item uniqueKey='Sidebar'>Sidebar</Item>
<Item uniqueKey='Searchbar'>Searchbar</Item>
<Item uniqueKey='Tools'>Tools</Item>
<Item uniqueKey='Console'>Console</Item>
</Menu>
<div>Selection Mode None</div>
<Menu
selectionMode="none"
width="200px">
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>Sections#
Menus may have Sections which can be used to wrap groups of Items. Each
Section takes a title and uniqueKey prop.
Static Items
<div>Edit</div>
<Menu width="200px">
<Section uniqueKey="rollback" title="Rollback Options">
<Item uniqueKey="undo">Undo</Item>
<Item uniqueKey="redo">Redo</Item>
</Section>
<Section uniqueKey="select" title="Selected Text Options">
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Section>
</Menu>Dynamic Items
Sections should be populated with dynamic Items from a hierarchical data
structure. Section takes an array of data using the items prop.
<div>File Types</div>
<Menu
items=[{name: 'Docs' children: [{name: 'PDF'}]} {name: 'Images' children: [{name: 'jpeg'} {name: 'png'} {name: 'tiff'}]}]
itemKey="name"
width="200px">
item => (
<Section items=itemchildren title=itemname>
item => <Item>itemname</Item>
</Section>
)
</Menu>Sematic Elements#
A Menu Item's content may be any renderable node, not just strings.
import {Keyboard Text} from '@react-spectrum/typography';
<MenuTrigger>
<ActionButton>
Edit
</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[itemicon];
return (
<Item childItems=itemchildren textValue=itemname>
<Icon size="S" />
<Text>itemname</Text>
<Keyboard>itemshortcut</Keyboard>
</Item>
);
}
</Menu>
</MenuTrigger>Internationalization#
To internationalize a Menu, a localized string should be passed to the children of each Menu Item.
For languages that are read right to left (e.g. Hebrew and Arabic), the layout of the Menu is flipped.
Accessibility#
Titleless Menu Sections must be provided with an aria-label for accessibility.
<MenuTrigger>
<ActionButton>
Edit
</ActionButton>
<Menu items=[{name: 'Rollback Options' children: [{name: 'Undo'} {name: 'Redo'}]} {name: 'Selected Text Options' children: [{name: 'Cut'} {name: 'Copy'} {name: 'Paste'}]}] itemKey="name">
item => (
<Section items=itemchildren aria-label=itemname>
item => <Item>itemname</Item>
</Section>
)
</Menu>
</MenuTrigger>Events#
Menu supports selection via mouse, keyboard, and touch.
onOpenChange#
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] = ReactuseState(false);
return (
<div>
<MenuTrigger onOpenChange=(isOpen) => setState(isOpen)>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>
<span style={'margin-left': '8px'}>Current open state: statetoString()</span>
</div>
);
}onAction#
Menu accepts an onAction handler which is triggered whenever a Menu Item is selected.
The example below uses the onAction to update text above with Menu with the last selected Item.
function Example() {
let [state setState] = ReactuseState(false);
return (
<div>
<div>Edit (onAction: statetoString())</div>
<Menu
onAction=(value) => setState(value)
width="200px">
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</div>
);
}Props#
MenuTrigger#
| Name | Type | Default | Description |
children | ReactElement[] | — | The contents of the MenuTrigger, a trigger and a Menu. See the MenuTrigger Content section for more information on what to provide as children. |
align | Alignment | — | Where the Menu aligns with its trigger. |
direction | 'bottom' | 'top' | — | Where the Menu opens relative to its trigger. |
closeOnSelect | boolean | — | Whether the Menu closes when a selection is made. |
isOpen | boolean | — | Whether the Menu loads open (controlled). |
defaultOpen | boolean | — | Whether the Menu loads open (uncontrolled). |
shouldFlip | boolean | — | Whether the element should flip its orientation when there is insufficient space for it to render within the view. |
Events
| Name | Type | Default | Description |
onOpenChange | (isOpen: boolean) => void | — | Handler that is called when the Menu opens or closes. |
Menu#
| Name | Type | Default | Description |
autoFocus | boolean | FocusStrategy | — | Where the focus should be set. |
shouldFocusWrap | boolean | — | Whether keyboard navigation is circular. |
children | ReactElement<SectionProps<T>> | ReactElement<ItemProps<T>> | ReactElement<SectionProps<T>> | ReactElement<ItemProps<T>>[] | (item: T) => ReactElement<SectionProps<T>> | ReactElement<ItemProps<T>> | — | The contents of the collection. |
disabledKeys | Iterable<Key> | — | They item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with. |
items | Iterable<T> | — | Item objects in the collection or section. |
itemKey | string | — | Property name on each item object to use as the unique key. id or key by default. |
isLoading | boolean | — | Whether the items are currently loading. |
selectionMode | SelectionMode | — | The type of selection that is allowed in the collection. |
disallowEmptySelection | boolean | — | Whether the collection allows empty selection. |
selectedKeys | Iterable<Key> | — | The currently selected keys in the collection (controlled). |
defaultSelectedKeys | Iterable<Key> | — | The initial selected keys in the collection (uncontrolled). |
UNSAFE_className | string | — | |
UNSAFE_style | CSSProperties | — |
Events
| Name | Type | Default | Description |
onAction | (key: Key) => void | — | Handler that is called when an item is selected. |
onLoadMore | () => any | — | Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. |
onSelectionChange | (keys: Set<Key>) => any | — | Handler that is called when the selection changes. |
Layout
| Name | Type | Default | Description |
flex | string | number | boolean | — | |
flexGrow | number | — | |
flexShrink | number | — | |
flexBasis | number | string | — | |
alignSelf | 'auto'
| 'normal'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'center'
| 'stretch' | — | |
justifySelf | 'auto'
| 'normal'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'center'
| 'left'
| 'right'
| 'stretch' | — | |
flexOrder | number | — | |
gridArea | string | — | |
gridColumn | string | — | |
gridRow | string | — | |
gridColumnStart | string | — | |
gridColumnEnd | string | — | |
gridRowStart | string | — | |
gridRowEnd | string | — |
Spacing
| Name | Type | Default | Description |
margin | DimensionValue | — | |
marginTop | DimensionValue | — | |
marginLeft | DimensionValue | — | |
marginRight | DimensionValue | — | |
marginBottom | DimensionValue | — | |
marginStart | DimensionValue | — | |
marginEnd | DimensionValue | — | |
marginX | DimensionValue | — | |
marginY | DimensionValue | — |
Sizing
| Name | Type | Default | Description |
width | DimensionValue | — | |
minWidth | DimensionValue | — | |
maxWidth | DimensionValue | — | |
height | DimensionValue | — | |
minHeight | DimensionValue | — | |
maxHeight | DimensionValue | — |
Positioning
| Name | Type | Default | Description |
position | 'static'
| 'relative'
| 'absolute'
| 'fixed'
| 'sticky' | — | |
top | DimensionValue | — | |
bottom | DimensionValue | — | |
left | DimensionValue | — | |
right | DimensionValue | — | |
start | DimensionValue | — | |
end | DimensionValue | — | |
zIndex | number | — | |
isHidden | boolean | — |
Accessibility
| Name | Type | Default | Description |
role | string | — | |
id | string | — | |
tabIndex | number | — | |
aria-label | string | — | Defines a string value that labels the current element. |
aria-labelledby | string | — | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | — | Identifies the element (or elements) that describes the object. |
aria-controls | string | — | Identifies the element (or elements) whose contents or presence are controlled by the current element. |
aria-owns | string | — | Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship between DOM elements where the DOM hierarchy cannot be used to represent the relationship. |
aria-hidden | boolean | 'false' | 'true' | — | Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable. |
Behavioral Options#
Align (MenuTrigger)#
<MenuTrigger align="start">
<ActionButton>
placement align=start
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger align="end">
<ActionButton>
placement align=end
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>Direction (MenuTrigger)#
<MenuTrigger direction="bottom" shouldFlip=false>
<ActionButton>
placement direction=bottom
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger direction="top" shouldFlip=false>
<ActionButton>
placement direction=top
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>Autofocus (Menu)#
Applying autoFocus to the Menu of the MenuTrigger sets focus to a Menu Item
within the Menu upon opening.
These examples demonstrate how to use autoFocus to set whether or not
the selected Menu Item should be automatically focused when the Menu is opened.
<MenuTrigger>
<ActionButton>
autofocus
</ActionButton>
<Menu
selectionMode="single"
selectedKeys=['copy']
autoFocus>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger>
<ActionButton>
autofocus=false
</ActionButton>
<Menu
selectionMode="single"
selectedKeys=['copy']
autoFocus=false>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>These examples demonstrate how to use autoFocus to set whether or not the
first Menu Item or last Menu Item is focused when the Menu is opened.
<MenuTrigger>
<ActionButton>
autofocus=first
</ActionButton>
<Menu autoFocus="first">
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger>
<ActionButton>
autofocus=last
</ActionButton>
<Menu autoFocus="last">
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>Closes on Selection (MenuTrigger)#
The closeOnSelect MenuTrigger prop closes the Menu when an MenuItem is
selected (default). Setting the closeOnSelect prop to false would be useful
for a Menu listing filtering options where the user would make multiple
selections at once.
<MenuTrigger closeOnSelect>
<ActionButton>
closeOnSelect=true
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger closeOnSelect=false>
<ActionButton>
closeOnSelect=false
</ActionButton>
<Menu>
<Item uniqueKey="jpg">jpg</Item>
<Item uniqueKey="png">png</Item>
<Item uniqueKey="tiff">tiff</Item>
</Menu>
</MenuTrigger>Disabled Menu Items (Menu)#
<div>Filter by:</div>
<Menu
items=[
{name: 'tiff' dataId: 'a1b2c3'}
{name: 'png' dataId: 'g5h1j9'}
{name: 'jpg' dataId: 'p8k3i4'}
{name: 'PDF' dataId: 'j7i3a0'}
]
itemKey="dataId"
disabledKeys=['a1b2c3' 'p8k3i4']>
item => <Item>itemname</Item>
</Menu>Flipping (MenuTrigger)#
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>
shouldFlip=true
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger shouldFlip=false>
<ActionButton>
shouldFlip=false
</ActionButton>
<Menu>
<Item uniqueKey="cut">Cut</Item>
<Item uniqueKey="copy">Copy</Item>
<Item uniqueKey="paste">Paste</Item>
</Menu>
</MenuTrigger>Open (MenuTrigger)#
The isOpen and defaultOpen props control whether the MenuTrigger is open by default.
They apply controlled and uncontrolled behavior on the MenuTrigger respectively.