/Documentation

Bottom app bar

PublishedUpdated

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.

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

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

Vanilla
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);
Web Components
<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>
React
import { BottomAppBar, IconButton } from 'mtrl/react';
import { searchIcon } from './app';

export function Example() {
  return (
    <BottomAppBar autoHide transitionDuration={200}>
      <IconButton icon={searchIcon} ariaLabel="Search" />
    </BottomAppBar>
  );
}
Vue
<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>
Svelte
<script lang="ts">
  import { BottomAppBar, IconButton } from 'mtrl/svelte';
  import { searchIcon } from './app';
</script>

<BottomAppBar autoHide transitionDuration={200}>
  <IconButton icon={searchIcon} ariaLabel="Search" />
</BottomAppBar>
SolidJS
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 toolbar named "Bottom app bar"; the web component's aria-label names 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