The opinion underneath it
Most search boxes are infrastructure cosplay: a request to a search endpoint, a spinner, a payload, all for what is usually a list of a few hundred names that the page could have shipped with in the first place.
This project's stance: if the catalog fits in the page, the search belongs in the page. Ship the index as JSON, score it in the browser on every keystroke, and there's nothing left to wait for. No search server, no framework, no library, one file. On The DJ Calendar, that's how site search has worked across roughly 1,000 pages.
The demo above proves the point in the counter under the search box: every keystroke rescores all 99 DJs in a fraction of a millisecond, and the network request count stays at zero.
How records are scored
Every keystroke scores every record. A record has to match every word you type somewhere, or it drops out (AND matching), so "house mafia" finds Swedish House Mafia and nothing else. Where each match lands decides the rank:
| Signal | Weight | Example |
|---|---|---|
| Title word, exact | 60 | "cox" hits Carl Cox |
| Title word, prefix | 40 | "ame" hits Amelie Lens |
| Keyword, exact | 30 | "techno" hits the genre field |
| Keyword, prefix | 18 | "tech" hits "techno" |
| Subtitle, exact / prefix | 12 / 8 | useful, but not the point |
| Loose substring | 4 | the safety net |
| Full-phrase bonus | +25 | "black coffee" beats token soup |
Ties go to the shorter title, so "Carl Cox" outranks a longer name that matches equally well, then to index order, which keeps results stable as you type. Tokens are precomputed once at load, so a keystroke scores arrays and never re-splits strings. Accents are folded too, which is why "tiesto" finds Tiësto and "kolsch" finds Kölsch.
The demo scores with the weights documented in the repo's README. Tick "Show how each result scored" above to see the math for each result.
The index contract
One JSON array. Three searchable fields and two display-only ones:
{
"title": "Carl Cox",
"subtitle": "Techno / House",
"keywords": "techno house icon ibiza space",
"badge": "Artist",
"url": "/artists/carl-cox"
}
title is required and weighted heaviest. subtitle is lightly searchable. keywords is the synonym field: genres, cities, nicknames, anything your visitors type that isn't in the title. badge is a display-only tag, and url is where Enter goes.
The production trick: the server renders the index once when the page is built, so the browser never fetches anything at search time. Search-time network work is zero, and the only payload is data the page wanted anyway.
Keyboard and ARIA, first-class
The input is a real ARIA combobox: role="combobox", aria-expanded, aria-controls and aria-activedescendant pointing into a role="listbox" of options, so screen readers announce the highlighted result as you move. Put the mouse away and try it on the demo:
| Key | What it does |
|---|---|
↓ / ↑ | Move the highlighted result |
Enter | Open the highlighted result, or go straight there if there's only one |
Escape | Close the list |
Matched text is highlighted with <mark>, and everything is escaped before it's rendered: index entries are treated as data, never as markup.
Use it in your project
Declarative: the index travels in an attribute and the widget builds itself.
<link rel="stylesheet" href="src/search.css">
<script src="src/search.js" defer></script>
<input type="text" data-instant-search='[
{"title": "Carl Cox", "subtitle": "Techno", "keywords": "techno house icon", "badge": "Artist", "url": "/artists/carl-cox"},
{"title": "Amelie Lens", "subtitle": "Techno", "keywords": "techno belgium", "badge": "Artist", "url": "/artists/amelie-lens"}
]'>
Programmatic, for records that live in JavaScript or arrive at runtime:
InstantSearch.attach(inputEl, records, { maxResults: 6, placeholder: "Find an artist..." });
From the repo's README. Options include minLength (default 2), maxResults (8), debounce (80 ms), emptyMessage and placeholder. Any browser from the last decade, no build step.
Related projects
Frequently asked questions
How does instant-search-index search without a server?
The page carries a prebuilt JSON array, and the browser scores those records locally as the visitor types. Search queries make no server round-trips.
How are search results ranked?
Every query token must match somewhere in a record. Exact and prefix matches in titles and keywords receive different weights, full-phrase matches get a bonus, and ties remain stable.
Can I use the search with a keyboard?
Yes. The input is an ARIA combobox: Arrow Up and Arrow Down move through options, Enter selects, and Escape closes the list.
What fields does an index record support?
Each record requires a title and can include a searchable subtitle and keywords plus a display badge and destination URL.
Need search that just feels fast?
Bryan Hamilton is a full-stack web engineer with 25+ years in PHP and JavaScript, available immediately for remote, hybrid or on-site roles in the NYC metro area.