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 buildand devserve; 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:
- 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.
- The index is served at
/_search/index.json(serve mode) or written to_search/index.json(build mode). - A small JavaScript library (
/_search/search.js, ~5 KB) is loaded when the search form first appears on the page. - On the first keystroke the library fetches the index, tokenizes the query, and scores every page using configurable field weights.
- Results appear instantly with highlighted matching terms, keyboard navigation, and a click-to-visit link.
DocFind backend:
- 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.
- The index is embedded into a WASM binary (
docfind_bg.wasm) that contains both the search engine and the data. - The client-side JS loads the WASM module on first interaction and runs queries entirely in the browser.
- 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) }}
| Argument | Type | Default | Description |
|---|---|---|---|
placeholder | string | "Search..." | Placeholder text for the input field |
results_limit | integer | 10 | Maximum 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]
| Parameter | Default | Description |
|---|---|---|
placeholder | "Search..." | Placeholder text |
limit | 10 | Maximum 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:
| Value | Behavior |
|---|---|
auto | Uses DocFind when available (accent build, dev serve, or --production with a Standard+ license), otherwise falls back to Simple Search. This is the default. |
docfind | Requires 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. |
simple | Always 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:
| Setting | Default | Description |
|---|---|---|
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/tagsitself
Disabling Search
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
| Feature | Core (free) | Standard | Pro |
|---|---|---|---|
Simple Search (accent serve) | yes | yes | yes |
Simple Search (accent build) | yes | yes | yes |
DocFind search (accent serve, dev mode) | yes | yes | yes |
DocFind search (accent serve --production) | – | yes | yes |
DocFind search (accent build) | yes | yes | yes |
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: autoorbackend: 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:
| Class | Element |
|---|---|
.acms-search-form | Outer container |
.acms-search-input | Text input field |
.acms-search-results | Dropdown results container |
.acms-search-result | Individual result link |
.acms-search-result-title | Result title |
.acms-search-result-snippet | Result snippet text |
.acms-search-highlight | Highlighted matching text |
.acms-search-empty | “No results found” message |
.acms-search-stale | Added 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:
| Key | Action |
|---|---|
| Arrow Down | Move to next result |
| Arrow Up | Move to previous result |
| Enter | Navigate to the selected result |
| Escape | Close the results dropdown |
Index Format (Simple Search)
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 } }
| Key | Full name | Description |
|---|---|---|
u | URL | Page URL path |
t | Title | Page title |
l | Snippet | Short display snippet – the lead, or a content excerpt (omitted if empty) |
ct | Content tokens | Deduplicated, lowercased content tokens used for matching (not raw text) |
g | Tags | Tag 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_lengthcontrols 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.jsonto verify the file size is reasonable relative to your content. A typical 50-page site produces an index under 20 KB.