← All projects Case study · Storefront script

Search V2

An on-site search overlay and autocomplete that attaches to a storefront's own search box with one script. Typo tolerance, facets, synonyms, redirects and ten layout presets, configured per brand without code. Live on 271 brands.

  • 271brands live
  • 10presets: 4 shells × 16 blocks
  • ~9klines across v2 and SearchKit
  • 0dependencies at runtime
Try it

Search an invented store

Click the search box or press / inside the demo. Try a typo like "lnen", a synonym like "tee", or switch presets.

atelier-nord.example · synthetic catalogueInteractive demo
The interactive search needs JavaScript. It shows a storefront whose search box opens an overlay with autocomplete, category chips, sorting, facets, typo correction and ten SearchKit layout presets and the classic V2 overlay over a synthetic catalogue.
Same client-side behaviour as production, with an in-memory catalogue and simulated network latency in place of the search service.
Problem

The old search widget was styled by hand for each brand, had no typo tolerance and no filters, and brands kept sending screenshots of competitors' search.

Every storefront has a different header, a different search input and its own CSS reset. The backend search service returns ranked products and categories but does not handle typos or narrowing by attribute. A replacement had to work on hundreds of sites without per-site code.

My role

I wrote Search V2 and its preset engine, SearchKit, as the only author, and the tooling used for the bulk rollout. The search service behind it is an existing Lucene-based endpoint I integrated with.

  • One IIFE bundle built with esbuild that creates its own DOM and CSS
  • Per-brand config: input selector, native panel to hide, synonyms, redirects, recommended queries, preset, CSS
  • Client-side typo tolerance, Turkish folding, facets with counts, sorting, zero-result recovery
  • Search and click events for analytics
Architecture

From keystroke to results

  1. AttachLoader picks the bundle, binds to the store's own input by selector and hides the native panel.
  2. ConfigBrand settings fetched once: preset, synonyms, redirects, suggested queries, CSS.
  3. Search serviceExisting endpoint returns ranked products and categories.
  4. Client layerFolding, typo fallback, facets, sort, stale-response guard.
  5. Render + trackPreset shell and blocks, search and click events.

Violet tint: parts I wrote.

Engineering decisions

What was hard and what I did

CSS that survives 271 other stylesheets

I first moved the base styles into @layer. Brands' unlayered resets beat layered rules, so I rolled it back and went with prefix-scoped selectors that out-rank a brand reset without !important everywhere. A cascade test page has to pass 8 of 8 before release.

No stale results

Every keystroke aborts the previous request with an AbortController, and a sequence guard drops anything that arrives late. Loading is a small state machine: skeleton after 550 ms, a "slow" notice at 3 s, timeout at 10 s.

Turkish text, done properly

Folding treats dotless ı as its own letter, repairs mis-decoded characters without decoding twice, and parses prices like 31.000,00 TL correctly. A wrong parse once showed 31 instead of 31,000.

Typos the backend doesn't catch

When a query returns nothing, the client compares it with a vocabulary built from product titles and offers "did you mean", then falls back to recommended products instead of an empty page.

Keyboard and screen readers

The input is an ARIA combobox with a listbox, results support arrow keys, / focuses search, focus is trapped while the overlay is open and returns on close, and reduced motion turns off movement.

A field guide for "it won't open"

On real sites the usual causes were dead selectors, consent banners holding scripts back, other vendors' search scripts and cached config. I wrote them down with fixes so support could solve most cases without me.

Before V2

Search V2 grew out of earlier work on a searchandising results page: a config-driven script that renders the full results page after a search, with category, banner and sorting rules per brand. It taught me where per-brand code goes wrong, which is why V2 pushed everything brand-specific into configuration.

Have a problem worth solving?Let's make it reliable

Start a conversation

Open to full-stack and applied AI roles, remote or hybrid.

Tell me about the role, the team and the problem.