Searchable Accordion
Expandable sections filtered by a search query, with match highlighting.
How it works
A searchable accordion is a long list of questions that each expand to their answer, with a search box that narrows the list as you type. It solves the help-center problem: people rarely know the exact wording, category or accents used, and they need to see why each result is listed, even when the match is hidden in a collapsed answer.
- A real disclosure pattern: each question is a
buttonin a heading witharia-expanded, and closed answers areinert. - Forgiving, literal search: every word must appear, in any order, ignoring case and accents, with no regular expressions.
- Every match is shown: highlights in questions and open answers, and a snippet for matches in closed ones.
- State survives filtering: a section you opened is still open when it comes back into the results.
Read moreShow less
Where you'd use it
- A help center or FAQ page with dozens of questions grouped by topic.
- Product documentation or release notes where each entry expands to its details.
- A settings page with many collapsible groups and a “Search settings” box.
- A policy or terms page where people jump to the one clause they care about.
How we built it
The questions come from the project's own mock endpoint, so loading and error states are real. Each question is a button inside a heading, pointing at its answer with aria-controls, and the answer is labelled by its question. Answers aren't landmark regions, because with dozens open every one would become a landmark. A closed answer stays in the page so its height can animate (a grid row going from 0fr to 1fr), but it's inert: out of the tab order and hidden from screen readers.
Search folds text one character at a time (remove accents, lowercase) and remembers where each folded character came from. Matching runs on the folded text with indexOf, so “zurich” finds “Zürich” and C++ is just text. Overlapping matches are merged, then highlights are mapped back to the original text and rendered as plain strings and <mark> elements, never as injected HTML.
Open sections live in a reducer, in the order they were opened, and filtering never touches that list. The search index is built once per load, the list follows a deferred copy of the query (useDeferredValue), unchanged sections are memoized, and off-screen ones use content-visibility: auto, so typing stays smooth with about 400 sections.
Best practices
Do: Use a real
buttoninside a heading, witharia-expandedandaria-controls.Avoid: Clickable divs, or turning every answer into a landmark region when many can be open.
Do: Make collapsed content
inert(or remove it) so Tab and screen readers skip it.Avoid: Hiding answers only visually, so keyboard users tab into text they can't see.
Do: Fold accents and case per character and keep a map back to the original text.
Avoid: Folding the whole string first: lengths can change and highlights land in the wrong place.
Do: Match with
indexOfand build highlights from text and<mark>nodes.Avoid: Building a
RegExpfrom user input or injecting highlighted HTML.Do: Index once, defer the query and memoize rows so long lists stay responsive; announce the result count after typing pauses.
Avoid: Re-folding every answer on each keystroke, or announcing the count on every key.
Try it with the demo controls
- Open sections: One at a time / Many
- With several sections open, switch to “One at a time”: only the most recently opened stays open, and opening another closes it.
- Content size
- “Large (~400)” reloads ten times the content. Type in the search box and watch the list keep up.
- Latency
- Takes effect on the next load. Set it high and press Reload to see the list-shaped loading placeholder.
- Failure rate
- Set it to 100% and press Reload: an error appears in the demo with a Retry button.
- Reload
- Loads again with the current settings. Your search and open sections are kept.
- Search box
- Try “card refund”, “zurich” or “C++”. The count shows “N of M sections match”; a match only in a closed answer shows a “Match in answer” badge with a snippet. Escape clears it.
- Expand all / Collapse all
- Only in “Many” mode. During a search they affect only the sections shown, and each disables when there is nothing left to do.
- Keyboard
- Press Down in the search box to reach the first question, then Up, Down, Home and End to move between questions; Enter or Space toggles one.
- Clear search
- Search for “zzz” to reach the “No results” state. Clearing restores the list and puts focus back in the search box.
Trade-offs and limits
- Sections that match only in their answer aren't opened for you: a badge and snippet explain the match without changing what you opened.
- No fuzzy or typo-tolerant matching and no relevance ranking: results keep content order.
- Content is loaded once and searched in the browser; server-side search, debouncing and caching are a separate practice.
- The search and open sections aren't kept in the URL, and the large set is the normal content repeated with numbered variants.