/Documentation

FAB menu

DraftUpdated

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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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 by ariaLabel; the items are menuitems of a menu.
  • 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