Bottom app bar

Bottom app bar

This screen's commands at the bottom edge, with an optional FAB — not its destinations.

A .bottom-app-bar is an 80dp row of actions pinned to the bottom of the screen: the commands that belong to the screen the reader is already on, plus, optionally, that screen's FAB.

Anatomy is the HTML. Direct <a> or <button> children holding a single icon are the actions; a .button.extra.circle child is the FAB and is pushed to the end. Every action is icon-only, so every one of them carries an aria-label — that is the only name it has.

<div class="bottom-app-bar">
  <button type="button" aria-label="Check">
    <span class="material-symbols" aria-hidden="true">check_box</span>
  </button>
  <button type="button" aria-label="Edit">
    <span class="material-symbols" aria-hidden="true">edit</span>
  </button>
  <button type="button" aria-label="Share">
    <span class="material-symbols" aria-hidden="true">share</span>
  </button>
  <button type="button" aria-label="More options">
    <span class="material-symbols" aria-hidden="true">more_vert</span>
  </button>
</div>

Not the navigation bar

These are two different components that live in the same place, and picking the wrong one is the mistake this page exists to prevent.

Bottom app barNavigation bar
Holds Commands for this screen 3–5 destinations, the same on every screen
Markup <div class="bottom-app-bar"> <nav class="navigation-bar" aria-label="…">
Landmark None navigation
Selected state None — an action fires and is done aria-current="page" on the destination

Because a bottom app bar holds commands, it is not a <nav> and takes no landmark of any kind — not <footer> either, which is contentinfo. That is the same reasoning that keeps a toolbar out of the landmark list. It takes no role="toolbar" either: that role promises arrow-key navigation between the actions, and nothing here implements one. They are reached with Tab.

Material says never show both bars at once — pick the one that matches what the bottom of your screen is for. Nothing in the two components' styling overlaps, so a page that shows both anyway gets two correct bars stacked on top of each other rather than one broken one. See the navigation bar for the other half of the choice.

With a FAB

Add the screen's FAB as the last child and it goes to the end of the bar on its own — there is no spacer element to write. Inside the bar it is flat and secondary-container rather than lifted and primary-container: the bar already carries the elevation, and a second shadow inside it reads as a sticker.

<div class="bottom-app-bar">
  <button type="button" aria-label="Check">
    <span class="material-symbols" aria-hidden="true">check_box</span>
  </button>
  <button type="button" aria-label="More options">
    <span class="material-symbols" aria-hidden="true">more_vert</span>
  </button>
  <button type="button" class="button extra circle" aria-label="New message">
    <span class="material-symbols" aria-hidden="true">add</span>
  </button>
</div>

The bar is one height whether or not a FAB is in it. Material's 72dp with-fab height is deprecated: 80dp, content vertically centred, both ways.

Pinning it

The bar is an ordinary block by default, so it sits wherever you put it — which is what the demos above are doing. Add .fixed to pin it to the bottom edge of the viewport, clear of the device's own safe area.

<div class="bottom-app-bar fixed">…</div>

A fixed bar covers the last 80dp of the page, so leave that much padding at the bottom of the content behind it.

Or a docked toolbar

M3 Expressive's answer to the same problem is the docked toolbar — a shorter 64dp bar holding the same kind of commands, with the emphasized action inside it rather than a FAB beside them. Both ship. This component is the app-bar-shaped version, and it stays because Material still publishes its tokens.

<div class="toolbar docked">…</div>

Tokens

Set these on the bar itself, or in a rule of your own. They are declared on the component, so a value set on an ancestor element is shadowed by the bar's own default rather than inherited.

Token Default
--md-comp-bottom-app-bar-container-colorvar(--md-sys-color-surface-container)
--md-comp-bottom-app-bar-container-height80px
--md-comp-bottom-app-bar-container-shape0
--md-comp-bottom-app-bar-icon-colorvar(--md-sys-color-on-surface-variant)
--md-comp-bottom-app-bar-icon-size24px
--md-comp-bottom-app-bar-action-size48px
--md-comp-bottom-app-bar-leading-space4px
--md-comp-bottom-app-bar-trailing-space4px
--md-comp-bottom-app-bar-fab-container-colorvar(--md-sys-color-secondary-container)
--md-comp-bottom-app-bar-fab-trailing-space16px
--md-comp-bottom-app-bar-z997

The leading and trailing space is 4dp, not 16: an action is a 48dp target around a 24dp glyph, so it brings 12dp of its own and the glyph lands 16dp from the container edge.

The first six names are Material's own (md.comp.bottom-app-bar.* — the container is the whole family it publishes). action-size, fab-trailing-space and -z are this framework's: the actions are icon buttons and the FAB is a FAB, so Material states their values under those components instead.

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.