The bar is the markup. A <header> whose child is a
<nav> is a top app bar. The heading is the headline.
Icon-only links and buttons are the leading and trailing actions.
A <menu> (or <ul>) holds text
destinations. There is no navbar, nav-wrapper,
or brand-logo class. Switching between app views at
the bottom of the screen is a
navigation bar.
Tokens follow the
M3 app bar spec.
The container is surface at rest, the headline is
on-surface at title-large, and icons are
24dp in a 48dp target, inset 4dp. Icons inherit the header color
so a fill + on-* pair stays readable.
The small bar is 64dp tall. Pair a fill utility with its
on-* text class if you want a colored bar.
The bar is CSS for layout. AppBar collapses
header.medium and header.large on scroll,
and opens the related search view when a search field in the bar is
selected. Menus and the navigation drawer are separate components that
AutoInit() starts. A
navigation-drawer-trigger inside the bar is still required — that
class is the NavigationDrawer contract, not bar chrome. Tabs live in
their own bar — do not nest .tabs in the header.
Small
Default. Leading icon, headline, trailing actions. DOM order is the layout — the headline grows, so anything after it sits on the end.
<header>
<nav aria-label="Main">
<button type="button" aria-label="Menu">
<span class="material-symbols" aria-hidden="true">menu</span>
</button>
<h2>Title</h2>
<a href="#!" aria-label="Search"><span class="material-symbols" aria-hidden="true">search</span></a>
<a href="#!" aria-label="More"><span class="material-symbols" aria-hidden="true">more_vert</span></a>
</nav>
</header>
Destinations
Text links go in a <menu>. Put the menu after the
heading to align it on the end; put it first to align it on the start.
Hide it below the Expanded breakpoint and pair it with a navigation drawer trigger
when the bar has to collapse.
<header>
<nav aria-label="Main">
<h2>Title</h2>
<menu>
<li><a href="#!">Sass</a></li>
<li><a href="#!">Components</a></li>
<li><a class="active" href="#!">JavaScript</a></li>
</menu>
</nav>
</header>
active goes on the link, not the list item. The state
layer mixes currentColor at 8% hover and 10% active, so
it follows the theme and any fill you put on the header.
Center-aligned
Add center to the header. The headline is taken out of
flow so the leading and trailing actions can sit on the edges without
shifting it. Keep the title short enough that it does not run under
the icons.
<header class="center">
<nav aria-label="Main">
<button type="button" aria-label="Back">
<span class="material-symbols" aria-hidden="true">arrow_back</span>
</button>
<h2>Title</h2>
<a href="#!" aria-label="More"><span class="material-symbols" aria-hidden="true">more_vert</span></a>
</nav>
</header>
Medium and large flexible
Same markup as the small bar. medium is the Expressive
medium-flexible bar: 112dp with a headline-medium title
on the second row. large is large-flexible: 120dp with
display-small. The deprecated medium/large bars used
headline-small / 152dp; do not recreate those. The title
wraps to two lines. It is ordered onto the bottom row
so the first row can hold the leading icon on the start and the
trailing icons on the end. Add center as well to
center that second-row title. On scroll, AppBar adds
collapsed and the bar is the small 64dp row.
<header class="medium">
<nav aria-label="Main">
<button type="button" aria-label="Back">
<span class="material-symbols" aria-hidden="true">arrow_back</span>
</button>
<h2>Medium title</h2>
<a href="#!" aria-label="More"><span class="material-symbols" aria-hidden="true">more_vert</span></a>
</nav>
</header>
<header class="large">…</header>
Subtitle
A subtitle is an <hgroup> wrapping the heading
and a <p>. That is the HTML for a heading plus
a subheading; there is no subtitle class. Small uses
label-medium. Medium-flexible grows to 136dp and uses
label-large. Large-flexible grows to 152dp and uses
title-medium.
<header class="medium">
<nav aria-label="Main">
<button type="button" aria-label="Back">
<span class="material-symbols" aria-hidden="true">arrow_back</span>
</button>
<hgroup>
<h2>Medium title</h2>
<p>Subtitle</p>
</hgroup>
<a href="#!" aria-label="More"><span class="material-symbols" aria-hidden="true">more_vert</span></a>
</nav>
</header>
Fixed
Add fixed to pin a top bar with position: sticky.
No wrapper is required. At rest the bar is surface,
the same as the page. Once content scrolls under it, supporting
browsers fill it with surface-container via
animation-timeline: scroll() so it separates from the
body — that is the M3 Expressive treatment, not a shadow.
Without that API the bar stays at rest.
The documentation header on this site is a fixed small bar. A second fixed bar on this page would sit on top of it, so the live example is the site header itself.
<header class="fixed">
<nav aria-label="Main">
<h2>Title</h2>
<a href="#!" aria-label="Search"><span class="material-symbols" aria-hidden="true">search</span></a>
</nav>
</header>
Color
The default fill is surface. Color utilities win because
they live in the utilities layer — put
primary on-primary-text (or any fill +
on-* pair) on the header. Icons inherit the header
color. Set
--md-comp-top-app-bar-trailing-icon-color to
var(--md-sys-color-on-surface-variant) if you want the
spec’s muted trailing icons.
<header class="primary on-primary-text">
<nav aria-label="Main">
<h2>Primary</h2>
</nav>
</header>
Component tokens you can set on the header (or on :root):
| Token | Default |
|---|---|
--md-comp-top-app-bar-container-color | --md-sys-color-surface |
--md-comp-top-app-bar-scrolled-container-color | --md-sys-color-surface-container |
--md-comp-top-app-bar-headline-color | --md-sys-color-on-surface |
--md-comp-top-app-bar-leading-icon-color | inherit (spec: on-surface) |
--md-comp-top-app-bar-trailing-icon-color | inherit (spec: on-surface-variant) |
--md-comp-top-app-bar-container-height | 64px |
--md-comp-top-app-bar-leading-icon-size | 24px |
Search
The search app bar is a <search class="search-bar">
in the nav, in place of the headline. Icons beside it sit outside
the search; icons inside it stay on the search-bar. The input is
centered. Focusing the field opens the related
.search-view.
<header>
<nav aria-label="Main">
<button type="button" aria-label="Menu">
<span class="material-symbols" aria-hidden="true">menu</span>
</button>
<search class="search-bar" aria-label="Search">
<span class="material-symbols" aria-hidden="true">search</span>
<input type="search" placeholder="Search" aria-label="Search">
</search>
</nav>
</header>
A <form> in the nav still fills the space between
the leading action and anything after it. The input is unstyled
against the bar — no extra field class.
Mobile collapse
Hide the destination menu below the Expanded breakpoint and put a
navigation-drawer-trigger in the leading slot. The trigger stays
visible at every size — it is the page-navigation control, not
collapse chrome. Pair it with a navigation-drawer whose id
matches data-target. The sidenav element itself must
not be a child of the <nav>.
<header>
<nav aria-label="Main">
<button type="button" data-target="mobile-demo" class="navigation-drawer-trigger" aria-label="Open menu">
<span class="material-symbols" aria-hidden="true">menu</span>
</button>
<h2>Title</h2>
<menu class="hide-on-med-and-down">
<li><a href="#!">Sass</a></li>
<li><a href="#!">Components</a></li>
</menu>
</nav>
</header>
<nav aria-label="Main">
<ul class="navigation-drawer" id="mobile-demo">
<li><a href="#!">Sass</a></li>
</ul>
</nav>
After you add the trigger and the navigation drawer, initialize NavigationDrawer (or let
AutoInit() do it).
document.addEventListener('DOMContentLoaded', function() {
Expressive.NavigationDrawer.init(document.querySelectorAll('.navigation-drawer'));
});