Search

Search

A search bar, and the view it expands into.

A <search> with search-bar on it is the bar: a leading icon or icon button, an <input type="search">, and whatever trailing actions the query needs. The element is the landmark — there is no role="search" to add.

<search class="search-bar" aria-label="Search">
  <span class="material-symbols" aria-hidden="true">search</span>
  <input type="search" aria-label="Search recipes" placeholder="Search recipes">
  <button type="button" class="icon-button" aria-label="Filters">
    <span class="material-symbols" aria-hidden="true">tune</span>
  </button>
</search>

The input carries none of the text-field chrome — no underline, no floating label, no container of its own. The bar is the container, so the input goes in bare rather than inside a .field.

Spacing follows what is at each end: a bare glyph sits 16dp from the edge, an icon button 4dp, because the button already insets its own glyph by 12dp. Nothing to set — the bar reads its own children.

.searchbar, the pre-1.0 name, still reaches the same rules.

A leading action

When the bar opens a drawer or goes back, the leading glyph becomes an icon button. The bar closes up around it on its own.

<search class="search-bar" aria-label="Search">
  <button type="button" class="icon-button" aria-label="Open navigation">
    <span class="material-symbols" aria-hidden="true">menu</span>
  </button>
  <input type="search" aria-label="Search mail" placeholder="Search mail">
  <button type="button" class="icon-button" aria-label="Account">
    <span class="material-symbols" aria-hidden="true">account_circle</span>
  </button>
</search>

An <img> in the bar is the account avatar M3 puts in that slot — 30dp and circular. Put it inside the button when it is tappable, so the 48dp target comes from the button.

Docked view

The docked view is a .search-view inside the bar. It hangs off the bar, so the bar keeps its position: relative and the view needs no coordinates of its own. Show and hide it with the hidden attribute.

It is a plain element rather than a <dialog> on purpose: dialog.show() moves focus into the dialog, which would take the caret out of the input the user is typing in.

The demo below starts open so the surface is on the page; the sample under it has the hidden a real page would write. Focus the input to reopen it.

<search class="search-bar" aria-label="Search">
  <span class="material-symbols" aria-hidden="true">search</span>
  <input type="search" aria-label="Search fruit" placeholder="Search fruit"
         onfocus="document.getElementById('results').hidden = false">
  <button type="button" class="icon-button" aria-label="Close search"
          onclick="document.getElementById('results').hidden = true">
    <span class="material-symbols" aria-hidden="true">close</span>
  </button>

  <div class="search-view" id="results" hidden>
    <ul class="list">
      <li><a href="/apricot"><span class="material-symbols" aria-hidden="true">history</span><span>Apricot</span></a></li>
    </ul>
  </div>
</search>

Full-screen view

Full screen, the view is a <dialog> opened with showModal(). Escape and a tap outside come from the platform, so there is nothing to wire up beyond opening it. Its header is another .search-bar — same markup, with the pill and the shadow taken off.


<button type="button" onclick="document.getElementById('search-full').showModal()">
  Search
</button>

<dialog class="search-view full-screen" id="search-full" aria-label="Search">
  <search class="search-bar" aria-label="Search">
    <form method="dialog">
      <button type="submit" class="icon-button" aria-label="Back">
        <span class="material-symbols" aria-hidden="true">arrow_back</span>
      </button>
    </form>
    <input type="search" autofocus aria-label="Search fruit" placeholder="Search fruit">
  </search>
  <hr>
  <ul class="list">
    <li><a href="/apricot"><span class="material-symbols" aria-hidden="true">history</span><span>Apricot</span></a></li>
  </ul>
</dialog>

autofocus on the input matters here. showModal() focuses the first focusable descendant, which is the back button — so without it a keyboard user lands on Back and cannot type.

Full screen at Compact and docked from Medium up is the M3 rule of thumb, and the app picks: there is no breakpoint in the sheet that swaps one for the other. The two are not interchangeable markup — the docked view is a plain element nested inside the bar, the full-screen one a top-level <dialog> — so render the one the window calls for.

if (window.matchMedia('(width < 600px)').matches) {
  document.getElementById('search-full').showModal();
} else {
  document.getElementById('results').hidden = false;
}

Suggestions are Autocomplete

Search does not ship a suggestion list of its own. Put autocomplete on the bar's input and you get the combobox, its listbox, and the arrow-key and aria-activedescendant handling that Autocomplete already implements and is tested for.

<search class="search-bar" aria-label="Search">
  <span class="material-symbols" aria-hidden="true">search</span>
  <input type="search" class="autocomplete" aria-label="Search fruit" placeholder="Search fruit">
</search>
Expressive.Autocomplete.init(document.querySelector('.search-bar input'), {
  minLength: 0,
  data: [{ id: 'Apricot' }, { id: 'Blackcurrant' }, { id: 'Clementine' }]
});

That is the whole relationship, and it is deliberate. A .search-view takes no composite role: its contents are links and buttons reached with Tab, and a second listbox here would be the same promise as Autocomplete's with no keyboard model behind it. Use the view for recent searches, filters, and results; use Autocomplete for a list the user arrows through.

Tokens

Override these on the bar or on the view.

Token Default
--md-comp-search-bar-container-color --md-sys-color-surface-container-high
--md-comp-search-bar-container-height 56px
--md-comp-search-bar-container-shape 9999px (full)
--md-comp-search-bar-leading-space 16px, or 4px beside an icon button
--md-comp-search-bar-trailing-space 16px, or 4px beside an icon button
--md-comp-search-bar-icon-label-space 16px
--md-comp-search-bar-icon-size 24px
--md-comp-search-bar-avatar-size 30px
--md-comp-search-view-container-color --md-sys-color-surface-container-high
--md-comp-search-view-container-shape 12px, 0 full-screen
--md-comp-search-view-header-height 56px, 72px full-screen
--md-comp-search-view-divider-color --md-sys-color-outline
--md-comp-search-view-bar-results-gap 2px
--md-comp-search-view-max-height 60vh, none full-screen
  • 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.