Toolbar
A toolbar holds the actions for the current page or the current selection: icon buttons, a few buttons, a text field. M3 Expressive has two. The docked toolbar spans the bottom of the window and holds global actions; it replaces the bottom app bar. The floating toolbar is a pill above the content and holds contextual actions, such as formatting, and can pair with a FAB. Don't show a toolbar and a navigation bar at the same time. See the M3 toolbar guidelines.
Usage
A docked toolbar spreads its items across the width. Icon buttons need ariaLabel to name
them, and the toolbar needs ariaLabel of its own when a page has more than one.
import { createToolbar } from 'mtrl';
const toolbar = createToolbar({
ariaLabel: 'Message actions',
items: [
{ icon: inboxIcon, ariaLabel: 'Move to inbox' },
{ icon: labelIcon, ariaLabel: 'Label' },
{ icon: shareIcon, ariaLabel: 'Share' },
{ icon: moreIcon, ariaLabel: 'More' },
],
});
document.body.append(toolbar.element);
<m-toolbar>
<m-icon-button aria-label="Move to inbox"></m-icon-button>
<m-icon-button aria-label="Label"></m-icon-button>
<m-icon-button aria-label="Share"></m-icon-button>
<m-icon-button aria-label="More"></m-icon-button>
</m-toolbar>
<script type="module">
const toolbar = document.querySelector('m-toolbar');
toolbar.querySelector('m-icon-button[aria-label="Move to inbox"]').setAttribute('icon', inboxIcon);
toolbar.querySelector('m-icon-button[aria-label="Label"]').setAttribute('icon', labelIcon);
toolbar.querySelector('m-icon-button[aria-label="Share"]').setAttribute('icon', shareIcon);
toolbar.querySelector('m-icon-button[aria-label="More"]').setAttribute('icon', moreIcon);
</script>
import { Toolbar, IconButton } from 'mtrl/react';
import { inboxIcon, labelIcon, shareIcon, moreIcon } from './app';
export function Example() {
return (
<Toolbar>
<IconButton icon={inboxIcon} ariaLabel="Move to inbox" />
<IconButton icon={labelIcon} ariaLabel="Label" />
<IconButton icon={shareIcon} ariaLabel="Share" />
<IconButton icon={moreIcon} ariaLabel="More" />
</Toolbar>
);
}
<script setup lang="ts">
import { MToolbar, MIconButton } from 'mtrl/vue';
import { inboxIcon, labelIcon, shareIcon, moreIcon } from './app';
</script>
<template>
<MToolbar>
<MIconButton :icon="inboxIcon" aria-label="Move to inbox" />
<MIconButton :icon="labelIcon" aria-label="Label" />
<MIconButton :icon="shareIcon" aria-label="Share" />
<MIconButton :icon="moreIcon" aria-label="More" />
</MToolbar>
</template>
<script lang="ts">
import { Toolbar, IconButton } from 'mtrl/svelte';
import { inboxIcon, labelIcon, shareIcon, moreIcon } from './app';
</script>
<Toolbar>
<IconButton icon={inboxIcon} ariaLabel="Move to inbox" />
<IconButton icon={labelIcon} ariaLabel="Label" />
<IconButton icon={shareIcon} ariaLabel="Share" />
<IconButton icon={moreIcon} ariaLabel="More" />
</Toolbar>
import { Toolbar, IconButton } from 'mtrl/solid';
import { inboxIcon, labelIcon, shareIcon, moreIcon } from './app';
export function Example() {
return (
<Toolbar>
<IconButton icon={inboxIcon} ariaLabel="Move to inbox" />
<IconButton icon={labelIcon} ariaLabel="Label" />
<IconButton icon={shareIcon} ariaLabel="Share" />
<IconButton icon={moreIcon} ariaLabel="More" />
</Toolbar>
);
}
Examples
Floating, with toggles
The floating toolbar is a pill, elevated by default. Toggle icon buttons keep their pressed state, for choices such as marking a photo.
import { createToolbar } from 'mtrl';
const toolbar = createToolbar({
variant: 'floating',
ariaLabel: 'Mark photo',
items: [
{
icon: heartOutlineIcon,
ariaLabel: 'Favorite',
toggle: true,
selected: true,
},
{ icon: starIcon, ariaLabel: 'Star', toggle: true },
{ icon: labelIcon, ariaLabel: 'Label', toggle: true },
],
});
document.body.append(toolbar.element);
<m-toolbar variant="floating">
<m-icon-button aria-label="Favorite" toggle selected></m-icon-button>
<m-icon-button aria-label="Star" toggle></m-icon-button>
<m-icon-button aria-label="Label" toggle></m-icon-button>
</m-toolbar>
<script type="module">
const toolbar = document.querySelector('m-toolbar');
toolbar.querySelector('m-icon-button[aria-label="Favorite"]').setAttribute('icon', heartOutlineIcon);
toolbar.querySelector('m-icon-button[aria-label="Star"]').setAttribute('icon', starIcon);
toolbar.querySelector('m-icon-button[aria-label="Label"]').setAttribute('icon', labelIcon);
</script>
import { Toolbar, IconButton } from 'mtrl/react';
import { heartOutlineIcon, starIcon, labelIcon } from './app';
export function Example() {
return (
<Toolbar variant="floating">
<IconButton icon={heartOutlineIcon} ariaLabel="Favorite" toggle selected />
<IconButton icon={starIcon} ariaLabel="Star" toggle />
<IconButton icon={labelIcon} ariaLabel="Label" toggle />
</Toolbar>
);
}
<script setup lang="ts">
import { MToolbar, MIconButton } from 'mtrl/vue';
import { heartOutlineIcon, starIcon, labelIcon } from './app';
</script>
<template>
<MToolbar variant="floating">
<MIconButton :icon="heartOutlineIcon" aria-label="Favorite" toggle selected />
<MIconButton :icon="starIcon" aria-label="Star" toggle />
<MIconButton :icon="labelIcon" aria-label="Label" toggle />
</MToolbar>
</template>
<script lang="ts">
import { Toolbar, IconButton } from 'mtrl/svelte';
import { heartOutlineIcon, starIcon, labelIcon } from './app';
</script>
<Toolbar variant="floating">
<IconButton icon={heartOutlineIcon} ariaLabel="Favorite" toggle selected />
<IconButton icon={starIcon} ariaLabel="Star" toggle />
<IconButton icon={labelIcon} ariaLabel="Label" toggle />
</Toolbar>
import { Toolbar, IconButton } from 'mtrl/solid';
import { heartOutlineIcon, starIcon, labelIcon } from './app';
export function Example() {
return (
<Toolbar variant="floating">
<IconButton icon={heartOutlineIcon} ariaLabel="Favorite" toggle selected />
<IconButton icon={starIcon} ariaLabel="Star" toggle />
<IconButton icon={labelIcon} ariaLabel="Label" toggle />
</Toolbar>
);
}
Vibrant
color: 'vibrant' puts the toolbar on primary container, for more emphasis or a temporary
mode such as editing.
import { createToolbar } from 'mtrl';
const toolbar = createToolbar({
variant: 'floating',
color: 'vibrant',
ariaLabel: 'Editing',
items: [
{ icon: closeIcon, ariaLabel: 'Cancel' },
{ icon: copyIcon, ariaLabel: 'Copy' },
{ icon: checkIcon, ariaLabel: 'Done', variant: 'filled' },
],
});
document.body.append(toolbar.element);
<m-toolbar variant="floating" color="vibrant">
<m-icon-button aria-label="Cancel"></m-icon-button>
<m-icon-button aria-label="Copy"></m-icon-button>
<m-icon-button aria-label="Done" variant="filled"></m-icon-button>
</m-toolbar>
<script type="module">
const toolbar = document.querySelector('m-toolbar');
toolbar.querySelector('m-icon-button[aria-label="Cancel"]').setAttribute('icon', closeIcon);
toolbar.querySelector('m-icon-button[aria-label="Copy"]').setAttribute('icon', copyIcon);
toolbar.querySelector('m-icon-button[aria-label="Done"]').setAttribute('icon', checkIcon);
</script>
import { Toolbar, IconButton } from 'mtrl/react';
import { closeIcon, copyIcon, checkIcon } from './app';
export function Example() {
return (
<Toolbar variant="floating" color="vibrant">
<IconButton icon={closeIcon} ariaLabel="Cancel" />
<IconButton icon={copyIcon} ariaLabel="Copy" />
<IconButton icon={checkIcon} ariaLabel="Done" variant="filled" />
</Toolbar>
);
}
<script setup lang="ts">
import { MToolbar, MIconButton } from 'mtrl/vue';
import { closeIcon, copyIcon, checkIcon } from './app';
</script>
<template>
<MToolbar variant="floating" color="vibrant">
<MIconButton :icon="closeIcon" aria-label="Cancel" />
<MIconButton :icon="copyIcon" aria-label="Copy" />
<MIconButton :icon="checkIcon" aria-label="Done" variant="filled" />
</MToolbar>
</template>
<script lang="ts">
import { Toolbar, IconButton } from 'mtrl/svelte';
import { closeIcon, copyIcon, checkIcon } from './app';
</script>
<Toolbar variant="floating" color="vibrant">
<IconButton icon={closeIcon} ariaLabel="Cancel" />
<IconButton icon={copyIcon} ariaLabel="Copy" />
<IconButton icon={checkIcon} ariaLabel="Done" variant="filled" />
</Toolbar>
import { Toolbar, IconButton } from 'mtrl/solid';
import { closeIcon, copyIcon, checkIcon } from './app';
export function Example() {
return (
<Toolbar variant="floating" color="vibrant">
<IconButton icon={closeIcon} ariaLabel="Cancel" />
<IconButton icon={copyIcon} ariaLabel="Copy" />
<IconButton icon={checkIcon} ariaLabel="Done" variant="filled" />
</Toolbar>
);
}
Vertical
A floating toolbar can be vertical in larger windows, placed at the start or end of the window, opposite a navigation rail. Up and Down then move between its items.
import { createToolbar } from 'mtrl';
const toolbar = createToolbar({
variant: 'floating',
orientation: 'vertical',
ariaLabel: 'Tools',
items: [
{ icon: editIcon, ariaLabel: 'Draw' },
{ icon: photoIcon, ariaLabel: 'Image' },
{ icon: shareIcon, ariaLabel: 'Share' },
],
});
document.body.append(toolbar.element);
<m-toolbar variant="floating" orientation="vertical">
<m-icon-button aria-label="Draw"></m-icon-button>
<m-icon-button aria-label="Image"></m-icon-button>
<m-icon-button aria-label="Share"></m-icon-button>
</m-toolbar>
<script type="module">
const toolbar = document.querySelector('m-toolbar');
toolbar.querySelector('m-icon-button[aria-label="Draw"]').setAttribute('icon', editIcon);
toolbar.querySelector('m-icon-button[aria-label="Image"]').setAttribute('icon', photoIcon);
toolbar.querySelector('m-icon-button[aria-label="Share"]').setAttribute('icon', shareIcon);
</script>
import { Toolbar, IconButton } from 'mtrl/react';
import { editIcon, photoIcon, shareIcon } from './app';
export function Example() {
return (
<Toolbar variant="floating" orientation="vertical">
<IconButton icon={editIcon} ariaLabel="Draw" />
<IconButton icon={photoIcon} ariaLabel="Image" />
<IconButton icon={shareIcon} ariaLabel="Share" />
</Toolbar>
);
}
<script setup lang="ts">
import { MToolbar, MIconButton } from 'mtrl/vue';
import { editIcon, photoIcon, shareIcon } from './app';
</script>
<template>
<MToolbar variant="floating" orientation="vertical">
<MIconButton :icon="editIcon" aria-label="Draw" />
<MIconButton :icon="photoIcon" aria-label="Image" />
<MIconButton :icon="shareIcon" aria-label="Share" />
</MToolbar>
</template>
<script lang="ts">
import { Toolbar, IconButton } from 'mtrl/svelte';
import { editIcon, photoIcon, shareIcon } from './app';
</script>
<Toolbar variant="floating" orientation="vertical">
<IconButton icon={editIcon} ariaLabel="Draw" />
<IconButton icon={photoIcon} ariaLabel="Image" />
<IconButton icon={shareIcon} ariaLabel="Share" />
</Toolbar>
import { Toolbar, IconButton } from 'mtrl/solid';
import { editIcon, photoIcon, shareIcon } from './app';
export function Example() {
return (
<Toolbar variant="floating" orientation="vertical">
<IconButton icon={editIcon} ariaLabel="Draw" />
<IconButton icon={photoIcon} ariaLabel="Image" />
<IconButton icon={shareIcon} ariaLabel="Share" />
</Toolbar>
);
}
Placement, a FAB and hiding on scroll
placement positions the toolbar in its container, which needs position: relative: a
horizontal floating toolbar 16dp above the bottom edge, a vertical one 24dp from the side. A
FAB passed as fab sits beside the toolbar, 8dp away. With scrollBehavior: 'exit', the
toolbar leaves the screen after 40px of scrolling down and comes back after 40px up, or at the
top; scrollTarget names the element that scrolls, the window by default.
import { createToolbar, createFab } from 'mtrl';
const toolbar = createToolbar({
variant: 'floating',
placement: 'bottom',
scrollBehavior: 'exit',
ariaLabel: 'Mark photo',
items: [
{ icon: heartOutlineIcon, ariaLabel: 'Favorite', toggle: true },
{ icon: starIcon, ariaLabel: 'Star', toggle: true },
],
fab: createFab({ icon: editIcon, ariaLabel: 'Edit', variant: 'primary-container' }),
});
document.body.append(toolbar.element);
Pair a vibrant toolbar with a tertiary-container FAB, a standard one with primary-container.
An overflow menu
overflow adds a trailing "more" button and hands it to a function that builds the menu it
opens. The toolbar never loads a menu of its own, so it costs nothing when unused. What the
function returns is destroyed with the toolbar.
import { createToolbar, createMenu } from 'mtrl';
const toolbar = createToolbar({
ariaLabel: 'Actions',
items: [{ icon: inboxIcon, ariaLabel: 'Move to inbox' }],
overflow: (opener) => createMenu({
opener,
items: [{ id: 'move', text: 'Move to' }, { id: 'mute', text: 'Mute' }],
}),
});
document.body.append(toolbar.element);
As an element, the menu goes in slot="overflow", and <m-toolbar> anchors it to the button:
<m-toolbar aria-label="Actions">
<m-icon-button aria-label="Move to inbox"></m-icon-button>
<m-menu slot="overflow">
<m-menu-item value="move">Move to</m-menu-item>
<m-menu-item value="mute">Mute</m-menu-item>
</m-menu>
</m-toolbar>
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
variant |
'docked' | 'floating' |
'docked' |
Full width at the bottom, or a pill above the content |
color |
'standard' | 'vibrant' |
'standard' |
Surface container, or primary container |
orientation |
'horizontal' | 'vertical' |
'horizontal' |
A floating toolbar's layout; docked is always horizontal |
placement |
'none' | 'bottom' | 'top' | 'start' | 'end' |
'none' |
Where it sits in its positioned container; docked takes only 'bottom' |
arrangement |
'spread' | 'center' |
'spread' |
A docked toolbar's items spread, or centred 32dp apart |
elevated |
boolean |
true for floating |
Level 1 shadow; a docked toolbar has none |
items |
ToolbarItem[] |
[] |
Icon button configs, button configs with text, or elements and components |
fab |
HTMLElement | { element } |
— | A FAB beside the toolbar, outside its tab stop |
fabPosition |
'start' | 'end' |
'end' |
The FAB's end of the toolbar |
overflow |
(opener: HTMLElement) => unknown |
— | Builds the menu the "more" button opens |
overflowIcon |
string |
more vertical | The overflow button's icon |
overflowLabel |
string |
'More options' |
The overflow button's name |
scrollBehavior |
'none' | 'exit' |
'none' |
Leave the screen while scrolling forward |
scrollTarget |
HTMLElement | Window |
window |
What scrolls |
scrollThreshold |
number |
40 |
Pixels of scrolling that hide or show it |
ariaLabel |
string |
'Toolbar' |
The toolbar's accessible name |
class |
string |
— | Extra classes on the element |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
add(item) |
item: ToolbarItem |
HTMLElement |
Adds an item before the overflow button |
remove(item) |
item: HTMLElement | { element } |
ToolbarComponent |
Removes an item; one the toolbar created is destroyed |
getItems() |
— | HTMLElement[] |
The item elements, without the overflow button |
show() |
— | ToolbarComponent |
Brings it back on screen |
hide() |
— | ToolbarComponent |
Moves it off screen and out of the focus order |
isVisible() |
— | boolean |
Whether it is on screen |
setColor(color) |
color: 'standard' | 'vibrant' |
ToolbarComponent |
Changes the colour |
getColor() |
— | 'standard' | 'vibrant' |
The current colour |
destroy() |
— | void |
Removes it, its listeners and the items it created |
bar is the element with the toolbar role, and overflowButton the overflow button, or
null.
Events
| Event | Payload | Description |
|---|---|---|
show |
— | It came back on screen |
hide |
— | It left the screen |
Element
<m-toolbar> takes the options as attributes (fab-position, scroll-behavior,
scroll-threshold, overflow-label), with flat for elevated: false. Its children are the
items; slot="fab" takes a FAB and slot="overflow" an <m-menu>. show() and hide()
dispatch show and hide.
Accessibility
- The element with the
toolbarrole holds the items and is named byariaLabel; a vertical toolbar setsaria-orientation="vertical". The FAB is beside it, outside the toolbar. - The toolbar is one tab stop. The arrow keys move along it (Left and Right follow the reading direction; Up and Down when vertical), Home and End go to the ends, and disabled items are skipped. Tab leaves it, and coming back lands on the item focused last.
- In a text field inside the toolbar, the arrow keys, Home and End stay with the caret.
- Off screen (
hide(), orscrollBehavior: 'exit'), the toolbar and its FAB areinert.
Styling
| Class | What it is |
|---|---|
.mtrl-toolbar |
The root: layout and placement |
.mtrl-toolbar__bar |
The element with the toolbar role |
.mtrl-toolbar__fab |
The FAB's container |
.mtrl-toolbar--docked, --floating |
The variant |
.mtrl-toolbar--vibrant |
The vibrant colour |
.mtrl-toolbar--hidden |
Off screen |
--mtrl-toolbar-shape overrides the container's corner: M3 allows a rounded docked toolbar on
the web and large screens.
Measurements
| Attribute | Value | Token |
|---|---|---|
| Height | 64dp | DockedToolbarTokens.ContainerHeight, FloatingToolbarTokens.ContainerHeight |
| Docked padding | 16dp at each end | DockedToolbarTokens.ContainerLeadingSpace, ContainerTrailingSpace |
| Docked spacing, centred | 32dp | DockedToolbarTokens.ContainerMaxSpacing |
| Docked corner | none | DockedToolbarTokens.ContainerShape (CornerNone) |
| Floating padding | 8dp | FloatingToolbarTokens.ContainerLeadingSpace, ContainerTrailingSpace |
| Floating spacing | 4dp | FloatingToolbarTokens.ContainerBetweenSpace |
| Floating corner | full | FloatingToolbarTokens.ContainerShape (CornerFull) |
| Floating elevation | level 1 | FloatingToolbarDefaults.ContainerExpandedElevationWithFab |
| Floating margin | 16dp, 24dp vertical | FloatingToolbarTokens.ContainerExternalPadding; m3.material.io guidelines (vertical) |
| FAB gap | 8dp | FloatingToolbarDefaults.ToolbarToFabGap |
| Standard container | surface container | DockedToolbarTokens.ContainerColor, FloatingToolbarTokens.StandardContainerColor |
| Vibrant container | primary container | FloatingToolbarTokens.VibrantContainerColor |
| Vibrant items | on primary container; selected on surface container | FloatingToolbarTokens.VibrantButton* |
| Scroll threshold | 40dp | FloatingToolbarDefaults.ScrollDistanceThreshold |