Product Genius Docs

ProductGeniusSearchBar

The Product Genius search bar mount for React storefronts — a working native search styled as a skeleton that the live bar replaces.

Skip this page unless your store also uses Product Genius search, not just the feed.

On Shopify Liquid themes, search-bar placement ships as a theme app extension block, "PG Search Bar" — see the Shopify section. <ProductGeniusSearchBar> is the same thing for storefronts without a Liquid theme.

Your header
import {ProductGeniusSearchBar} from '@productgenius/react';

// full-width bar:
<ProductGeniusSearchBar placeholder="Search products…" maxWidth={400} />

// or icon-only — a compact square the live bar expands on focus:
<ProductGeniusSearchBar layout="icon" />

What you are mounting

The live bar that replaces the skeleton is Product Genius search: LLM-based query understanding that handles typos, synonyms, and ambiguous terms; abstract queries like "a gift for my mom"; results that keep refining as the shopper scrolls; and a transition into interest-based suggestions as results are exhausted. Ranking policies for shopper segments, and the live bar's own styling, are configured on the Product Genius side with your contact — your storefront's job is to give it a mount point in the right place.

Progressive enhancement, not a placeholder

What the component renders is a working native search styled as a loading shimmer: a real <form method="get"> pointed at your own search route in bar layout, or a real link to that route in icon layout. The Product Genius bundle replaces it with the live search bar once it mounts.

Until then — and if the bundle never loads at all — your visitors still have search that works. That is the intended posture, not a degraded one.

The component is purely presentational: no effects, no globals, no data fetching. It renders in your initial server HTML, so the skeleton paints with the page instead of after hydration, and it never causes layout shift.

The handoff is attribute-driven

When the live bar mounts into the container, the bundle sets data-pg-search-ready="true" on it and CSS hides the skeleton. The skeleton stays in the DOM — React and the bundle never fight over the container's children.

Prerequisites

Two things have to be true before the live bar can appear.

The bundle must be loaded on the page

<ProductGeniusSearchBar> does not load it — <ProductGeniusFeed> does. Render the feed component on every page that shows the search bar. On non-product routes, omit pageConfig; its mount node simply stays empty unless your store's configuration renders a feed there.

When the live bar appears depends on the page: where your configuration renders a feed, the search bar mounts together with it (after the feed's container resolves and its first fetch completes); on pages without a feed it mounts standalone. Until then, and if it never mounts, the skeleton stays a working native search.

Your store's search configuration must be enabled

Set by your Product Genius contact, like the rest of your store configuration:

KeyWhat it does
search.pagePatternsThe pages where the search bundle may mount. Without it, search stays off on pages that don't render a feed.
design.search.enabledEnables the search bar on feed pages.
design.search.containerMust be [".pg-search-mount"] — the class this component renders.
design.search.action.inputMust match your layout: "open" for bar, "collapsed" for icon.

Placement rules

One mount per page

The bundle resolves the container with querySelector — first match — and mounts one live bar into it. A second <ProductGeniusSearchBar> never goes live; its skeleton shimmers forever. For a desktop/mobile pair, render a single instance conditionally. Do not render two and hide one with CSS: a display: none first match still wins querySelector.

It must exist at bundle init

The bundle looks for .pg-search-mount once, when it initializes. Render the component somewhere that exists at page load — a persistent header — not inside lazily-mounted UI like a search drawer or a modal. A skeleton that enters the DOM later is only picked up after a full bundle reboot, which means <ProductGeniusFeed> unmounting and remounting.

If your design opens search in a drawer or modal, put the component in your persistent header anyway and let the live bar handle its own expansion — that is what layout="icon" is for: a compact square in the header that the live bar widens on focus.

Props

PropTypeDefaultNotes
layout'bar' | 'icon''bar'bar renders a full search field; icon renders an icon-only square that links to your search page, which the live bar expands on focus. Must match the store's design.search.action.input ("open" / "collapsed").
placeholderstring'Search'The input placeholder, and the accessible label of the input (or of the icon link). Match your store's configured placeholder so the text doesn't change at handoff.
maxWidthnumber320Container max-width in px. In icon layout, also the width the live bar expands to on focus.
heightnumber38Bar height in px. Match your store's configured live-bar height so the handoff is shift-free; the default matches the Product Genius default.
actionstring'/search'Your native search page — the fallback form's GET target and the icon link's href. The default is Shopify's route; point it at your own otherwise.
inputNamestring'q'The query-param name the fallback form submits (/search?q=…). Default is Shopify's param. bar layout only — the icon layout renders a plain link, with no input.
onSubmitFormEventHandler<HTMLFormElement>—Intercept the fallback form's submit, e.g. preventDefault() and push to your client-side router. Omit it to keep the plain GET, which works with no JS at all. bar layout only.
noncestring—CSP nonce for the inline <style> this component renders. See below.
idstring—Passed to the container.
classNamestring—Merged after pg-search-mount, which the bundle's container selector needs.
styleCSSProperties—Merged after the sizing custom properties, so it wins per-property.

action lands in an href verbatim

action must be a same-site path or an https: URL you control. Never feed untrusted (CMS- or user-supplied) data into it.

RSC note: functions don't cross the server-to-client boundary, so passing onSubmit requires the component that renders the search bar to be a Client Component (in the Next.js App Router, for example). Without onSubmit, you can render it straight from a Server Component.

Styling the skeleton

Styles are injected as a small inline <style> per instance — nothing to import, SSR-safe. The skeleton's colours, radius, and sizing are CSS custom properties on the container, declared at zero specificity so any rule of yours overrides them:

Custom propertyDefaultApplies to
--pg-skeleton-radius0pxCorner radius
--pg-skeleton-bg#ffffffBackground
--pg-skeleton-border1px solid #d4d0cdBorder shorthand
--pg-skeleton-icon-color#8a8a8aThe magnifier icon
--pg-skeleton-color#1f1f1fTyped text
--pg-skeleton-placeholder-color#9f9f9fPlaceholder text
--pg-skeleton-sizefrom height (38px)Bar height, and the icon square's width
--pg-compact-expanded-widthfrom maxWidth (320px)icon layout: width while focused

Override them from your own stylesheet:

.pg-search-mount {
  --pg-skeleton-radius: 8px;
  --pg-skeleton-bg: #faf9f7;
}

Or per instance via the style prop: style={{'--pg-skeleton-radius': '8px'} as CSSProperties}.

Two caveats:

  • If you override sizes through style rather than the maxWidth prop, set --pg-compact-expanded-width alongside it — in icon layout the expanded width is derived from the prop, so a style={{maxWidth}} override moves the container cap but not the expanded width.
  • On a hybrid page that also loads the Shopify theme app extension's pg-search.css, that stylesheet declares the same defaults at class specificity, so an equal-specificity override of yours can lose on document order. The style prop override always wins there.

This styles the skeleton only. The live search bar's appearance is part of your store's Product Genius design configuration, like the feed's. The skeleton cannot read the live bar's variables — they are scoped to the bundle's own markup and don't exist until it mounts — so set the skeleton's height, colours, and radius to match your store's configured bar and the swap is seamless.

The shimmer animation is disabled under prefers-reduced-motion: reduce, and the fallback input carries a visible :focus-visible outline, since pre-handoff it is the only focusable control.

Placeholder contrast

The default placeholder colour (#9f9f9f on white, roughly 2.6:1) mirrors the Product Genius default and is below WCAG AA's 4.5:1. If your store needs AA placeholder contrast, override --pg-skeleton-placeholder-color and ask your Product Genius contact to match the live bar's placeholder styling, so the handoff stays seamless.

CSP and the inline <style>

The component renders one inline <style> element per instance. Pass the nonce prop whenever your style-src doesn't allow inline styles without a nonce — remember that browsers ignore 'unsafe-inline' when the directive also carries a nonce or a hash.

This is a different nonce use from the feed's: <ProductGeniusFeed nonce> nonces an injected <script> (script-src), while <ProductGeniusSearchBar nonce> nonces an inline <style> (style-src). In a framework with a per-request nonce, it's usually the same value passed to both. See Content-Security-Policy.

How is this guide?

On this page