Passer au contenu principal

HeroSearch

Implemented

Homepage hero search — GET form (what + where) with a submit button; stacks vertically on mobile. An optional third « Quand » segment (ADR-057) renders when whenPlaceholder is supplied. Either field can be turned into a DS combobox with the optional onSearch (What) / onLocationSearch (Where, ADR-074) / onSelectSuggestion / suggestionLabels API; the outer GET form always stays the no-JS fallback.

Categorymolecules
Sourcepatterns/molecules/herosearch.html
AI HintGET-form navigation search (homepage hero). The three label props are required — never render it bare or the inputs ship unnamed. searchInputName must match the target route's query param (?q= vs ?search=). For in-form async option filtering use SearchSelect. ADR-070: field rows use the overlay construction (pointer-events-none absolute icon over a full-width padded input) — never compose an icon as an inline sibling of an input; see docs/decisions/ADR-070-search-field-composition.md.

Two-segment GET-form search bar (what + where) with a submit button, for hero sections. Pill-shaped and inline on ≥ sm breakpoints, stacks vertically on mobile.

HeroSearch

Commerce — Family 7

Server-renderable uncontrolled GET form — no client JS required. Enter submits, and the browser navigates to `action` with both terms as query params.

This component supports light and dark mode.

Usage

Uncontrolled GET form — no state, no client JS. The submitted term arrives as ?<searchInputName>=… on the action route.

import { HeroSearch } from '@epasslive/design-system'

// /events reads the `q` param — pass searchInputName="q" so the term is not dropped
<HeroSearch
  searchPlaceholder={t('hero.searchEventsPlaceholder')}
  locationPlaceholder={t('hero.searchLocationPlaceholder')}
  submitButtonLabel={t('hero.searchButton')}
  action="/events"
  searchInputName="q"
/>

Props

Prop
Type
Default
Description
searchPlaceholder*
string
—

Placeholder and accessible name for the search ("what") input.

locationPlaceholder*
string
—

Placeholder and accessible name for the location ("where") input.

submitButtonLabel*
string
—

Visible text of the submit button.

action
string
'/events'

GET form action URL — the results route the form navigates to.

searchInputName
string
'search'

Query param name for the search term. Must match the param the target route reads — see Usage.

locationInputName
string
'location'

Query param name for the location term.

className
string
—

Additional CSS classes appended to the form element.

id
string
—

id attribute on the form element.

Props

Preview

HeroSearch

Prop
Type
Required
Default
Description
searchPlaceholder
string
Required
—

Placeholder (and accessible name) of the query segment.

locationPlaceholder
string
Required
—

Placeholder (and accessible name) of the location segment.

submitButtonLabel
string
Required
—

Visible text of the submit button.

action
string
Optional
'/events'

GET form action URL the hero submits to.

searchInputName
string
Optional
'search'

Query-param name of the query input — must match the target route's expected param.

locationInputName
string
Optional
'location'

Query-param name of the location input.

className
string
Optional
—

Additional CSS classes on the form.

id
string
Optional
—

Stable root id.

whenPlaceholder
string
Optional
—

ADR-057 (HV2 / R4). Optional third segment « Quand ». Rendered only when supplied, so the historical two-field form is unchanged; its value posts into whenInputName.

whenInputName
string
Optional
'when'

Query-param name of the optional date segment (the /events when filter contract: ISO date, ISO range from..to, or today|weekend|month).

whenSlot
ReactNode
Optional
—

ADR-068 amendment (2026-11-04). Replaces the built-in When input with consumer-owned content (the hero datepicker) while the labeled cell structure stays; the slot's control should carry id hero-${whenInputName} so the label binds. whenPlaceholder still renders the historical input when the slot is absent.

searchLabel
string
Optional
—

Visible 13px label for the What field (plan §5 flat typography); renders a real <label> bound to the input and becomes the accessible name.

locationLabel
string
Optional
—

Visible 13px label for the Where field; renders a real <label> bound to the input and becomes the accessible name.

whenLabel
string
Optional
—

Visible 13px label for the third field; renders a real <label> bound to the input and becomes the accessible name.

variant
'default' | 'flat'
Optional
'default'

ADR-057 (HV2 / R4). 'flat' removes the card surface and the resting shadow, uses square edges and 56px typographic rows with a 1px underline (editorial homepage). Additive: unknown values fall back to 'default'; tap targets and focus rings are identical in both.

onSearch
(query: string) => Promise<SearchInputGroup[]>
Optional
—

Turns the What field into the DS SearchInput combobox (renderForm={false} mode). The outer GET form stays the no-JS fallback and still submits the query param.

onLocationSearch
(query: string) => Promise<SearchInputGroup[]>
Optional
—

ADR-074. Turns the Where field into the same DS SearchInput combobox (chromeless, renderForm={false}, map icon, 250ms debounce, minChars 2, shared suggestionLabels), mirroring the What field. No onSelect is wired: picking a suggestion only fills the Where input, it never navigates.

onSelectSuggestion
(value: string, option: SearchInputOption) => void
Optional
—

Called with the picked suggestion; the consumer navigates (event href or the query param).

suggestionLabels
{ shortQuery?: string; loading?: string; noResults?: string }
Optional
English

Combobox status copy (short-query hint, loading copy, no-results copy). Omitted keys fall back to the DS English defaults.

Quick use:<HeroSearch />

Accessibility

ARIA rolesearch

Type

1 rule

NASA Rule 5

searchPlaceholder, locationPlaceholder, submitButtonLabel required (NASA Rule 5)

ARIA

1 rule

WCAG 1.3.1

Renders role='search'; each input is named by its placeholder; results announcement belongs to the results page, not this form

Usage

Import

tsxSnippet
import { HeroSearch } from "@epasslive/design-system"

Example 1

tsxSnippet
<HeroSearch searchPlaceholder={t("searchEventsPlaceholder")} locationPlaceholder={t("searchLocationPlaceholder")} submitButtonLabel={t("searchButton")} action="/events" searchInputName="q" />

Example 2

tsxSnippet
<HeroSearch searchPlaceholder="Search events" locationPlaceholder="Montreal" submitButtonLabel="Search" />

Anti-patterns

— Do not hardcode data in molecules

— Avoid complex business logic in molecules

Design System v4.1.0 | ePassLive