Add search-bar to a <search> element
to make the bar. It holds a leading icon or icon button, an
<input type="search">, and whatever trailing
actions the query needs. The element is the landmark, so you do
not need to add role="search".
<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 has none of the text field chrome: no underline,
floating label, or container of its own. The bar is the
container, so put the input in bare, not inside a
.field.
The bar sets its spacing from what is at each end, so you do not need to set anything. A bare glyph sits 16dp from the edge. An icon button sits 4dp from it, because the button already insets its own glyph by 12dp.
A leading action
When the bar opens a drawer or goes back, use an icon button for the leading glyph. The bar's leading space shrinks to 4dp 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. It is 30dp and circular. Put it inside the button
when it is tappable, so the 48dp target comes from the button.
Docked view
For the docked view, put a .search-view inside the bar.
The view is positioned against the bar, which keeps its
position: relative, so the view needs no coordinates
of its own. Show and hide it with the
hidden attribute.
The view is a plain element rather than a <dialog>,
because dialog.show() moves focus into the dialog.
That would take the caret out of the input the user is typing in.
The demo below starts open so the surface shows 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
For the full-screen view, use a <dialog> and open
it with showModal(). The browser handles Escape and a tap
outside, so you only have to open it. Its header is another
.search-bar with the same markup, minus the pill
shape and the shadow.
<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>
Put autofocus on the input.
showModal() focuses the first focusable descendant,
which is the back button, so without autofocus a
keyboard user lands on Back and cannot type.
The M3 guideline is the full-screen view at Compact and the
docked view from Medium up. Your app picks which one to show,
because the stylesheet has no breakpoint that swaps one for the other. The
two use different markup. The docked view is a plain element
nested inside the bar, and the full-screen view is a top-level
<dialog>, so render the one that fits the
window size.
if (window.matchMedia('(width < 600px)').matches) {
document.getElementById('search-full').showModal();
} else {
document.getElementById('results').hidden = false;
}
Suggestions are Autocomplete
For suggestions, put autocomplete on the bar's input.
You get the combobox, its listbox, and the arrow-key and
aria-activedescendant handling from
Autocomplete, which
already implements and tests them. Search does not ship a
suggestion list of its own.
<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' }]
});
Search and Autocomplete share nothing else, by design. A
.search-view takes no composite role. Its contents
are links and buttons that users reach with Tab. A second listbox
here would announce the same behavior as Autocomplete's without
the keyboard handling 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 |