SearchBar

A compound search bar component for location, dates, and guests selection.

Installation

pnpm add @wandercom/design-system-web

Usage

The SearchBar is a compound component that provides a flexible search interface. It consists of a collapsed bar with location trigger, date/guest buttons, and an expandable dropdown panel for location search.

import {
  SearchBar,
  SearchBarActionArea,
  SearchBarButton,
  SearchBarDesktop,
  SearchBarLocationTrigger,
  SearchBarRoot,
  SearchBarSearchButton,
} from '@wandercom/design-system-web/ui/search-bar';

export function Example() {
  return (
    <SearchBarRoot
      desktop={
        <SearchBarDesktop>
          <SearchBarLocationTrigger>Where?</SearchBarLocationTrigger>
          <SearchBarButton>Anytime</SearchBarButton>
          <SearchBarActionArea>
            <SearchBarButton>Any guests</SearchBarButton>
            <SearchBarSearchButton />
          </SearchBarActionArea>
        </SearchBarDesktop>
      }
    />
  );
}

Use SearchBar for the full experience with built-in panels and labels:

import { SearchBar } from '@wandercom/design-system-web/ui/search-bar';

export function Example() {
  return (
    <SearchBar
      labels={{
        searchLabel: 'Search',
        locationLabel: 'Where?',
      }}
    />
  );
}

Controlled location

Use the location and onLocationChange props to manage location state externally, e.g. syncing with a map view.

import { SearchBar, type SearchBarLocation } from '@wandercom/design-system-web/ui/search-bar';
import { useState } from 'react';

export function ControlledLocationExample() {
  const [location, setLocation] = useState<SearchBarLocation | null>(null);

  return (
    <SearchBar
      location={location}
      onLocationChange={setLocation}
      onSearch={(values) => console.log(values)}
    />
  );
}

When location is omitted, the component manages its own state internally (uncontrolled mode, initialized from initialValues).

Examples

A complete search bar with location trigger, dates, guests, and search button.

Loading example...

With popover

The SearchBarPopover provides a dropdown for location search with sections and items using the Popover component.

import {
  SearchBarPopover,
  SearchBarPopoverContent,
  SearchBarPopoverInput,
  SearchBarPopoverItem,
  SearchBarPopoverSection,
  SearchBarPopoverTrigger,
  SearchBarLocationTrigger,
} from '@wandercom/design-system-web/ui/search-bar';
import { useState } from 'react';

export function LocationPopover() {
  const [open, setOpen] = useState(false);

  return (
    <SearchBarPopover open={open} onOpenChange={setOpen}>
      <SearchBarPopoverTrigger asChild>
        <SearchBarLocationTrigger active={open}>Where?</SearchBarLocationTrigger>
      </SearchBarPopoverTrigger>
      <SearchBarPopoverContent>
        <SearchBarPopoverInput placeholder="Search locations..." />
        <SearchBarPopoverSection label="Recent searches">
          <SearchBarPopoverItem
            title="Los Angeles"
            subtitle="Los Angeles, California"
          />
          <SearchBarPopoverItem
            title="Joshua Tree"
            subtitle="California, United States"
          />
        </SearchBarPopoverSection>
      </SearchBarPopoverContent>
    </SearchBarPopover>
  );
}

Composition with asChild

Use the asChild prop to render custom elements while maintaining functionality.

import { SearchBarLocationTrigger } from '@wandercom/design-system-web/ui/search-bar';
import { Popover, PopoverTrigger } from '@wandercom/design-system-web/ui/popover';

export function ComposedTrigger() {
  return (
    <Popover>
      <SearchBarLocationTrigger asChild>
        <PopoverTrigger>Where?</PopoverTrigger>
      </SearchBarLocationTrigger>
    </Popover>
  );
}

Date popover

The SearchBarDatePopoverContent provides a date range picker with calendar, mode toggle (Dates/Flexible), and flexibility options. Click the "Anytime" button in the example above to see the date popover.

import {
  SearchBarButton,
  SearchBarDatePopoverCalendar,
  SearchBarDatePopoverContent,
  SearchBarDatePopoverDuration,
  SearchBarDatePopoverFlexibility,
  SearchBarDatePopoverHeader,
  SearchBarDatePopoverMonthGrid,
  SearchBarPopover,
  SearchBarPopoverTrigger,
  type SearchBarDateDuration,
  type SearchBarDateMonth,
} from '@wandercom/design-system-web/ui/search-bar';
import { useState } from 'react';
import type { DateRange } from 'react-day-picker';

export function DatePopover() {
  const [open, setOpen] = useState(false);
  const [mode, setMode] = useState<'dates' | 'flexible'>('dates');
  const [dateRange, setDateRange] = useState<DateRange | undefined>();
  const [flexibility, setFlexibility] = useState<'exact' | '1' | '2' | '3' | '7'>('exact');
  const [duration, setDuration] = useState<SearchBarDateDuration>('weekend');
  const [selectedMonths, setSelectedMonths] = useState<SearchBarDateMonth[]>([]);

  return (
    <SearchBarPopover open={open} onOpenChange={setOpen}>
      <SearchBarPopoverTrigger asChild>
        <SearchBarButton>Anytime</SearchBarButton>
      </SearchBarPopoverTrigger>
      <SearchBarDatePopoverContent>
        <SearchBarDatePopoverHeader
          mode={mode}
          onModeChange={setMode}
          onClear={() => {
            setDateRange(undefined);
            setSelectedMonths([]);
          }}
        />
        {mode === 'dates' && (
          <>
            <SearchBarDatePopoverCalendar
              selected={dateRange}
              onSelect={setDateRange}
              disabled={{ before: new Date() }}
            />
            <SearchBarDatePopoverFlexibility
              value={flexibility}
              onValueChange={setFlexibility}
              disabled={!dateRange?.from || !dateRange?.to}
            />
          </>
        )}
        {mode === 'flexible' && (
          <div className="flex flex-col gap-6 px-7 py-7">
            <SearchBarDatePopoverDuration value={duration} onValueChange={setDuration} />
            <SearchBarDatePopoverMonthGrid selected={selectedMonths} onSelect={setSelectedMonths} />
          </div>
        )}
      </SearchBarDatePopoverContent>
    </SearchBarPopover>
  );
}

Guests popover

The SearchBarGuestsPopoverContent provides a stepper interface for selecting guests and pets. Click the "Who?" button in the example above to see the guests popover.

import {
  SearchBarButton,
  SearchBarGuestsPopoverContent,
  SearchBarGuestsPopoverRow,
  SearchBarPopover,
  SearchBarPopoverTrigger,
} from '@wandercom/design-system-web/ui/search-bar';
import { useState } from 'react';

export function GuestsPopover() {
  const [open, setOpen] = useState(false);
  const [guests, setGuests] = useState(1);
  const [pets, setPets] = useState(0);

  return (
    <SearchBarPopover open={open} onOpenChange={setOpen}>
      <SearchBarPopoverTrigger asChild>
        <SearchBarButton>Any guests</SearchBarButton>
      </SearchBarPopoverTrigger>
      <SearchBarGuestsPopoverContent>
        <SearchBarGuestsPopoverRow
          label="Guests"
          value={guests}
          onChange={setGuests}
          min={1}
          max={16}
        />
        <SearchBarGuestsPopoverRow
          label="Pets"
          value={pets}
          onChange={setPets}
          max={10}
        />
      </SearchBarGuestsPopoverContent>
    </SearchBarPopover>
  );
}

Props

className?:

string
Additional CSS classes to apply to the root container.

locations?:

SearchBarLocation[]
Location suggestions used for search matching.

units?:

SearchBarLocation[]
Property suggestions shown in the location panel.

recentSearches?:

SearchBarLocation[]
Recent search items shown when the query is empty.

suggestedRegions?:

SearchBarLocation[]
Suggested regions shown when the query is empty.

initialValues?:

Partial<SearchBarValues>
Initial values for location, dates, guests, and pets.

minDate?:

Date
Minimum selectable date. Defaults to today.

labels?:

SearchBarLabels
Optional label overrides for i18n and copy updates.

onSearch?:

(values: SearchBarValues) => void
Callback when the user triggers search.

location?:

SearchBarLocation | null
Controlled location value. When provided, overrides internal location state. Use this to sync with external state (e.g., map movements).

onLocationChange?:

(location: SearchBarLocation | null) => void
Callback when the location changes internally (user selects a suggestion). Use alongside location for controlled mode.

onLocationSelect?:

(location: SearchBarLocation) => void
Callback when a location suggestion is selected.

onLocationQueryChange?:

(query: string) => void
Callback when the location search query changes. Use this for server-side search.

locationSuggestions?:

SearchBarLocation[]
Server-provided location suggestions based on the current query.

unitSuggestions?:

SearchBarLocation[]
Server-provided unit/property suggestions based on the current query.

isLoadingSuggestions?:

boolean
Whether location suggestions are currently loading.

filtersContent?:

ReactNode
Content for the mobile filters sub-drawer.

filtersValue?:

string
Current filters value text displayed on the mobile filters trigger.

onClearFilters?:

() => void
Callback when the clear filters button is clicked.

desktopFiltersContent?:

ReactNode
Content for the desktop filters panel.

asChild?:

boolean
When true, uses Radix Slot for the outermost wrapper, allowing composition inside a parent element (e.g., a <form>).

SearchBarRoot

className?:

string
Additional CSS classes to apply to the root container.

desktop?:

ReactNode
Content to render on desktop (the pill-shaped collapsed bar). Falls back to children if not provided.

mobile?:

ReactNode
Content to render on mobile (typically the drawer trigger and drawer content). Falls back to children if not provided.

asChild?:

boolean
When true, uses Radix Slot for the outermost wrapper, allowing composition inside a parent element (e.g., a <form>).

children?:

ReactNode
Fallback content used for both desktop and mobile when those props are not provided.

SearchBarDesktop

className?:

string
Additional CSS classes to apply.

children:

ReactNode
SearchBar segments (LocationTrigger, Button, ActionArea). Dividers are automatically added between children.

activeSegment?:

'location' | 'dates' | 'guests' | null
The currently active segment for external state management.

onActiveSegmentChange?:

(segment: 'location' | 'dates' | 'guests' | null) => void
Callback when the active segment changes.

hasLocationValue?:

boolean
Whether the location field has a value. Affects filled state styling.

hasDatesValue?:

boolean
Whether the dates field has a value. Affects filled state styling.

hasGuestsValue?:

boolean
Whether the guests field has a value. Affects filled state styling.

SearchBarLocationTrigger

active?:

boolean
When true, shows elevated state with white background and shadow.

asChild?:

boolean
Renders child element and merges props using Radix Slot.

className?:

string
Additional CSS classes to apply.

label?:

string
Label shown when active and empty. Defaults to "Where?".

placeholder?:

string
Label shown when inactive and empty. Defaults to "Where".

SearchBarButton

asChild?:

boolean
Renders child element and merges props using Radix Slot.

active?:

boolean
When true, shows the elevated/active state.

segment?:

'dates' | 'guests'
The segment this button represents. Enables context integration for active state and auto-labeling.

className?:

string
Additional CSS classes to apply.

label?:

string
Label shown when active and empty. Defaults to "When?" for dates, "Who?" for guests.

placeholder?:

string
Label shown when inactive and empty. Defaults to "When" for dates, "Who" for guests.

SearchBarActionArea

className?:

string
Additional CSS classes to apply.

children:

ReactNode
Usually contains a SearchBarButton and SearchBarSearchButton.

SearchBarSearchButton

className?:

string
Additional CSS classes to apply.

children?:

ReactNode
Button content. Defaults to "Search".

SearchBarPopover

Re-exported Radix Popover component for controlling open/closed state.

SearchBarPopoverTrigger

Re-exported PopoverTrigger for triggering the popover. Use with asChild prop.

SearchBarPopoverAnchor

Re-exported PopoverAnchor for custom anchor positioning.

SearchBarPopoverContent

Extends all PopoverContent props from Radix.

className?:

string
Additional CSS classes to apply.

children:

ReactNode
Popover content (PopoverInput, PopoverSection, etc.).

align?:

"start" | "center" | "end"
Alignment relative to the trigger. Defaults to "center".

sideOffset?:

number
Distance from the trigger in pixels. Defaults to 8.

SearchBarPopoverInput

placeholder?:

string
Input placeholder text. Defaults to "Search locations...".

className?:

string
Additional CSS classes to apply.

onClear?:

() => void
Callback when the clear button is clicked. When provided, a clear button appears when the input has a value.

onSubmit?:

() => void
Callback when Enter is pressed. Use this to advance to the next segment.

SearchBarPopoverSection

label?:

string
Section label displayed above items.

maxItems?:

number
Maximum number of items to display in this section. Defaults to 3.

className?:

string
Additional CSS classes to apply.

children:

ReactNode
Section content (SearchBarPopoverItem components).

SearchBarPopoverItem

title:

string
Primary text for the item.

subtitle?:

string
Secondary text displayed below the title.

icon?:

ReactNode
Custom icon to display. Shows map pin icon by default.

image?:

string
Image URL to display instead of an icon.

selected?:

boolean
Whether the item is currently selected.

asChild?:

boolean
Renders child element and merges props using Radix Slot.

onSelect?:

(location: { title: string; subtitle?: string }) => void
Callback when the item is selected. Receives the title and subtitle.

className?:

string
Additional CSS classes to apply.

SearchBarDatePopoverContent

Extends all PopoverContent props from Radix.

className?:

string
Additional CSS classes to apply.

children:

ReactNode
Popover content (Header, Calendar, Flexibility components).

align?:

"start" | "center" | "end"
Alignment relative to the trigger. Defaults to "center".

sideOffset?:

number
Distance from the trigger in pixels. Defaults to 8.

SearchBarDatePopoverHeader

mode?:

'dates' | 'flexible'
Current date mode. Defaults to 'dates'.

onModeChange?:

(mode: 'dates' | 'flexible') => void
Callback when the mode changes.

onClear?:

() => void
Callback when the clear button is clicked.

showClear?:

boolean
Whether to show the clear button. Defaults to true.

className?:

string
Additional CSS classes to apply.

datesLabel?:

string
Label for the Dates tab. Defaults to "Dates".

flexibleLabel?:

string
Label for the Flexible tab. Defaults to "Flexible".

clearLabel?:

string
Label for the clear action. Defaults to "Clear dates".

SearchBarDatePopoverCalendar

Extends most DayPickerProps from react-day-picker (excluding mode, numberOfMonths, and showOutsideDays).

selected?:

DateRange
The currently selected date range ({ from?: Date, to?: Date }).

onSelect?:

(range: DateRange | undefined) => void
Callback when a date range is selected.

endMonth?:

Date
The last month the calendar can navigate to. Defaults to 2 years from current month.

disabled?:

Matcher | Matcher[]
Days to disable (e.g., { before: new Date() }). Inherited from DayPickerProps.

className?:

string
Additional CSS classes to apply. Inherited from DayPickerProps.

SearchBarDatePopoverFlexibility

value?:

'exact' | '1' | '2' | '3' | '7'
The selected flexibility option. Defaults to 'exact'.

onValueChange?:

(value: 'exact' | '1' | '2' | '3' | '7') => void
Callback when the flexibility option changes.

disabled?:

boolean
Whether the flexibility options are disabled. Defaults to false.

className?:

string
Additional CSS classes to apply.

exactLabel?:

string
Label for the exact dates option. Defaults to "Exact dates".

oneDayLabel?:

string
Label for the ± 1 day option. Defaults to "± 1 day".

twoDaysLabel?:

string
Label for the ± 2 days option. Defaults to "± 2 days".

threeDaysLabel?:

string
Label for the ± 3 days option. Defaults to "± 3 days".

sevenDaysLabel?:

string
Label for the ± 7 days option. Defaults to "± 7 days".

SearchBarDatePopoverDuration

value?:

'weekend' | 'week'
The selected duration option. Defaults to 'weekend'.

onValueChange?:

(value: 'weekend' | 'week') => void
Callback when the duration option changes.

className?:

string
Additional CSS classes to apply.

titleLabel?:

string
Heading above the duration toggle. Defaults to "How long would you like to stay?".

weekendLabel?:

string
Label for the weekend option. Defaults to "Weekend".

weekLabel?:

string
Label for the week option. Defaults to "Week".

SearchBarDatePopoverMonthGrid

selected?:

SearchBarDateMonth[]
Array of selected months ({ month: number, year: number }).

onSelect?:

(months: SearchBarDateMonth[]) => void
Callback when month selection changes.

monthCount?:

number
Number of months to display in the grid. Defaults to 12.

startDate?:

Date
Starting date for the month grid. Defaults to current month.

titleLabel?:

string
Heading above the month grid. Defaults to "When do you want to go?".

className?:

string
Additional CSS classes to apply.

SearchBarGuestsPopoverContent

Extends all PopoverContent props from Radix.

className?:

string
Additional CSS classes to apply.

children:

ReactNode
Popover content (SearchBarGuestsPopoverRow components).

align?:

"start" | "center" | "end"
Alignment relative to the trigger. Defaults to "center".

sideOffset?:

number
Distance from the trigger in pixels. Defaults to 8.

SearchBarGuestsPopoverRow

label:

string
The label displayed on the left side of the row.

value:

number | null
The current count value. Null displays "Any" placeholder.

onChange:

(value: number | null) => void
Callback when the value changes.

min?:

number
Minimum allowed value. Defaults to 0.

max?:

number
Maximum allowed value. Defaults to 99.

className?:

string
Additional CSS classes to apply.

SearchBarMobileDrawer

className?:

string
Additional CSS classes for the trigger wrapper.

children?:

ReactNode
Content to render inside the drawer (SearchBarMobileCard components).

open?:

boolean
Whether the drawer is open.

onOpenChange?:

(open: boolean) => void
Callback when the drawer open state changes.

trigger?:

ReactNode
The trigger element that opens the drawer.

activeSegment?:

'location' | 'dates' | 'guests'
The currently active section. Defaults to 'location'.

onActiveSegmentChange?:

(segment: 'location' | 'dates' | 'guests') => void
Callback when the active section changes.

hasLocationValue?:

boolean
Whether the location field has a value.

hasDatesValue?:

boolean
Whether the dates field has a value.

hasGuestsValue?:

boolean
Whether the guests field has a value.

onSearch?:

() => void
Callback when the search button is clicked.

titleLabel?:

string
Screen-reader title for the drawer. Defaults to "Search".

filtersLabel?:

string
Label for the filters trigger. Defaults to "Filters?".

filtersPlaceholder?:

string
Placeholder text when no filters are selected. Defaults to "No filters".

filtersValue?:

string
Current filters value text.

filtersContent?:

ReactNode
Content for the filters sub-drawer.

onClearFilters?:

() => void
Callback when the clear filters button is clicked.

filtersTitleLabel?:

string
Title for the filters sub-drawer. Defaults to "Filters".

clearFiltersLabel?:

string
Label for the clear filters button. Defaults to "Clear filter".

showResultsLabel?:

string
Label for the show results button. Defaults to "Show results".

SearchBarMobileFooter

className?:

string
Additional CSS classes to apply.

onSkip?:

() => void
Callback when the skip button is clicked.

showSkip?:

boolean
Whether to show the skip button. Defaults to false.

searchLabel?:

string
Label for the search button. Defaults to "Search".

nextLabel?:

string
Label for the next button. Defaults to "Next".

skipLabel?:

string
Label for the skip button. Defaults to "Skip".

SearchBarMobileTrigger

className?:

string
Additional CSS classes to apply.

placeholder?:

string
Text displayed when empty. Defaults to "Start your search".

location?:

string
Location value to display (e.g., "San Francisco").

dates?:

string
Dates value to display. Defaults to "When".

guests?:

string
Guests value to display. Defaults to "Who".

SearchBarMobileCard

section?:

'location' | 'dates' | 'guests'
The section this card represents. Enables automatic accordion behavior within SearchBarMobileDrawer.

label:

string
Label displayed at the top when expanded or on the left when collapsed.

value?:

string
Value displayed on the right side when collapsed.

expanded?:

boolean
Whether the card is expanded. Auto-determined when using section prop.

onClick?:

() => void
Callback when the collapsed card is clicked.

headerAction?:

ReactNode
Action element in the header (e.g., "Clear dates" button). Only shown when expanded.

autoFocus?:

boolean
When true, focuses the first input inside the card after the entry animation completes. Defaults to true.

className?:

string
Additional CSS classes to apply.

children?:

ReactNode
Content rendered when the card is expanded.

SearchBarMobileCardContent

className?:

string
Additional CSS classes to apply.

children?:

ReactNode
Content to render inside the card.

SearchBarMobileCalendar

A self-contained calendar experience for the mobile drawer, including a mode toggle (Dates/Flexible), date range calendar with day-of-week labels, flexibility selector, duration toggle, and month grid.

selected?:

DateRange
The currently selected date range.

onSelect?:

(range: DateRange | undefined) => void
Callback when a date range is selected.

mode?:

'dates' | 'flexible'
Current date mode. Defaults to 'dates'.

onModeChange?:

(mode: 'dates' | 'flexible') => void
Callback when the mode changes.

flexibility?:

'exact' | '1' | '2' | '3' | '7'
The flexibility option value. Defaults to 'exact'.

onFlexibilityChange?:

(value: 'exact' | '1' | '2' | '3' | '7') => void
Callback when the flexibility option changes.

numberOfMonths?:

number
Number of months to display. Defaults to 12.

minDate?:

Date
The minimum selectable date.

duration?:

'weekend' | 'week'
The duration option for flexible dates. Defaults to 'weekend'.

onDurationChange?:

(value: 'weekend' | 'week') => void
Callback when the duration option changes (flexible mode).

selectedMonths?:

SearchBarDateMonth[]
The selected months for flexible dates.

onSelectedMonthsChange?:

(months: SearchBarDateMonth[]) => void
Callback when selected months change (flexible mode).

labels?:

object
Optional label overrides for i18n: datesTabLabel, flexibleTabLabel, durationTitleLabel, monthTitleLabel, weekendLabel, weekLabel, exactLabel, oneDayLabel, twoDaysLabel, threeDaysLabel, sevenDaysLabel.

className?:

string
Additional CSS classes to apply.

SearchBarFallback

A lightweight static fallback for use with Suspense boundaries or initial server renders. See the SearchBar Fallback documentation for full details.

Accessibility

The SearchBar component includes proper accessibility features:

  • Uses semantic button elements for interactive triggers
  • Dividers are marked with aria-hidden="true"
  • Panel items support aria-selected for selection state
  • All interactive elements are keyboard accessible
  • Focus states are visible with proper styling
  • Screen reader compatible with proper text labels
  • Stepper buttons have descriptive aria-label attributes (e.g., "Increase guests", "Decrease pets")
  • Counter values use aria-live="polite" to announce changes to screen readers
  • Disabled buttons are properly marked with disabled attribute
SearchBar