Search

Accent CMS generates a lightweight search index from your content at build time (and on-the-fly in serve mode). A bundled JavaScript library fetches the index on first interaction and scores results entirely in the browser – no backend required, works offline, zero external dependencies.

Accent CMS ships two search backends:

  • Simple Search (no license required) – JSON index with client-side substring matching. Lightweight, zero dependencies, works everywhere.
  • DocFind – WASM-powered search using FST fuzzy matching and RAKE keyword extraction. Handles typos, scales to large sites, and produces smaller indexes. Free in accent build and dev serve; a Standard or Pro license is required only to serve it dynamically in --production.

Quick Start

Add a search form to any template with the search_form() function:

{{ search_form() }}

Or embed one in a markdown page with the [search] shortcode:

[search]

That is all you need. The search index is generated automatically and the JavaScript is loaded on demand.

How It Works

Simple Search backend:

  1. Accent CMS scans all published pages and builds a JSON index containing each page’s title, URL, lead, tags, and a truncated plain-text excerpt.
  2. The index is served at /_search/index.json (serve mode) or written to _search/index.json (build mode).
  3. A small JavaScript library (/_search/search.js, ~5 KB) is loaded when the search form first appears on the page.
  4. On the first keystroke the library fetches the index, tokenizes the query, and scores every page using configurable field weights.
  5. Results appear instantly with highlighted matching terms, keyboard navigation, and a click-to-visit link.

DocFind backend:

  1. Accent CMS scans all published pages and feeds them into the DocFind engine, which builds an FST (finite state transducer) index with RAKE keyword extraction.
  2. The index is embedded into a WASM binary (docfind_bg.wasm) that contains both the search engine and the data.
  3. The client-side JS loads the WASM module on first interaction and runs queries entirely in the browser.
  4. DocFind supports fuzzy matching – queries like “templete” still find “template” – and scales better to large sites than Simple Search.

Template Function

{# Default placeholder and limit #}
{{ search_form() }}

{# Custom placeholder text #}
{{ search_form("Search the docs...") }}

{# Custom placeholder and max results #}
{{ search_form("Find articles...", 5) }}
ArgumentTypeDefaultDescription
placeholderstring"Search..."Placeholder text for the input field
results_limitinteger10Maximum number of results to display

The function returns safe HTML that can be placed in any template position – a header, sidebar, or dedicated search page.

Shortcode

Use the [search] shortcode inside any markdown page:

[search]

[search placeholder="Search docs..." limit=5]
ParameterDefaultDescription
placeholder"Search..."Placeholder text
limit10Maximum results

Configuration

All search settings are optional. The defaults work for most sites.

search:
  enabled: true           # Generate the search index (default: true)
  backend: auto           # auto | docfind | simple (default: auto)
  min_word_length: 2      # Minimum word length to index (default: 2)
  content_length: 200     # Characters of content per page (default: 200)
  exclude:                # URL patterns to exclude from the index
    - "/404"
    - "/tags/*"
  fields:                 # Field weights for relevance scoring
    title: 3.0            # Title matches score highest (default: 3.0)
    content: 1.0          # Body content (default: 1.0)
    tags: 2.0             # Tag matches (default: 2.0)
    lead: 1.5             # Lead/excerpt text (default: 1.5)
  docfind:
    category_field: section  # Frontmatter field for result categories (default: section)

Search Backend

The backend setting controls which search engine is used:

ValueBehavior
autoUses DocFind when available (accent build, dev serve, or --production with a Standard+ license), otherwise falls back to Simple Search. This is the default.
docfindRequires the DocFind backend. Falls back to Simple Search with a warning if the binary was not compiled with DocFind support, or in --production without a Standard+ license.
simpleAlways uses Simple Search (JSON-based), even on Standard+ editions.

In accent build and dev mode (accent serve without --production), DocFind is available with no license. Only --production serving requires a Standard or Pro license.

DocFind Settings

The docfind section configures the DocFind backend:

SettingDefaultDescription
category_field"section"Frontmatter field used to group results into categories. If the field is not present on a page, the top-level URL segment is used instead.

Field Weights

Weights control how much a match in each field contributes to a page’s relevance score. Higher values mean matches in that field rank higher. The weights are embedded in the JSON index and applied by the client-side scorer.

Exclude Patterns

Use glob patterns to keep pages out of the index. The * wildcard matches any characters except /:

  • "/404" – exact match
  • "/tags/*" – matches /tags/rust, /tags/web, but not /tags itself

Set search.enabled: false to skip index generation entirely. The /_search/index.json route will return 404.

Versioned Documentation

When versioned content is configured, search results are automatically scoped to the version the reader is currently viewing. A reader on /docs/v2.0/... sees only v2.0 results; the same topics in older or in-development versions are hidden, so a query no longer returns a near-duplicate hit per version.

The scoping is derived entirely from the URL – the index publishes the list of versioning roots, and both the Simple Search client and the search island keep only results whose version matches the reader’s. When the reader is not inside a version (for example the site home page), results fall back to each root’s default version. Pages outside any versioning root (home, blog, and so on) always appear regardless of version.

No configuration is required: scoping turns on automatically whenever versioning is enabled. Use the Simple Search backend (search.backend: simple) for scoped results today – the DocFind backend is not yet version-aware.

Editions

FeatureCore (free)StandardPro
Simple Search (accent serve)yesyesyes
Simple Search (accent build)yesyesyes
DocFind search (accent serve, dev mode)yesyesyes
DocFind search (accent serve --production)yesyes
DocFind search (accent build)yesyesyes

In accent build and dev mode (accent serve without --production), DocFind is available with no license. Only production serving requires a Standard or Pro license: without a valid license, accent serve --production falls back to Simple Search. Static builds are free, so accent build always produces DocFind output when backend is auto or docfind.

Build Mode

When you run accent build, the search files are written to the output directory. The exact files depend on the active backend and your license:

  • backend: auto or backend: docfind – generates the DocFind WASM index plus the Simple JSON index as a baseline. No license required.
  • backend: simple – generates the Simple Search JSON index only.

Simple Search output:

output/
  _search/
    index.json    # JSON search index
    search.js     # Client-side library

DocFind backend output:

output/
  _search/
    index.json       # Simple JSON index (baseline + fallback)
    docfind_bg.wasm  # WASM search engine with embedded index
    docfind.js       # JS glue for loading the WASM module
    search.js        # Client-side library (dual-backend)

The static build is fully self-contained – open index.html from a file server and search works without any backend.

Styling

The search UI uses CSS classes prefixed with acms-search-. The default theme includes styles in _search.scss. You can override them in your own theme:

ClassElement
.acms-search-formOuter container
.acms-search-inputText input field
.acms-search-resultsDropdown results container
.acms-search-resultIndividual result link
.acms-search-result-titleResult title
.acms-search-result-snippetResult snippet text
.acms-search-highlightHighlighted matching text
.acms-search-empty“No results found” message
.acms-search-staleAdded to the form when the DocFind index is being rebuilt (serve mode only)

The styles use CSS custom properties (--color-border, --color-bg, --color-accent, etc.) so they adapt to light and dark themes automatically.

Stale Index Indicator (DocFind)

In serve mode with the DocFind backend, the search form receives the .acms-search-stale CSS class while the WASM index is being rebuilt after a content change. You can style this to show a subtle indicator:

.acms-search-form.acms-search-stale .acms-search-input {
  border-color: var(--color-warning, #e6a817);
}

The stale class is removed automatically once the rebuild completes.

Keyboard Navigation

The search input supports keyboard navigation:

KeyAction
Arrow DownMove to next result
Arrow UpMove to previous result
EnterNavigate to the selected result
EscapeClose the results dropdown

The Simple Search JSON index uses short keys to minimize file size:

{
  "pages": [
    {
      "u": "/blog/my-post",
      "t": "My Blog Post",
      "l": "A short lead or content excerpt, shown in results",
      "ct": ["content", "deduplicated", "post", "text", "tokens"],
      "g": ["rust", "web"]
    }
  ],
  "weights": { "t": 3.0, "c": 1.0, "g": 2.0, "l": 1.5 }
}
KeyFull nameDescription
uURLPage URL path
tTitlePage title
lSnippetShort display snippet – the lead, or a content excerpt (omitted if empty)
ctContent tokensDeduplicated, lowercased content tokens used for matching (not raw text)
gTagsTag list (omitted if empty)

The page content is shipped pre-tokenized and deduplicated (ct) rather than as a raw plaintext blob. The client matches against these tokens directly, so it never re-tokenizes the whole corpus on each keystroke – this keeps search responsive even on large indexes – and the deduplicated tokens make the payload smaller than shipping the full text. The content field weight (weights.c) applies to matches found in ct.

Pages with status: draft, status: archived, or matching an exclude pattern are not included. Only published, listable pages appear in the index.

Raw HTML in content

Markdown may embed raw HTML, and layout markup is not something a reader can search for. Both backends therefore index the visible text of a page: tags, HTML comments, and the contents of <script> and <style> are dropped before the text is tokenized. A page that opens with

<header class="page-header">
  <div class="eyebrow">Coaching</div>
</header>

is indexed as Coaching, not as header, class, div and eyebrow – so searching for div no longer matches every page that happens to use one.

HTML shown inside a code block or an inline code span is kept verbatim, because there it is the subject of the page rather than its layout. This holds for fenced blocks, four-space indented blocks, and backtick spans alike, so a page documenting <div> stays findable by searching div.

Only text the renderer treats as markup is removed. Angle brackets that your page actually displays – a < b, a stray < in prose, an example written as <angle brackets ... – are indexed as the words they are, so anything a reader can see on the page is something they can search for.

Result titles and snippets are HTML-escaped when the client renders them, so markup that does reach a result – from a code sample, say – is displayed as text rather than interpreted as part of the page.

Tips

  • content_length controls match depth, not raw payload. It sets how much of each page is tokenized for matching (default 200 characters). Because the index ships deduplicated tokens rather than raw prose, raising it for deeper full-content matching grows the download far less than the character count suggests.
  • Exclude utility pages. Add /404, /tags/*, and other non-content pages to the exclude list to keep results relevant.
  • Place the form in your header. Using search_form() in your base template’s header makes search available on every page.
  • Check index size. After building, inspect _search/index.json to verify the file size is reasonable relative to your content. A typical 50-page site produces an index under 20 KB.