Navbar

Navbar

Material Design 3 top app bars, from the HTML.

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-colorinherit (spec: on-surface)
--md-comp-top-app-bar-trailing-icon-colorinherit (spec: on-surface-variant)
--md-comp-top-app-bar-container-height64px
--md-comp-top-app-bar-leading-icon-size24px

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'));
});
  • 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.