# Search Experience (HawkSearch × Caster) — Reasoning & Recommendations

**Companion to:** [feature-design-guidelines.md](feature-design-guidelines.md), [USAClean_IA_Search_Roadmap.md](USAClean_IA_Search_Roadmap.md), [megamenu-concepts.md](megamenu-concepts.md), [model-landing-concepts.md](model-landing-concepts.md)
**Live preview:** https://demo.thecxlabs.com/v2/index.html (hero + autocomplete) · https://demo.thecxlabs.com/v2/plp.html (query SERP + facets)
**Date:** June 2026

This document explains how to bring USA Clean's **search experience** under the Caster design system. Search is the single most-used surface on the site — and it is powered by HawkSearch, an already-deployed third-party app whose front end renders visually-foreign UI through its own templating and a shadow-DOM web component. The core question this brief answers is not "which search app should we buy" (we have one), but **"how do we make HawkSearch look and behave like Caster — and which surfaces should we take away from Hawk entirely."**

---

## TL;DR

Search drives **32.1% of all sessions — ~10× the browse landing** ([IA roadmap](USAClean_IA_Search_Roadmap.md)). HawkSearch (`@bridgeline-digital/hawksearch-handlebars-ui`) already powers it, but renders through Hawk's Handlebars templates plus a `hawksearch-search-results` web component that carries **its own shadow DOM** — so Caster's structural classes (`.product-card`, `.btn`) can't reach inside, and the result looks nothing like the rest of the redesigned site.

The recommendation splits by surface, because the right answer is different for the **entry surfaces** (where intent is captured) than for the **results surfaces** (where Hawk's engine does real work):

- **Own the entry surfaces as native Caster.** The hero search box, the autocomplete panel, and the machine-lookup affordance are where users start, where Hawk's stock UI is weakest, and where we want full control. Build them as native Caster components that call Hawk's search/suggest API but render our own markup.
- **Skin the results surfaces in place.** The SERP grid and facet rail are Hawk's strength. Override Hawk's Handlebars templates and ship a `.hawk-results`-scoped Caster skin (tokens cross the shadow boundary; structure is overridden in Hawk's own templates) rather than rebuilding Hawk's filtering from scratch.
- **Fix the `searchRequest` race** (blank grid on cold `/search` loads) as code hygiene in Phase 1.

| Phase | Surface | Effort |
|---|---|---|
| **Phase 1** _(Own the front door)_ | Native Caster **hero search**, **autocomplete panel**, and **machine-lookup** affordance, calling Hawk's suggest/search API. Fix the `searchRequest` null-reference race. Drop a token-level `.hawk-results` skin on the existing Hawk SERP so it stops clashing. | **Medium** — Caster front-end + one theme-JS bug fix |
| **Phase 2** _(Reclaim the SERP)_ | Full Hawk **Handlebars-template override**: results render Caster `.product-card`s, facets render the Caster accordion + removable chips + pagination. Compatibility-aware results via the `compatible_models[]` field shared with model-landing. | Higher — template override + content ops |
| **Phase 3** _(Replace the rendering)_ | Swap Hawk's Handlebars UI for its **React/headless SDK** rendered as native Caster components (keep Hawk's API/index). Personalized result ranking for signed-in fleets + AI/concept facets. | Highest — **Catalyst-replatform candidate** |

---

## Current state

> **Confirmed against the theme source** ([quasiobject/usa-clean](https://github.com/quasiobject/usa-clean), audited June 2026). The integration is more controllable than first assumed — three corrections carry through the rest of this brief:
>
> 1. **Vendored, not CDN.** `hawksearch-handlebars-ui` is committed into the theme at `assets/js/hawksearch/` and webpack-built (env vars `WEBPACK_HAWKSEARCH_{CLIENT_ID,SEARCH_URL,AUTOCOMPLETE_URL,RECOMMENDATIONS_URL,TRACKING_URL}`). Entry point: `assets/js/hawksearch/index.js`, run from `app.js`.
> 2. **The theme already overrides Hawk's templates.** `index.js` registers theme-owned Handlebars templates for every major component — `search-results`, `search-results-item` / `-list`, `facets-list`, `autocomplete`, `search-field`, `sorting`, `page-size` (in `assets/js/hawksearch/templates/*.hbs`). The header search is `<hawksearch-search-field>` (`templates/components/common/quick-search.html`; native BC quick-search is commented out). **We already control the markup.**
> 3. **Shadow DOM is not a blocker — there's a sanctioned injection point.** Hawk renders into `hawksearch-*` web components (some with shadow DOM), but the init passes `css: { customStyles: 'hawksearch-custom-styles' }`, and `templates/usac/components/hawksearch-custom-styles.html` is a `<template>` whose `<style>` opens `:host, :root { --hawksearch-color-accent: #00209F; … }` — i.e. it is **adopted inside the shadow roots**. Any CSS placed there, Caster's included, reaches inside the encapsulation.
>
> Net: bringing search under Caster is concentrated, well-scoped work in two places — the `.hbs` templates (markup → Caster classes) and `hawksearch-custom-styles` (CSS → Caster tokens/components) — plus the `searchRequest` null-guard. It is **not** a fight against encapsulation.

USA Clean's `/search` is powered by HawkSearch (Bridgeline Digital), via the **vendored** `@bridgeline-digital/hawksearch-handlebars-ui` library (committed in the theme, webpack-built) wired into the BigCommerce Stencil theme. It renders through **theme-owned Handlebars templates** mounted inside a tree of `hawksearch-*` web components — the `/search` page mounts `<hawksearch-search-results>`, which contains `hawksearch-facets-list`, `hawksearch-search-results-list`, `hawksearch-autocomplete`, `hawksearch-sorting`, `hawksearch-pagination`, and the theme's own `pagination-summary` element (which **does** carry its own shadow DOM).

What's wrong today:

- **It's visually foreign.** Hawk injects its own component CSS late in the page (its own `.button`, its own result-card markup). The redesigned Caster site stops at the edge of the search results and a different design language takes over. The [stencil-integration doc](../design-system/docs/stencil-integration.html) already flags this and prescribes `.hawk-results .btn { … }` scoping as the override mechanism.
- **A known race condition blanks the grid.** The theme's `partSearchLinks` function reads `HawkSearch.searchRequest` before Hawk's async init resolves — `Cannot read properties of null (reading 'searchrequest')`, 251 sessions. On a cold `/search` load Hawk counts 268,271 results but renders zero. Limited real-world impact (most `/search` sessions arrive in-site where Hawk is already initialized) but it's a real bug.
- **The front door is generic.** The homepage leads with browse CTAs, not search — despite the 9.8× search:browse ratio. Autocomplete is Hawk's stock suggestions, not tuned to how facility buyers think (part number / brand / **machine model**).
- **There's no machine-lookup intercept.** The dominant job is "*I have a Tennant T5, I need its parts*," but nothing captures a brand+model at the point of search. Hawk already has the data; the UI doesn't expose it.
- **Hawk contributes to CLS** (0.29, above Google's 0.25 "poor" threshold) by injecting the results component after first paint.

**Search is already the navigation; the redesign should make search *feel* like the rest of the site and meet the brand→model mental model head-on.**

---

## References

### Grainger
Best-in-class industrial autocomplete: as you type, the panel splits into **suggested terms**, **categories**, and **product previews with thumbnails + price**. The model to emulate for the Caster autocomplete panel — grouped, scannable, keyboard-navigable.

### McMaster-Carr
The gold standard for industrial faceted search: instant results, server-fast facets, no full-page reload, dense scannable rows. Sets the bar for what "reclaiming the SERP" should feel like — Hawk's engine can approach this; its stock UI does not.

### Sweepscrub
Parts search scoped to cleaning equipment by brand→model. Same catalog shape as USA Clean; their search is a flat filtered list with no machine intercept — the gap our machine-lookup affordance closes.

### Tennant.com (OEM)
Model-scoped search routes to a spec/doc page but has no parts-purchase path. Confirms the brand→model intent is universal; USA Clean's opportunity is to be the rich, *buyable* search destination for every machine.

### Baymard Institute — B2B search & autocomplete
B2B research shows part-number / SKU search and "scoped search" (search *within* a machine's compatible parts) are high-value patterns buyers expect. Autocomplete that previews products, not just query strings, measurably reduces abandonment.

### The search-app comparison class — Algolia / Klevu / Searchspring
The peer set for HawkSearch. Relevant only to confirm that **swapping the engine is not warranted** (see shortlist): Hawk's relevance/merchandising/facets are competitive; the deficiency is the front-end skin, which is ours to fix.

---

## What BC Stencil gives us

- **HawkSearch is already integrated and paid for.** The engine (index, relevance, facets, synonyms, "did you mean," keyword redirects, recommendations, merchandising/pinning, analytics) is configured in the Hawk dashboard and serves the `/search` template. No engine work needed.
- **Hawk's Handlebars UI is template-overridable.** The `@bridgeline-digital/hawksearch-handlebars-ui` library renders from Handlebars partials we can override, and accepts supplemental CSS. This is the hook for the Phase 2 Caster skin — we replace Hawk's result/facet templates with markup that uses Caster classes.
- **Hawk's suggest/search API is callable directly.** The autocomplete and machine-lookup surfaces (Phase 1) can query Hawk's API and render **our own** Caster markup — we don't have to accept Hawk's stock suggestion UI.
- **`/search` Stencil template override.** `templates/pages/search.html` is ours to control for the page chrome around Hawk's component.
- **Product custom field `compatible_models[]`** (the same field that powers [model-landing](model-landing-concepts.md)) lets results and facets be compatibility-aware — "parts that fit your Tennant T500e" as a Hawk facet or a scoped search.
- **Storefront API** for any supplemental product data the result cards need (stock, price tiers).

**Sources:** [HawkSearch docs](https://learn.hawksearch.com/), [Hawk Handlebars UI (npm)](https://www.npmjs.com/package/@bridgeline-digital/hawksearch-handlebars-ui), [BC Stencil — Search template](https://developer.bigcommerce.com/docs/storefront/stencil/themes/search), [BC — Adding Custom Fields](https://support.bigcommerce.com/s/article/Adding-Custom-Fields).

---

## Stencil limitations

- **Shadow DOM is encapsulated, but the theme has a sanctioned styling channel.** Hawk's `hawksearch-*` components use shadow DOM, so global `components.css` and a `.hawk-results .btn {…}` scope do **not** reach inside. The override path that *does* work is already wired: author CSS into the `hawksearch-custom-styles` template (adopted into the shadow roots — it already defines `--hawksearch-color-accent` etc.) and edit the theme's `.hbs` templates for structure. The real cost is that Caster's search CSS lives in that Hawk-specific template, **parallel to** `components.css` — not that the styling is impossible.
- **Two templating systems to maintain.** Search markup lives in `assets/js/hawksearch/templates/*.hbs` (Hawk's Handlebars), separate from Stencil's page templates. Already true today; porting to Caster edits an existing system rather than adding one.
- **Bespoke autocomplete groups need API-level work.** `search-autocomplete.hbs` already groups suggested-queries / categories / content / products — restyling those to Caster is template + CSS work. But groups Hawk's response doesn't provide (e.g. **recent / saved machines** for signed-in fleets) require joining the account to the suggest call — custom front-end beyond a restyle.
- **No machine-lookup intercept in stock Hawk.** Brand+model → scoped parts is a custom affordance on top of Hawk's search API.
- **The `searchRequest` race is theme-JS, not Hawk.** Confirmed at `assets/js/hawksearch/index.js:173`, inside the theme's `pagination-summary` shadow element: `if (HawkSearch.searchRequest.query)` runs on `hawksearch:bind-pagination` before `searchRequest` resolves on cold loads. Fix = `HawkSearch.searchRequest?.query`. (That heading is also hard-coded `font-size:25px; font-weight:700` inline — a Caster-token opportunity.)
- **CLS from late injection.** Reserving space for `<hawksearch-search-results>` (min-height / skeleton) is a theme-template fix.

---

## App marketplace shortlist

The usual "native vs. app vs. custom" table is inverted here: **HawkSearch *is* the app, and it's already deployed.** The real decision is what to do with its front end.

| Option | What it is | Verdict |
|---|---|:---:|
| **Skin Hawk in place** (edit the theme's `.hbs` templates + the shadow-injected `hawksearch-custom-styles`) | Keep Hawk's engine; the theme **already** owns the templates + a shadow-DOM stylesheet — port both to Caster. | ✅ **Already the architecture.** Phase 2 = port markup + CSS to Caster, not build override infrastructure. |
| **Own the entry surfaces as native Caster** (hero search, autocomplete, machine lookup calling Hawk's API) | Custom Caster front end on top of Hawk's suggest/search API. | ✅ **Phase 1 path.** Narrow surface, highest intent, where Hawk's stock UI is weakest. Custom build is clearly the right call (Rule #3: narrow surface, Caster fit). |
| **Swap the search engine** (Klevu / Searchspring / Algolia) | Replace HawkSearch with a different search app. | ❌ **Skip.** Hawk's relevance/facets/merchandising are competitive; the problem is the skin, not the engine. Re-indexing + re-merchandising + a new subscription to solve a CSS problem fails the amortization test — the front-end fix costs a fraction of an engine migration. |
| **BC native faceted search** | Drop Hawk, use Stencil's stock faceted search. | ❌ **Skip.** A capability downgrade (no synonyms/concept search/merchandising tuning) to win styling we can get by skinning Hawk. |
| **Replace Hawk's *rendering* with its React/headless SDK** | Keep Hawk's API/index; render results as native Caster React components. | ⏳ **Phase 3 / Catalyst.** Best long-term fit, but only justified alongside personalization + the headless roadmap (see candidacy). |

**Amortization note (Rule #3):** swapping engines carries a recurring new-subscription cost *plus* a large migration; the front-end skin is a one-time build against an engine we already pay for. The custom entry surfaces are a one-time build whose payback is immediate (the front door of a 32%-of-sessions surface) — no subscription competes with them.

---

## Caster impact

**New patterns introduced inline in `v2/` first (Rule #7), promoted to Caster when a second consumer arrives:**

- **`.search-autocomplete`** — combobox/suggestions panel: grouped sections (products with thumb+price, categories, popular searches, recent machines), keyboard-navigable, built on the existing `dropdown.*` tokens.
- **`.search-hero`** — search-dominant hero block with model-aware placeholder and inline scope chips.
- **`.facet-group`** — collapsible accordion filter group (the gap between Caster's `.checkbox` and a real facet rail).
- **`.filter-chip`** — removable applied-filter chip (× to clear) — a `.pill` variant with a remove affordance.
- **`.pagination`** — page controls / load-more (Caster has none today).
- **`.input--search`** — input with a leading magnifier icon (Caster's `.input` has no leading-icon slot).
- **`.facet-drawer`** — off-canvas mobile facet panel (37% of traffic is mobile).
- **`.hawk-results` skin** — the scoping convention from [stencil-integration.html](../design-system/docs/stencil-integration.html): `.hawk-results { --caster-…: … }` to pass tokens across the shadow boundary, plus `.hawk-results .btn { … }` for any non-encapsulated overrides.

**Reused as-is:** `.input` / `.input-group`, `.product-card`, `.pill`, `.stock-badge`, `.checkbox`, `.tabs`, `.container`, `.site-header__search`, `.machine-card` (for machine-lookup results).

**Downstream impact:** the autocomplete panel and `.input--search` also serve the global header search on every page; the facet patterns serve any future filtered listing (category pages, model-landing parts grids). These graduate to Caster quickly because they have multiple consumers by design.

---

## Phased recommendation

### Phase 1 — Own the front door

Ship the three entry surfaces as native Caster, calling Hawk's API; fix the race; stop the SERP from clashing.

1. **Elevated hero search** (roadmap #8) — search input as the dominant homepage element, model-aware placeholder ("Search by part number, brand, or machine model"), optional scope chips (All / Parts / Machines).
2. **Caster autocomplete panel** — on focus/typing, grouped suggestions: products (thumb + SKU + price + stock), categories, popular searches, and recent/saved machines for signed-in users. Full keyboard + ARIA combobox semantics.
3. **Machine-lookup affordance** — brand-select + model/serial entry that routes to a compatibility-scoped result set (and to the model-landing page when one exists). Shares the [megamenu](megamenu-concepts.md) "Find by machine" story.
4. **Bug fix** — guard `HawkSearch.searchRequest` / move `partSearchLinks` to the `hawksearch:search-complete` event; reserve result-area height to cut CLS.
5. **Token-level `.hawk-results` skin** — pass `--caster-*` into Hawk's shadow DOM so the existing SERP adopts Caster color/type immediately, before the full template override.

**What "winning" looks like:** higher search-initiation rate from the homepage, autocomplete-selection rate climbing (users picking suggestions rather than submitting raw queries), machine-lookup usage, and the cold-load blank-grid error going to zero.

### Phase 2 — Reclaim the SERP

Override Hawk's Handlebars templates so results and facets render as Caster:

- Result cards → `.product-card` (with compatibility note when `compatible_models[]` matches the user's fleet).
- Facet rail → `.facet-group` accordion + `.filter-chip` applied-filter row + `.pagination`.
- Mobile → `.facet-drawer`.
- Compatibility-aware results: a "fits my equipment" Hawk facet driven by `compatible_models[]`.

**Trigger:** Phase 1 is live and the token skin proves the SERP is the remaining visual seam; merch has begun populating `compatible_models[]` (shared with model-landing Phase 2).

### Phase 3 — Replace the rendering

Swap Hawk's Handlebars UI for its **React/headless SDK**, rendered as native Caster components (Hawk's API/index unchanged). Adds personalized result ranking for signed-in fleets ("parts your fleet has bought for this model first") and AI/concept facets.

**Catalyst-replatform candidate** — see below.

---

## Implementation notes

### Where the Caster work lands (confirmed file map)
The whole search redesign is two files of theme work plus a one-line fix:
- **Markup → Caster classes/structure:** `assets/js/hawksearch/templates/*.hbs` — `search-results.hbs` (SERP layout), `search-results-item.hbs` (result card → `.product-card`), `search-facets-list.hbs` (facet rail → accordion), `search-autocomplete.hbs` (the grouped panel), `search-field.hbs` (the input).
- **CSS → Caster tokens/components:** `templates/usac/components/hawksearch-custom-styles.html` — the `<style>` adopted into the shadow roots. Bridge `--hawksearch-*` to Caster values (e.g. `--hawksearch-color-accent: var(--caster-color-brand-default)`) and add the `.product-card` / facet / autocomplete component CSS **here** (not in the global `components.css`, which can't reach the shadow DOM). Pull Caster tokens in via an `@import` or by inlining the token layer into this template.
- **Bug fix:** `assets/js/hawksearch/index.js:173` → `HawkSearch.searchRequest?.query`.
- The header search (`<hawksearch-search-field>` in `quick-search.html`) and the autocomplete are the same component pair site-wide, so a single restyle covers the header and any hero placement.

### The `searchRequest` fix
Null-guard `HawkSearch.searchRequest?.query` in `partSearchLinks`, or move the call into a `hawksearch:search-complete` event listener so it never runs before Hawk's async init. Reserve the result container's min-height to cut the post-paint shift.

### Autocomplete accessibility
ARIA combobox pattern: `role="combobox"` input with `aria-expanded` / `aria-controls`, `role="listbox"` panel, `aria-activedescendant` tracking the highlighted option; full keyboard support (↑/↓, Enter, Esc, Home/End); group headings are non-focusable labels. Debounce the suggest call; show a loading affordance; handle zero-suggestions gracefully.

### Compatibility data
Compatibility-aware results/facets read the product `compatible_models[]` custom field — the same data investment as [model-landing](model-landing-concepts.md) Phase 2. Tagging the ~12k-part catalog is the shared content-ops cost; sequence it once for both features.

### Analytics
- Search-initiation rate (homepage hero) and autocomplete-selection rate.
- Zero-result query rate (feeds Hawk synonym/redirect tuning in the dashboard).
- Machine-lookup usage and its conversion vs. plain search.
- Search → result-click → PDP → cart funnel; cold-load error count (should hit zero post-fix).

### Mobile behavior
Search pinned to the top bar; autocomplete panel full-width below it; facets collapse into the `.facet-drawer` off-canvas panel (37% of traffic is mobile, and mobile users are disproportionately forced into search because the nav fails at 375px — roadmap #10).

---

## Catalyst / replatform candidacy

**Phase 1 + Phase 2 ship in Stencil.** Native Caster entry surfaces calling Hawk's API, a theme-JS bug fix, and a Handlebars-template override are all Stencil-native work. No replatform needed to bring search under Caster.

**Phase 3 is the Catalyst case.** HawkSearch ships **React/headless SDKs** that fit a Catalyst storefront far better than its Handlebars UI fits Stencil — rendering Hawk results as first-class React components eliminates the shadow-DOM/encapsulation friction entirely, and personalized server-side ranking (fleet-aware result order on first paint) is exactly what Stencil templates struggle to do without jitter. Combined with the broader headless roadmap and the model-landing Phase 3 personalization, search rendering is a natural anchor for the replatform — but it is **not** a reason to replatform on its own. Skinning Hawk in Stencil (Phases 1–2) buys the visual consistency now; Phase 3 waits for the personalization + headless forcing function.

---

## Sources

- [USAClean_IA_Search_Roadmap.md](USAClean_IA_Search_Roadmap.md) — search is 32% of sessions; the `searchRequest` race; roadmap items #8 (elevate search) and #12 (machine lookup); confirmed Hawk library + shadow-DOM inspection
- [stencil-integration.html](../design-system/docs/stencil-integration.html) — Hawk CSS-collision guidance and the `.hawk-results` scoping convention
- [megamenu-concepts.md](megamenu-concepts.md) — "Find by machine" intent intercept at the nav level
- [model-landing-concepts.md](model-landing-concepts.md) — `compatible_models[]` data model shared with compatibility-aware search
- [feature-design-guidelines.md](feature-design-guidelines.md) — operating manual (the eight rules + brief template)
- [HawkSearch — Learn / docs](https://learn.hawksearch.com/)
- [HawkSearch Handlebars UI (npm)](https://www.npmjs.com/package/@bridgeline-digital/hawksearch-handlebars-ui)
- [BC Stencil — Search template](https://developer.bigcommerce.com/docs/storefront/stencil/themes/search)
- [BC — Adding Custom Fields](https://support.bigcommerce.com/s/article/Adding-Custom-Fields)
- [Baymard Institute — ecommerce search & autocomplete UX](https://baymard.com/blog/autocomplete-design)
- [Grainger](https://www.grainger.com/) — grouped autocomplete reference
- [McMaster-Carr](https://www.mcmaster.com/) — industrial faceted-search reference
- [Sweepscrub](https://www.sweepscrub.com/) — cleaning-equipment parts-search pattern
