Wiki Search Results
Live preview
with-results
zero-results
The results surface for a documentation wiki's full-text search — an ordered list of hits, each a linked article title over a ~180-character plain-text excerpt, with a distinct zero-results state that prompts the reader to retry. It renders whatever the wiki's Tantivy search index returns; it holds no query logic of its own.
components/wiki-search-results/recipe.jsonWhen to use Wiki Search Results
Use Wiki Search Results to present the response to a full-text search across a Knowledge Platform wiki. It is the display layer for one specific data shape — the hit list returned by the wiki's search index — and nothing else. Given a query and a set of hits, it renders the count, the ranked list of article links with excerpts, and, when the set is empty, a retry prompt.
It is not a search input. The search box, its keyboard handling, and the request to the index live elsewhere; this component consumes the response. It is also not a general-purpose list — the markup, excerpt length, and empty-state copy are all specific to the wiki search contract described below.
Variants
The recipe defines two variants, corresponding to the two states a search response can be in.
| Variant | Shows |
|---|---|
| with-results | A count summary (N results for "query") followed by an ordered list of hits — each an article-title link over a plain-text excerpt. |
| zero-results | An empty state with the message No results for "query". Try fewer or different keywords. and no list. |
The two states are mutually exclusive within a single render. The
zero-results block is toggled through the [hidden] attribute rather
than a display rule, which keeps the state change legible to
assistive technology (see Accessibility).
Anatomy
The results set is a single <section> landmark labelled for the
active query. Inside it:
- Summary —
N result(s) for "query", where the pluralsis present only when the count is not 1. - List — an ordered list (
<ol>), one<li>per hit. Each hit is a title link (<a href="/{slug}">{title}</a>) followed by a plain-text excerpt (<p>{snippet}</p>). - Empty state — a single message block, rendered in place of the list when the count is zero.
The excerpt is displayed as text, not HTML. The index returns the
first non-empty, non-heading paragraph of the article, roughly 180
characters, truncated at a word boundary with a trailing ellipsis
(…). No highlight markup is present today; see Data source.
Data source
This component renders the response of the wiki's search index. Per
the recipe's research notes, that index is Tantivy, queried over the
same JSON-RPC-over-/mcp convention the substrate documents at
MCP overview: an HTTP POST /mcp carrying
a JSON-RPC 2.0 envelope. The wiki deployment exposes a search
method — params: { q, limit } (the HTML page requests limit: 25;
agent callers default to 10).
The response shape the component is built against is:
{ "query": "…", "count": N, "hits": [ { "slug": "…", "title": "…", "snippet": "…" } ] }
Two properties of that contract shape the markup directly:
- Snippets are plain text — there is no
<mark>highlighting. The excerpt is assigned as text content, never as innerHTML. Do not add client-side highlighting that assumes HTML in the snippet. - The relevance score is internal. Tantivy computes a BM25 score to rank hits, but it is not part of the returned JSON. The list is already in rank order; the component does not display or re-sort by score.
Tokens
Every colour, space, and type value in the recipe resolves to a substrate token — no literal values are hard-coded except the 1px hairline rule on each result divider. The recipe declares the following token surface:
| Token | Tier | Drives |
|---|---|---|
{primitive.font.family.body} | primitive | Body typeface for the whole results section |
{semantic.text.primary} | theme | Primary text |
{semantic.text.secondary} | theme | Summary line, excerpt, and empty-state text |
{semantic.interactive.link} | theme | Article-title link colour |
{semantic.interactive.link-hover} | theme | Title link on hover |
{semantic.border.subtle} | theme | Hairline divider between results |
{primitive.space.2} | primitive | Gaps between hits, list spacing |
{primitive.space.4} | primitive | Vertical padding of the empty state |
Because the palette values come entirely from theme tokens, the component re-themes with the active tenant automatically — the same markup carries whatever text, link, and border values the mounted theme supplies.
Accessibility
Target: WCAG 2.2 AA.
- Live region. The results
<section>carriesaria-live="polite"andaria-atomic="true", and anaria-labelnaming the active query. When a new search replaces the contents, a screen reader announces the updated block — including the result count — without the reader having to move focus into it.polite(notassertive) lets the announcement wait for a pause in speech. - Empty state toggling. The zero-results block is shown and hidden
with the
[hidden]attribute rather than adisplay:noneCSS rule.[hidden]removes the block from the accessibility tree cleanly, so assistive technology never encounters a stale or duplicated empty message when results are present. - Excerpt text. Snippets are plain text straight from the index, so there is no markup for a screen reader to stumble over and no sanitisation step required.
- Links. Each hit is a real anchor to the article's slug, so it is reachable by keyboard and exposed as a link — not a scripted click target — to assistive technology.
When not to use
- Do not use this for a search input or autocomplete surface — it renders results only.
- Do not use it for a non-wiki result set. The excerpt length, empty-state copy, and plain-text snippet contract are specific to the wiki search index; a different data source needs a different component.
- Do not inject HTML into the excerpt. The current contract is plain-text-only; if the index later returns highlighted snippets (see below), that is a recipe change, not a per-consumer override.
Open question
The recipe records one forward-looking item: a later Tantivy phase may
add query-aware snippet generation with <mark> highlights. If that
ships, the excerpt template will need a sanitised innerHTML
assignment in place of the current text assignment. Until then, treat
the plain-text snippet contract as fixed.
Important Information
Design System disclosure
This site provides open-source design tokens, documentation, and self-hostable software published by Woodfine Capital Projects Inc. Information here is for general reference only and does not constitute an offer, warranty, or a guarantee of fitness for any particular purpose. Statements regarding planned, intended, or targeted future features are forward-looking and subject to change without notice; they are not undertaken to be updated except as required by law.