FAB menu
A FAB menu opens from a FAB to offer two to six related actions: reply, reply all and forward, say, where one FAB would force a choice. Use it only from a FAB, a medium FAB or a large FAB. Don't pair it with a floating toolbar or a navigation rail. See the M3 FAB menu guidelines.
M3 shows it two ways. In a compact window the FAB turns into a close button and the actions
rise above it as a list. On the web and in larger windows the FAB opens the baseline
menu. presentation: 'auto', the default, takes the list below 600px and the menu
from 600px; 'list' and 'menu' fix one.
Usage
ariaLabel names the menu the FAB opens, and every item has a label.
import { createFabMenu } from 'mtrl';
const fabMenu = createFabMenu({
icon: editIcon,
ariaLabel: 'Reply options',
presentation: 'list',
items: [
{ id: 'reply', text: 'Reply', icon: backIcon },
{ id: 'forward', text: 'Forward', icon: forwardIcon },
{ id: 'share', text: 'Share', icon: shareIcon },
],
});
document.body.append(fabMenu.element);
<m-fab-menu aria-label="Reply options" presentation="list">
<m-fab-menu-item value="reply">Reply</m-fab-menu-item>
<m-fab-menu-item value="forward">Forward</m-fab-menu-item>
<m-fab-menu-item value="share">Share</m-fab-menu-item>
</m-fab-menu>
<script type="module">
const fabMenu = document.querySelector('m-fab-menu');
fabMenu.setAttribute('icon', editIcon);
fabMenu.querySelector('m-fab-menu-item[value="reply"]').setAttribute('icon', backIcon);
fabMenu.querySelector('m-fab-menu-item[value="forward"]').setAttribute('icon', forwardIcon);
fabMenu.querySelector('m-fab-menu-item[value="share"]').setAttribute('icon', shareIcon);
</script>
import { FabMenu, FabMenuItem } from 'mtrl/react';
import { editIcon, backIcon, forwardIcon, shareIcon } from './app';
export function Example() {
return (
<FabMenu icon={editIcon} ariaLabel="Reply options" presentation="list">
<FabMenuItem value="reply" icon={backIcon}>Reply</FabMenuItem>
<FabMenuItem value="forward" icon={forwardIcon}>Forward</FabMenuItem>
<FabMenuItem value="share" icon={shareIcon}>Share</FabMenuItem>
</FabMenu>
);
}
<script setup lang="ts">
import { MFabMenu, MFabMenuItem } from 'mtrl/vue';
import { editIcon, backIcon, forwardIcon, shareIcon } from './app';
</script>
<template>
<MFabMenu :icon="editIcon" aria-label="Reply options" presentation="list">
<MFabMenuItem value="reply" :icon="backIcon">Reply</MFabMenuItem>
<MFabMenuItem value="forward" :icon="forwardIcon">Forward</MFabMenuItem>
<MFabMenuItem value="share" :icon="shareIcon">Share</MFabMenuItem>
</MFabMenu>
</template>
<script lang="ts">
import { FabMenu, FabMenuItem } from 'mtrl/svelte';
import { editIcon, backIcon, forwardIcon, shareIcon } from './app';
</script>
<FabMenu icon={editIcon} ariaLabel="Reply options" presentation="list">
<FabMenuItem value="reply" icon={backIcon}>Reply</FabMenuItem>
<FabMenuItem value="forward" icon={forwardIcon}>Forward</FabMenuItem>
<FabMenuItem value="share" icon={shareIcon}>Share</FabMenuItem>
</FabMenu>
import { FabMenu, FabMenuItem } from 'mtrl/solid';
import { editIcon, backIcon, forwardIcon, shareIcon } from './app';
export function Example() {
return (
<FabMenu icon={editIcon} ariaLabel="Reply options" presentation="list">
<FabMenuItem value="reply" icon={backIcon}>Reply</FabMenuItem>
<FabMenuItem value="forward" icon={forwardIcon}>Forward</FabMenuItem>
<FabMenuItem value="share" icon={shareIcon}>Share</FabMenuItem>
</FabMenu>
);
}
Examples
The baseline menu
presentation: 'menu' opens the baseline menu from the FAB, 4dp above it at its trailing
edge. The menu is loaded the first time it is needed, and ahead of that in a wide window or
when the FAB is first pointed at or focused, so a page that never opens it never loads it.
import { createFabMenu } from 'mtrl';
const fabMenu = createFabMenu({
icon: addIcon,
ariaLabel: 'Create',
presentation: 'menu',
items: [
{ id: 'doc', text: 'Document' },
{ id: 'sheet', text: 'Spreadsheet' },
{ id: 'slides', text: 'Presentation' },
],
});
document.body.append(fabMenu.element);
<m-fab-menu aria-label="Create" presentation="menu">
<m-fab-menu-item value="doc">Document</m-fab-menu-item>
<m-fab-menu-item value="sheet">Spreadsheet</m-fab-menu-item>
<m-fab-menu-item value="slides">Presentation</m-fab-menu-item>
</m-fab-menu>
<script type="module">
const fabMenu = document.querySelector('m-fab-menu');
fabMenu.setAttribute('icon', addIcon);
</script>
import { FabMenu, FabMenuItem } from 'mtrl/react';
import { addIcon } from './app';
export function Example() {
return (
<FabMenu icon={addIcon} ariaLabel="Create" presentation="menu">
<FabMenuItem value="doc">Document</FabMenuItem>
<FabMenuItem value="sheet">Spreadsheet</FabMenuItem>
<FabMenuItem value="slides">Presentation</FabMenuItem>
</FabMenu>
);
}
<script setup lang="ts">
import { MFabMenu, MFabMenuItem } from 'mtrl/vue';
import { addIcon } from './app';
</script>
<template>
<MFabMenu :icon="addIcon" aria-label="Create" presentation="menu">
<MFabMenuItem value="doc">Document</MFabMenuItem>
<MFabMenuItem value="sheet">Spreadsheet</MFabMenuItem>
<MFabMenuItem value="slides">Presentation</MFabMenuItem>
</MFabMenu>
</template>
<script lang="ts">
import { FabMenu, FabMenuItem } from 'mtrl/svelte';
import { addIcon } from './app';
</script>
<FabMenu icon={addIcon} ariaLabel="Create" presentation="menu">
<FabMenuItem value="doc">Document</FabMenuItem>
<FabMenuItem value="sheet">Spreadsheet</FabMenuItem>
<FabMenuItem value="slides">Presentation</FabMenuItem>
</FabMenu>
import { FabMenu, FabMenuItem } from 'mtrl/solid';
import { addIcon } from './app';
export function Example() {
return (
<FabMenu icon={addIcon} ariaLabel="Create" presentation="menu">
<FabMenuItem value="doc">Document</FabMenuItem>
<FabMenuItem value="sheet">Spreadsheet</FabMenuItem>
<FabMenuItem value="slides">Presentation</FabMenuItem>
</FabMenu>
);
}
Color and size
color is primary (the default), secondary or tertiary: the FAB takes the role's
container, the close button the role, and the items its container. Match it to the FAB it
replaces. size is default, medium or large; the close button is always 56dp.
import { createFabMenu } from 'mtrl';
const fabMenu = createFabMenu({
icon: editIcon,
ariaLabel: 'Edit options',
presentation: 'list',
color: 'tertiary',
size: 'medium',
items: [
{ id: 'copy', text: 'Copy', icon: copyIcon },
{ id: 'save', text: 'Save', icon: saveIcon },
],
});
document.body.append(fabMenu.element);
<m-fab-menu aria-label="Edit options" color="tertiary" size="medium" presentation="list">
<m-fab-menu-item value="copy">Copy</m-fab-menu-item>
<m-fab-menu-item value="save">Save</m-fab-menu-item>
</m-fab-menu>
<script type="module">
const fabMenu = document.querySelector('m-fab-menu');
fabMenu.setAttribute('icon', editIcon);
fabMenu.querySelector('m-fab-menu-item[value="copy"]').setAttribute('icon', copyIcon);
fabMenu.querySelector('m-fab-menu-item[value="save"]').setAttribute('icon', saveIcon);
</script>
import { FabMenu, FabMenuItem } from 'mtrl/react';
import { editIcon, copyIcon, saveIcon } from './app';
export function Example() {
return (
<FabMenu icon={editIcon} ariaLabel="Edit options" color="tertiary" size="medium" presentation="list">
<FabMenuItem value="copy" icon={copyIcon}>Copy</FabMenuItem>
<FabMenuItem value="save" icon={saveIcon}>Save</FabMenuItem>
</FabMenu>
);
}
<script setup lang="ts">
import { MFabMenu, MFabMenuItem } from 'mtrl/vue';
import { editIcon, copyIcon, saveIcon } from './app';
</script>
<template>
<MFabMenu :icon="editIcon" aria-label="Edit options" color="tertiary" size="medium" presentation="list">
<MFabMenuItem value="copy" :icon="copyIcon">Copy</MFabMenuItem>
<MFabMenuItem value="save" :icon="saveIcon">Save</MFabMenuItem>
</MFabMenu>
</template>
<script lang="ts">
import { FabMenu, FabMenuItem } from 'mtrl/svelte';
import { editIcon, copyIcon, saveIcon } from './app';
</script>
<FabMenu icon={editIcon} ariaLabel="Edit options" color="tertiary" size="medium" presentation="list">
<FabMenuItem value="copy" icon={copyIcon}>Copy</FabMenuItem>
<FabMenuItem value="save" icon={saveIcon}>Save</FabMenuItem>
</FabMenu>
import { FabMenu, FabMenuItem } from 'mtrl/solid';
import { editIcon, copyIcon, saveIcon } from './app';
export function Example() {
return (
<FabMenu icon={editIcon} ariaLabel="Edit options" color="tertiary" size="medium" presentation="list">
<FabMenuItem value="copy" icon={copyIcon}>Copy</FabMenuItem>
<FabMenuItem value="save" icon={saveIcon}>Save</FabMenuItem>
</FabMenu>
);
}
Choosing an action
select reports the chosen item's value, the item's id (the factory's payload also has it as
id), and the menu closes.
import { createFabMenu } from 'mtrl';
const fabMenu = createFabMenu({
icon: editIcon,
ariaLabel: 'Reply options',
presentation: 'list',
items: [
{ id: 'reply', text: 'Reply', icon: backIcon },
{ id: 'forward', text: 'Forward', icon: forwardIcon },
],
});
fabMenu.on('select', ({ value }) => choose(value));
document.body.append(fabMenu.element);
<m-fab-menu aria-label="Reply options" presentation="list">
<m-fab-menu-item value="reply">Reply</m-fab-menu-item>
<m-fab-menu-item value="forward">Forward</m-fab-menu-item>
</m-fab-menu>
<script type="module">
const fabMenu = document.querySelector('m-fab-menu');
fabMenu.setAttribute('icon', editIcon);
fabMenu.querySelector('m-fab-menu-item[value="reply"]').setAttribute('icon', backIcon);
fabMenu.querySelector('m-fab-menu-item[value="forward"]').setAttribute('icon', forwardIcon);
fabMenu.addEventListener('select', (event) => choose(event.detail.value));
</script>
import { FabMenu, FabMenuItem } from 'mtrl/react';
import { choose, editIcon, backIcon, forwardIcon } from './app';
export function Example() {
return (
<FabMenu onSelect={(event) => choose(event.detail.value)} icon={editIcon} ariaLabel="Reply options" presentation="list">
<FabMenuItem value="reply" icon={backIcon}>Reply</FabMenuItem>
<FabMenuItem value="forward" icon={forwardIcon}>Forward</FabMenuItem>
</FabMenu>
);
}
<script setup lang="ts">
import { MFabMenu, MFabMenuItem } from 'mtrl/vue';
import { choose, editIcon, backIcon, forwardIcon } from './app';
</script>
<template>
<MFabMenu @select="choose($event.detail.value)" :icon="editIcon" aria-label="Reply options" presentation="list">
<MFabMenuItem value="reply" :icon="backIcon">Reply</MFabMenuItem>
<MFabMenuItem value="forward" :icon="forwardIcon">Forward</MFabMenuItem>
</MFabMenu>
</template>
<script lang="ts">
import { FabMenu, FabMenuItem } from 'mtrl/svelte';
import { choose, editIcon, backIcon, forwardIcon } from './app';
</script>
<FabMenu onselect={(event) => choose(event.detail.value)} icon={editIcon} ariaLabel="Reply options" presentation="list">
<FabMenuItem value="reply" icon={backIcon}>Reply</FabMenuItem>
<FabMenuItem value="forward" icon={forwardIcon}>Forward</FabMenuItem>
</FabMenu>
import { FabMenu, FabMenuItem } from 'mtrl/solid';
import { choose, editIcon, backIcon, forwardIcon } from './app';
export function Example() {
return (
<FabMenu onSelect={(event) => choose(event.detail.value)} icon={editIcon} ariaLabel="Reply options" presentation="list">
<FabMenuItem value="reply" icon={backIcon}>Reply</FabMenuItem>
<FabMenuItem value="forward" icon={forwardIcon}>Forward</FabMenuItem>
</FabMenu>
);
}
placement: 'bottom-end' (or 'bottom-start') puts the FAB 16dp from the edges of its
positioned container, 24dp in large windows.
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
icon |
string |
— | The FAB's icon |
ariaLabel |
string |
— | The FAB's name: it describes the menu |
items |
FabMenuItem[] |
— | { id, text, icon? }, 2 to 6, top to bottom |
color |
'primary' | 'secondary' | 'tertiary' |
'primary' |
The colour set |
size |
'default' | 'medium' | 'large' |
'default' |
The FAB's size |
presentation |
'auto' | 'list' | 'menu' |
'auto' |
The list below 600px and the menu from 600px, or one of them |
placement |
'none' | 'bottom-end' | 'bottom-start' |
'none' |
Where it sits in its positioned container |
closeIcon |
string |
a close icon | The close button's icon |
menu |
(opener, items) => FabMenuMenu |
— | A menu of the app's own for the menu presentation |
class |
string |
— | Extra classes on the element |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
open(event?) |
event?: Event |
FabMenuComponent |
Opens it |
close() |
— | FabMenuComponent |
Closes it |
toggle(event?) |
event?: Event |
FabMenuComponent |
Opens or closes it |
isOpen() |
— | boolean |
Whether it is open |
getPresentation() |
— | 'list' | 'menu' |
The presentation in use |
destroy() |
— | void |
Removes it, its listeners and its menu |
fab is the FAB and list the list of the list presentation.
Events
| Event | Payload | Description |
|---|---|---|
open |
— | It opened |
close |
— | It closed |
select |
{ id, value } |
An item was chosen; it closes. value is the id again |
Element
<m-fab-menu> takes the options as attributes, and <m-fab-menu-item value icon> children
declare the items: the text is the label, or label. open reflects whether it is open, and
setting it opens it; show(), hide() and toggle() do the same. select's detail is
{ value }.
Accessibility
- The FAB is a menu button (
aria-haspopup,aria-expanded,aria-controls), named byariaLabel; the items aremenuitems of amenu. - Opening the list leaves focus on the FAB, now the close button. ArrowDown or Tab goes to the top item, ArrowUp to the one nearest the FAB. In the list the arrows wrap and Home and End go to the ends.
- Escape, Tab out of the list, or choosing an item closes it and returns focus to the FAB. A press outside closes it.
- The menu presentation is the baseline menu, with its own keyboard: a key that opens it lands on its first item.
Styling
| Class | What it is |
|---|---|
.mtrl-fab-menu |
The root, sized to the FAB |
.mtrl-fab-menu__fab |
The FAB and close button |
.mtrl-fab-menu__list |
The list |
.mtrl-fab-menu__item |
An item |
.mtrl-fab-menu--open |
Open |
.mtrl-fab-menu--list, --menu |
The presentation in use |
Measurements
| Attribute | Value | Token |
|---|---|---|
| Close button | 56dp, full corner | FabMenuBaselineTokens.CloseButtonContainerHeight, CloseButtonContainerShape |
| Close icon | 20dp | FabMenuBaselineTokens.CloseButtonIconSize |
| Item height | 56dp | FabMenuBaselineTokens.ListItemContainerHeight |
| Item padding | 24dp at each end | FabMenuBaselineTokens.ListItemLeadingSpace, ListItemTrailingSpace |
| Item icon | 24dp, 8dp from the label | FabMenuBaselineTokens.ListItemIconSize, ListItemIconLabelSpace |
| Item label | title medium | m3.material.io FAB menu specs |
| Between items | 4dp | FabMenuBaselineTokens.ListItemBetweenSpace |
| List to close button | 8dp | FabMenuBaselineTokens.CloseButtonBetweenSpace |
| Menu to FAB | 4dp | m3.material.io FAB menu specs (web) |
| Margin | 16dp, 24dp in large windows | m3.material.io FAB menu specs |
| Stagger | 26 to 114ms for six items | Compose SlowEffects spring on the visible count |