Accessibility

What the storefront components do for keyboard, focus and screen reader users, and what stays your shop's to set.

The components aim at WCAG 2.2 AA. Underneath they are links and GET forms, so search, filters, sort and paging work before the script loads, and without it. Every state a shopper reaches is a URL:

URLs the components write
/suche?q=couch                              the search box's form
/wohnen/sofas?color=grey                    a color ticked in the filters' form
/wohnen/sofas?color=grey&sort=price_asc     a sort chosen in its select

/suche is the home store's search page (suche: search). With script, the components add what the sections below say.

Announcements

StorefrontProvider renders a polite and an assertive live region after the page. A message is said once it has held for 500 ms and differs from the last; what a page says while it loads is not, since a screen reader reads the page. Say your own with useAnnouncement, urgently only for a failure:

app/unavailable.tsx
import {  } from "@orbsearch/react";

// "Search is not available right now"
export function () {
    ("Die Suche ist gerade nicht erreichbar", true);
    return <>Die Suche ist gerade nicht erreichbar</>;
}
  • A labelled combobox in a <search> landmark, with a submit button. The list opens when suggestions arrive for typed text, never on focus.
  • Groups read by name: text suggestions, the text in a category, each section's links and the products, each named as the arrow keys enter it.
  • Focus stays in the box while the arrow keys move, and a highlighted text suggestion shows in it. Escape closes the list and restores what was typed.
  • One count of the suggestions is announced once typing settles.
  • What a suggestion adds to the typed text is semibold, while its name reads as plain text; SuggestionText does the same for a row of your own.

Listing and results

  • The title is the one h1. When results arrive, the polite region says their count and order, after what the search left out. A page without results is said urgently and offers a next step.
  • A tile is an <article> whose heading is its one link. Its photo has empty alt text, since the name says what it shows.
  • Facts are words: a reduced price reads "was … now …", a rating its stars and reviews, and badges, delivery and stock are text. A sponsored product's heading starts with "Anzeige" (ad).
  • A banner is no article, so moving by articles meets the products alone. Its heading is its one link, and its image's alt text follows the words.
  • busy, set by your router on ListingPage, marks the results aria-busy while the next page loads; their photos dim, their text keeps its contrast.
  • "Load more" is a link to the next page. It appends the page, moves focus to the first new item and says how many came. There is no infinite scroll.
  • The trail is a labelled <nav> with aria-current="page" on its last step, and a rail a section with named arrow buttons.

Facets and filters

  • A facet is a button with aria-expanded inside a heading. A closed facet stays in the page, so the browser's find opens it.
  • Values are native checkboxes or radios in a fieldset named by the facet, the count in each label. They keep their order while focus is inside, and a swatch shows its name.
  • A range is two named number inputs that apply on Enter or blur. A minimum above the maximum is announced urgently and applies nothing.
  • A finder is a fieldset of labelled native selects, "Any" first.
  • A facet that loads says so in a status, and a failure in an alert.
  • The panel is a labelled <aside> whose choices apply at once, focus staying on the control.
  • A quick filter opens a popover named by its facet, focus on its first value. A choice applies after a short pause; closing keeps it and returns focus to the chip, and one visit is one step back in the history.
  • The sheet is a modal. Escape or the close button discard the pending choice and return focus to its button, which says how many filters are active.
  • A chip is a link named for what it does: "Filter entfernen: Farbe Grau" (remove filter: color grey). A rule's filter is text that says the shop set it. After a removal, focus moves to the next chip, else the previous, else the results' heading.
  • The sort is a native select with a label.

Product page

  • Each choice is a fieldset named by its detail, such as "Farbe" (color), with native radios. A swatch is named by its value.
  • A value no variant has beside the others chosen reads "in dieser Kombination nicht erhältlich" (not available in this combination).
  • The chosen value is ringed twice as thick and set heavier, never marked by color alone.
  • A pick's price and stock are said in the polite region, since they change away from the radio.

Motion and focus

  • Focus rings sit on each control's own classes, so a reset does not remove them. Controls and chips are at least --orb-spacing-target (2.75rem) high.
  • Nothing fades in front of an answer: results and suggestions show the moment they arrive.
  • Moving and scaling run only under prefers-reduced-motion: no-preference; otherwise a pressed button dims instead of shrinking.
  • Photos dim only once --orb-motion-wait has passed, so a quick answer never flickers.
  • On a touch screen, hover styles stay off and a field's text is at least 16 px, so the browser does not zoom. Under forced colors the system draws the checkboxes and radios.

What stays the shop's

The document's title, its lang and a skip link are yours to set in your root:

app/root.tsx (trimmed)
<html lang="de">
    <body>
        <a href="#content">Zum Inhalt springen</a>
        <main id="content" tabIndex={-1}>
            {children}
        </main>
    </body>
</html>

The link skips to the content. In a layout of your own, the heading you name in AppliedFilters' resultsHeading takes tabIndex={-1}, so focus can land there after the last chip; ListingPage sets it on its title.

Next

On this page