Skip to content

Search

Static search on the index of fumadocs-core, in a dialog that opens from the layouts or with a shortcut, and leads to the heading of a result.

The search runs in the browser, on the static index that the search server of fumadocs-core exports: no search service, no request per query. DocsProvider renders its dialog once, the layouts open it from their search buttons, and ⌘ K opens it from anywhere. Try it here.

The index

A route of the site serves the index: createFromSource() builds it from the pages of the source, their headings, paragraphs and tables, and staticGET() returns it as one file.

app/routes/search.ts
import { createFromSource } from 'fumadocs-core/search/server';

import { source } from '../lib/source';

const server = createFromSource(source, { language: 'english' });

export function loader() {
  return server.staticGET();
}
  • The address. The dialog fetches the index from /api/search, the default of fumadocs-core: React Router serves it from the route above, at api/search in app/routes.ts, and Next.js from app/api/search/route.ts (Setup).
  • A static site prerenders the route as a file, as it does the pages.
  • The language of createFromSource() sets how Orama, the search engine of fumadocs-core, splits and stems the words of the index.

Options

search on DocsProvider sets where the index is, or turns the search off:

// A site under a base path
<DocsProvider site={site} search={{ from: '/my-docs/api/search' }}>
  {children}
</DocsProvider>;

// No search: no buttons, no shortcut, no dialog
<DocsProvider site={site} search={false}>
  {children}
</DocsProvider>;
OptionTypeDefault
fromstring'/api/search'

The index loads as the dialog first opens, and stays for the rest of the visit.

  • The buttons. The sidebar holds a search field and the header of a phone an icon; a collapsed sidebar keeps an icon in its toolbar. HomeLayout shows a field from 48rem and an icon below, or none with search={false}, where the shortcut still opens the search (HomeLayout). A layout of your own renders SearchTrigger (Layouts).
  • The shortcut. ⌘ K on macOS, iOS and iPadOS, Ctrl K elsewhere, opens the dialog, and closes it again. Once the page has hydrated, the search buttons name the keys of the platform in aria-keyshortcuts, and the wide ones show them, except on touch screens.
  • In a text field on Apple platforms, Ctrl K deletes to the end of the line: the shortcut leaves it to the field there. A keyboard layout without Latin letters, such as Russian, takes the key in the place of K.
  • The page comes first. A shortcut that the page handles itself, calling preventDefault(), as a live example may, does not open the search.

Results

The results come as the user types, grouped by page: the page, with its section, then the headings, paragraphs and rows of tables of that page that match, indented under it. The words of the query are highlighted.

  • Ranking. A page whose title is the query comes first: button finds the Button page before the pages that mention buttons. The other pages keep the order of the search.
  • Keys. ↑ and ↓ move through the results, Enter opens the one highlighted, and Esc closes the dialog, giving the focus back to where it was. The keys are listed under the results, except on touch screens.
  • Status. Under the field, the dialog says that it is searching, that nothing matches the query, or that the index did not load; screen readers hear how many results there are.
  • Opened again, the dialog keeps the query and its results, and selects the query, so typing replaces it.

Where a result leads

Opening a result closes the dialog and goes to its address through the router of the framework: its page, at its heading. The focus moves there once the page shows it: to the heading, or to the article for a page, so a screen reader reads on from there, and the next Tab goes on in the content. A result on the page already shown only scrolls there, without a new entry in the history.

Accessibility

  • The dialog is named "Search". Its field is a combobox, "Search the docs", whose results are the options of a listbox: the field keeps the focus while the keys move through them.
  • Each group of results is named after its page and its section.
  • A cell of a table row starts with the name of its column, for screen readers, and for everyone once the dialog is too narrow for the columns.