Bottom app bar
A bottom app bar puts a screen's frequent actions within thumb reach on a phone: a row of icon buttons and, often, a FAB. M3 Expressive replaces it with the docked toolbar; this page describes the bar as mtrl ships it today. See the M3 bottom app bar guidelines.
Usage
The actions sit at the leading side and the FAB at the trailing end. Icon buttons need
ariaLabel to name them.
import { createBottomAppBar, createIconButton, createFab } from 'mtrl';
const bottomBar = createBottomAppBar();
bottomBar.addAction(createIconButton({ icon: searchIcon, ariaLabel: 'Search' }).element);
bottomBar.addAction(createIconButton({ icon: heartOutlineIcon, ariaLabel: 'Favorite' }).element);
bottomBar.addAction(createIconButton({ icon: shareIcon, ariaLabel: 'Share' }).element);
bottomBar.addFab(createFab({ icon: addIcon, ariaLabel: 'Compose' }).element);
document.body.append(bottomBar.element);
<m-bottom-app-bar>
<m-icon-button aria-label="Search"></m-icon-button>
<m-icon-button aria-label="Favorite"></m-icon-button>
<m-icon-button aria-label="Share"></m-icon-button>
<m-fab slot="fab" aria-label="Compose"></m-fab>
</m-bottom-app-bar>
<script type="module">
const bottomAppBar = document.querySelector('m-bottom-app-bar');
bottomAppBar.querySelector('m-icon-button[aria-label="Search"]').setAttribute('icon', searchIcon);
bottomAppBar.querySelector('m-icon-button[aria-label="Favorite"]').setAttribute('icon', heartOutlineIcon);
bottomAppBar.querySelector('m-icon-button[aria-label="Share"]').setAttribute('icon', shareIcon);
bottomAppBar.querySelector('m-fab[slot="fab"][aria-label="Compose"]').setAttribute('icon', addIcon);
</script>
import { BottomAppBar, IconButton, Fab } from 'mtrl/react';
import { searchIcon, heartOutlineIcon, shareIcon, addIcon } from './app';
export function Example() {
return (
<BottomAppBar>
<IconButton icon={searchIcon} ariaLabel="Search" />
<IconButton icon={heartOutlineIcon} ariaLabel="Favorite" />
<IconButton icon={shareIcon} ariaLabel="Share" />
<Fab slot="fab" icon={addIcon} ariaLabel="Compose" />
</BottomAppBar>
);
}
<script setup lang="ts">
import { MBottomAppBar, MIconButton, MFab } from 'mtrl/vue';
import { searchIcon, heartOutlineIcon, shareIcon, addIcon } from './app';
</script>
<template>
<MBottomAppBar>
<MIconButton :icon="searchIcon" aria-label="Search" />
<MIconButton :icon="heartOutlineIcon" aria-label="Favorite" />
<MIconButton :icon="shareIcon" aria-label="Share" />
<template #fab>
<MFab :icon="addIcon" aria-label="Compose" />
</template>
</MBottomAppBar>
</template>
<script lang="ts">
import { BottomAppBar, IconButton, Fab } from 'mtrl/svelte';
import { searchIcon, heartOutlineIcon, shareIcon, addIcon } from './app';
</script>
<BottomAppBar>
<IconButton icon={searchIcon} ariaLabel="Search" />
<IconButton icon={heartOutlineIcon} ariaLabel="Favorite" />
<IconButton icon={shareIcon} ariaLabel="Share" />
{#snippet fab()}
<Fab icon={addIcon} ariaLabel="Compose" />
{/snippet}
</BottomAppBar>
import { BottomAppBar, IconButton, Fab } from 'mtrl/solid';
import { searchIcon, heartOutlineIcon, shareIcon, addIcon } from './app';
export function Example() {
return (
<BottomAppBar>
<IconButton icon={searchIcon} ariaLabel="Search" />
<IconButton icon={heartOutlineIcon} ariaLabel="Favorite" />
<IconButton icon={shareIcon} ariaLabel="Share" />
<Fab slot="fab" icon={addIcon} ariaLabel="Compose" />
</BottomAppBar>
);
}
Examples
A centered FAB
fabPosition: 'center' puts the FAB in the middle of the bar, and the actions keep the
leading side.
import { createBottomAppBar, createIconButton, createFab } from 'mtrl';
const bottomBar = createBottomAppBar({ fabPosition: 'center' });
bottomBar.addAction(createIconButton({ icon: menuIcon, ariaLabel: 'Menu' }).element);
bottomBar.addFab(createFab({ icon: photoIcon, ariaLabel: 'Scan' }).element);
document.body.append(bottomBar.element);
<m-bottom-app-bar fab-position="center">
<m-icon-button aria-label="Menu"></m-icon-button>
<m-fab slot="fab" aria-label="Scan"></m-fab>
</m-bottom-app-bar>
<script type="module">
const bottomAppBar = document.querySelector('m-bottom-app-bar');
bottomAppBar.querySelector('m-icon-button[aria-label="Menu"]').setAttribute('icon', menuIcon);
bottomAppBar.querySelector('m-fab[slot="fab"][aria-label="Scan"]').setAttribute('icon', photoIcon);
</script>
import { BottomAppBar, IconButton, Fab } from 'mtrl/react';
import { menuIcon, photoIcon } from './app';
export function Example() {
return (
<BottomAppBar fabPosition="center">
<IconButton icon={menuIcon} ariaLabel="Menu" />
<Fab slot="fab" icon={photoIcon} ariaLabel="Scan" />
</BottomAppBar>
);
}
<script setup lang="ts">
import { MBottomAppBar, MIconButton, MFab } from 'mtrl/vue';
import { menuIcon, photoIcon } from './app';
</script>
<template>
<MBottomAppBar fab-position="center">
<MIconButton :icon="menuIcon" aria-label="Menu" />
<template #fab>
<MFab :icon="photoIcon" aria-label="Scan" />
</template>
</MBottomAppBar>
</template>
<script lang="ts">
import { BottomAppBar, IconButton, Fab } from 'mtrl/svelte';
import { menuIcon, photoIcon } from './app';
</script>
<BottomAppBar fabPosition="center">
<IconButton icon={menuIcon} ariaLabel="Menu" />
{#snippet fab()}
<Fab icon={photoIcon} ariaLabel="Scan" />
{/snippet}
</BottomAppBar>
import { BottomAppBar, IconButton, Fab } from 'mtrl/solid';
import { menuIcon, photoIcon } from './app';
export function Example() {
return (
<BottomAppBar fabPosition="center">
<IconButton icon={menuIcon} ariaLabel="Menu" />
<Fab slot="fab" icon={photoIcon} ariaLabel="Scan" />
</BottomAppBar>
);
}
Hiding on scroll
With autoHide, the bar slides out once the window scrolls more than 10px down, and back on
the way up, over transitionDuration milliseconds.
import { createBottomAppBar, createIconButton } from 'mtrl';
const bottomBar = createBottomAppBar({ autoHide: true, transitionDuration: 200 });
bottomBar.addAction(createIconButton({ icon: searchIcon, ariaLabel: 'Search' }).element);
document.body.append(bottomBar.element);
<m-bottom-app-bar auto-hide transition-duration="200">
<m-icon-button aria-label="Search"></m-icon-button>
</m-bottom-app-bar>
<script type="module">
const bottomAppBar = document.querySelector('m-bottom-app-bar');
bottomAppBar.querySelector('m-icon-button[aria-label="Search"]').setAttribute('icon', searchIcon);
</script>
import { BottomAppBar, IconButton } from 'mtrl/react';
import { searchIcon } from './app';
export function Example() {
return (
<BottomAppBar autoHide transitionDuration={200}>
<IconButton icon={searchIcon} ariaLabel="Search" />
</BottomAppBar>
);
}
<script setup lang="ts">
import { MBottomAppBar, MIconButton } from 'mtrl/vue';
import { searchIcon } from './app';
</script>
<template>
<MBottomAppBar auto-hide :transition-duration="200">
<MIconButton :icon="searchIcon" aria-label="Search" />
</MBottomAppBar>
</template>
<script lang="ts">
import { BottomAppBar, IconButton } from 'mtrl/svelte';
import { searchIcon } from './app';
</script>
<BottomAppBar autoHide transitionDuration={200}>
<IconButton icon={searchIcon} ariaLabel="Search" />
</BottomAppBar>
import { BottomAppBar, IconButton } from 'mtrl/solid';
import { searchIcon } from './app';
export function Example() {
return (
<BottomAppBar autoHide transitionDuration={200}>
<IconButton icon={searchIcon} ariaLabel="Search" />
</BottomAppBar>
);
}
Auto-hide follows the window; a bar over its own scrolling container is driven with show()
and hide(). onVisibilityChange(visible) is called whenever the bar hides or shows,
whatever caused it. The bar is position: absolute at the bottom of its container, so that
container needs position: relative, and room at the end of its content for the bar.
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
hasFab |
boolean |
false |
The bar's height with a FAB from the start; addFab() sets it too |
fabPosition |
'center' | 'end' |
'end' |
Where the FAB sits |
autoHide |
boolean |
false |
Hide on scroll down, show on scroll up |
transitionDuration |
number |
300 |
Milliseconds for the slide; applied with autoHide |
onVisibilityChange |
(visible: boolean) => void |
— | Called when the bar hides or shows |
tag |
string |
'div' |
The element to build the bar from |
class |
string |
— | Extra classes on the element |
prefix |
string |
'mtrl' |
Class-name prefix |
componentName |
string |
'bottom-app-bar' |
Name used in class generation |
Methods
| Method | Returns | Description |
|---|---|---|
addAction(button) |
BottomAppBar |
Appends an element to the actions |
addFab(fab) |
BottomAppBar |
Replaces the FAB, and gives the bar its height with a FAB |
show() / hide() |
BottomAppBar |
Slides the bar into or out of view |
isVisible() |
boolean |
Whether it is shown |
getActionsContainer() |
HTMLElement |
The actions container, to remove or reorder actions |
destroy() |
void |
Removes the bar and its window scroll listener |
Events
The bar emits no events: onVisibilityChange reports its visibility.
Accessibility
- The bar is a
toolbarnamed "Bottom app bar"; the web component'saria-labelnames it instead. Its buttons are each in the tab order: the toolbar has no arrow-key navigation. - Each icon button and the FAB need their
ariaLabel. - Hidden, the bar is moved out of view and stays in the tab order.
Styling
The background is the theme's surface-container. Hiding is transform: translateY(100%).
.mtrl-bottom-app-bar { }
.mtrl-bottom-app-bar--with-fab, .mtrl-bottom-app-bar--fab-center, .mtrl-bottom-app-bar--hidden { }
.mtrl-bottom-app-bar__actions, .mtrl-bottom-app-bar__fab-container { }
Measurements
| Attribute | Value |
|---|---|
| Height | 80dp; 72dp with a FAB |
| Padding | 12dp top and bottom, 4dp leading, 16dp trailing |
| Space between actions | 4dp |
| Elevation | Level 2 |
| Corners | None |