Accessibility pattern · Form inputs

Search field

The search element makes a landmark people can jump to, and the field inside it has a visible label, a clear button and a real submit button. Submitting keeps focus in the field and says how many results there are in a status message, so people can refine the words or Tab on to the results.

WCAG criteria
7
Keyboard rules
4
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Recent searches

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabMoves from the field to the clear button (when there is text), Search, the recent searches, and then the results.
EnterIn the field, searches; focus stays in the field and the count is announced.
EscapeIn the field, clears it and shows the recent searches again.
Enter or SpaceOn a recent search or a suggestion, runs that search and puts focus back in the field.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the fieldSearch help articles, search, Search help articles, search edit text
Enter searches for contrast8 results for “contrast”
A search finds nothingNo results for “contast”
Tab reaches the first resultContrast ratio, explained, link, list, 8 items
Recent searches are clearedRecent searches cleared

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-search-field" data-ap-search-field>
  <search class="ap-search-field__search" aria-labelledby="search-field-label">
    <form class="ap-search-field__form" action="#search-field-count" data-ap-form>
      <label class="ap-label" id="search-field-label" for="search-field-q">Search help articles</label>
      <div class="ap-search-field__row">
        <div class="ap-search-field__box">
          <svg class="ap-search-field__glass" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
          <input class="ap-search-field__input" id="search-field-q" type="search" name="q" autocomplete="off" spellcheck="false" enterkeyhint="search" />
          <button type="button" class="ap-search-field__clear" data-ap-erase aria-label="Clear search" hidden>
            <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18"/></svg>
          </button>
        </div>
        <button type="submit" class="ap-btn ap-btn--primary ap-search-field__go">Search</button>
      </div>
    </form>
  </search>

  <div class="ap-search-field__recent" data-ap-recent>
    <div class="ap-search-field__bar">
      <h3 class="ap-search-field__sub" id="search-field-recent">Recent searches</h3>
      <button type="button" class="ap-search-field__forget" data-ap-forget aria-label="Clear recent searches">Clear</button>
    </div>
    <ul class="ap-search-field__chips" aria-labelledby="search-field-recent" data-ap-recent-list>
        <li><button type="button" class="ap-search-field__chip" data-ap-term><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l3 2"/></svg><span translate="no">contrast</span></button></li>
        <li><button type="button" class="ap-search-field__chip" data-ap-term><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l3 2"/></svg><span translate="no">focus order</span></button></li>
        <li><button type="button" class="ap-search-field__chip" data-ap-term><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l3 2"/></svg><span translate="no">alt text</span></button></li>
    </ul>
  </div>

  <p class="ap-search-field__count" id="search-field-count" role="status" data-ap-status></p>
  <ul class="ap-search-field__hits" aria-labelledby="search-field-count" data-ap-hits hidden></ul>
  <div class="ap-search-field__none" data-ap-none hidden>
    <p class="ap-search-field__fix" data-ap-fix hidden><span>Did you mean</span> <button type="button" class="ap-search-field__fix-btn" data-ap-term><span translate="no" data-ap-fix-term></span></button></p>
    <p class="ap-search-field__tip">Check the spelling, or try one of these:</p>
    <ul class="ap-search-field__chips" aria-label="Suggested searches">
        <li><button type="button" class="ap-search-field__chip" data-ap-term><svg class="ap-search-field__glass" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><span translate="no">contrast</span></button></li>
        <li><button type="button" class="ap-search-field__chip" data-ap-term><svg class="ap-search-field__glass" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><span translate="no">forms</span></button></li>
        <li><button type="button" class="ap-search-field__chip" data-ap-term><svg class="ap-search-field__glass" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><span translate="no">keyboard</span></button></li>
        <li><button type="button" class="ap-search-field__chip" data-ap-term><svg class="ap-search-field__glass" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><span translate="no">captions</span></button></li>
    </ul>
  </div>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    The search element is a landmark, the recent searches are a labelled list of buttons, and results are a list named by the count.

  • 2.4.6 Headings and Labels Level AA

    The field's label, Search help articles, says what is searched, not just Search.

  • 2.4.3 Focus Order Level A

    Focus stays in the field after a search, and Tab moves on through the clear button, Search and the results in reading order.

  • 3.2.2 On Input Level A

    Typing never changes the page by itself unless Search as you type is switched on; Enter or the Search button runs it.

  • 3.3.3 Error Suggestion Level AA

    When nothing matches, a likely spelling fix is offered as a button, with other searches to try.

  • 4.1.2 Name, Role, Value Level A

    The clear button is named Clear search, and every recent search is a real button named by its words.

  • 4.1.3 Status Messages Level AA

    The result count, or the lack of results, is announced in a status message without moving focus.

Usage

When to use it

Use it

  • Site or help-centre search where people know roughly what they want and type it.
  • Searching a large collection that would be slow to browse, like articles, orders or contacts.

Use something else

  • Choosing one value from a known list as you type: use a combobox with autocomplete.
  • Narrowing a list that is already on screen by a few properties: use filters.

Common failures

How it usually goes wrong

  • A placeholder instead of a label

    Placeholder text disappears on the first key and is often too faint to read. The label here stays visible above the field.

  • An icon-only submit with no name

    A magnifying glass button without text or aria-label is read as "button". The submit button here says Search.

  • Focus thrown to the results

    Moving focus away after every search makes refining the words a chore. Focus stays put and the count is announced instead.

  • A silent empty result

    When nothing matches and nothing is said, people cannot tell a slow search from a failed one. "No results" is announced with suggestions.

  • Two clear buttons

    type=search draws its own clear control in some browsers, which is tiny and unnamed. It is hidden here in favour of one labelled button.

  • Recent searches as plain text

    Recent terms that look clickable but are spans cannot be reached by keyboard. Each one is a button that runs the search.

Notes

Building it

  • Older browsers render an unknown search element as a plain div; add role="search" to the form instead if you need to support them.
  • The landmark is named by the field's label so it can be told apart from a site-wide search in the header.
  • Search as you type waits for a pause before searching and announcing, so the count is not read out on every key.
  • The results here come from a small list in the script; keep the same markup and status message when they come from a server, and say "Searching" if it takes long.
  • Result links in the demo point to # targets that stand for real articles.

Sources: HTML: the search element · WAI-ARIA Authoring Practices: Search landmark

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction