<pagefind-searchbox>

An all-in-one search component combining input, results dropdown, and keyboard navigation. Results appear in a floating dropdown below the input and load their full data only when scrolled into view using the Intersection Observer API.

<pagefind-searchbox></pagefind-searchbox>

Attributes

AttributeTypeDefaultDescription
placeholderstringLanguage dependentPlaceholder text for the input
debouncenumber150Milliseconds to wait after typing
max-resultsnumberunlimitedLimit number of results shown
show-sub-resultsbooleanfalseShow anchor-based sub-results in dropdown
show-keyboard-hintsbooleantrueShow keyboard navigation hints in footer
autofocusbooleanfalseFocus input on page load
shortcutstring"mod+k"Keyboard shortcut to focus the input
hide-shortcutbooleanfalseHide the keyboard shortcut display
instancestring"default"Connect to a specific Pagefind instance

Keyboard Shortcut

By default, the searchbox listens for Cmd+K (Mac) or Ctrl+K (Windows/Linux) to focus the input, and displays the shortcut hint inside the input field.

<!-- Default: Cmd+K / Ctrl+K -->
<pagefind-searchbox></pagefind-searchbox>

<!-- Custom shortcut -->
<pagefind-searchbox shortcut="/"></pagefind-searchbox>

<!-- Hide the visual shortcut hint -->
<pagefind-searchbox hide-shortcut></pagefind-searchbox>

The shortcut attribute accepts the same syntax as <pagefind-modal-trigger>.

Keyboard Navigation

Full keyboard support is built in:

KeyAction
/ Navigate through results
EnterGo to selected result
Shift+EnterOpen result in new tab
Ctrl+Enter / Cmd+EnterOpen result in new tab
EscapeClose dropdown
TabMove focus away

Both Shift+Enter and the platform modifier (Ctrl on Windows/Linux, Cmd on Mac) with Enter will open the result in a new tab.

Custom Templates

Like <pagefind-results>, you can customize rendering with <script type="text/pagefind-template"> elements. Templates use a simple syntax with variables, conditionals, and loops.

Basic Template

<pagefind-searchbox>
  <script type="text/pagefind-template">
    <a class="my-result" id="{{ aria.result_id }}" href="{{ meta.url | default(url) | safeUrl }}" role="option" aria-selected="false" aria-labelledby="{{ aria.title_id }}">
      <p id="{{ aria.title_id }}">{{ meta.title }}</p>
      {{#if excerpt}}
      <p>{{+ excerpt +}}</p>
      {{/if}}
    </a>
  </script>
</pagefind-searchbox>

The full templating syntax can be seen in the adequate-little-templates package.

Available Data

FieldDescription
meta.titlePage title
meta.urlPage URL
meta.*Any custom metadata
excerptSearch excerpt with <mark> highlighting (use {{+ excerpt +}})
urlFallback URL
sub_resultsArray of matching sections (when show-sub-results is set)
options.show_sub_resultsWhether show-sub-results is set on the searchbox
aria.result_idUnique ID for ARIA attributes
aria.title_idID for the title element
aria.excerpt_idID for the excerpt element

Each sub-result has: title, url, excerpt, and its own aria object.

Full Template Example

Here’s the built-in template. Copy and customize it:

<pagefind-searchbox>
  <script type="text/pagefind-template">
    {{#if and(options.show_sub_results, sub_results)}}<div class="pf-searchbox-group" role="group" aria-label="{{ meta.title | default('Untitled') }}">{{/if}}
    <a class="pf-searchbox-result" id="{{ aria.result_id }}" href="{{ meta.url | default(url) | safeUrl }}" role="option" aria-selected="false" aria-labelledby="{{ aria.title_id }}"{{#if excerpt}} aria-describedby="{{ aria.excerpt_id }}"{{/if}}>
      <p class="pf-searchbox-result-title" id="{{ aria.title_id }}">{{ meta.title | default("Untitled") }}</p>
      {{#if excerpt}}
      <p class="pf-searchbox-result-excerpt" id="{{ aria.excerpt_id }}">{{+ excerpt +}}</p>
      {{/if}}
    </a>
    {{#if and(options.show_sub_results, sub_results)}}
    {{#each sub_results as sub}}
    <a class="pf-searchbox-result pf-searchbox-subresult" id="{{ sub.aria.result_id }}" href="{{ sub.url | safeUrl }}" role="option" aria-selected="false" aria-labelledby="{{ sub.aria.title_id }}"{{#if sub.excerpt}} aria-describedby="{{ sub.aria.excerpt_id }}"{{/if}}>
      <p class="pf-searchbox-result-title" id="{{ sub.aria.title_id }}">{{ sub.title | default("Section") }}</p>
      {{#if sub.excerpt}}
      <p class="pf-searchbox-result-excerpt" id="{{ sub.aria.excerpt_id }}">{{+ sub.excerpt +}}</p>
      {{/if}}
    </a>
    {{/each}}
    </div>{{/if}}
  </script>
  <script type="text/pagefind-template" data-template="placeholder">
    <div class="pf-searchbox-result pf-searchbox-placeholder" aria-hidden="true">
      <p class="pf-searchbox-result-title pf-skeleton pf-skeleton-title"></p>
      <p class="pf-searchbox-result-excerpt pf-skeleton pf-skeleton-excerpt"></p>
    </div>
  </script>
</pagefind-searchbox>

Placeholder Template

While results are loading, a skeleton placeholder is shown. You can customize it with a second template using data-template="placeholder", as shown in the full example above. The placeholder template receives no data — it is static HTML. Include aria-hidden="true" on the root element so screen readers skip the loading skeletons.

Template Requirements

The searchbox implements the ARIA combobox pattern, so custom templates must include specific attributes for keyboard navigation and screen readers to work. Without these, keyboard navigation will skip your results entirely.

  • Each navigable item must be an <a> element with a valid href, role="option", and aria-selected="false"
  • Set id="{{ aria.result_id }}" (or {{ sub.aria.result_id }} for sub-results) so the input can track the active selection
  • The root element of your template must have role="option" or role="group"
  • Without sub-results, your template root is the <a role="option"> itself
  • With sub-results, your template root is a role="group" element wrapping the main result and its sub-results together, with each navigable item as a role="option" child (as shown in the full template above)
Components