SearchFilters

Composable filter sections for search interfaces with price distribution, amenities, rooms, and property type sections. Available as SearchFiltersContent (headless) and SearchFiltersModal (with ResponsiveModal wrapper).

Installation

pnpm add @wandercom/design-system-web

Usage

Loading example...
import { SearchFiltersModal } from '@wandercom/design-system-web/blocks/search-filters';
import { Button } from '@wandercom/design-system-web/ui/button';

export function Example() {
  const [priceRange, setPriceRange] = useState<[number, number]>([100, 800]);
  const [amenities, setAmenities] = useState<string[]>([]);
  const [bedrooms, setBedrooms] = useState<number | null>(null);
  const [bathrooms, setBathrooms] = useState<number | null>(null);
  const [propertyTypes, setPropertyTypes] = useState<string[]>([]);

  return (
    <SearchFiltersModal
      trigger={<Button variant="outline">Filters</Button>}
      price={{
        data: priceDistribution,
        value: priceRange,
        onChange: setPriceRange,
        min: 0,
        max: 1000,
        currency: "$",
      }}
      amenities={{
        options: [
          { key: "wifi", label: "Wi-Fi" },
          { key: "pool", label: "Pool" },
          { key: "hot-tub", label: "Hot Tub" },
        ],
        value: amenities,
        onChange: setAmenities,
      }}
      rooms={{
        bedrooms,
        bathrooms,
        onBedroomsChange: setBedrooms,
        onBathroomsChange: setBathrooms,
      }}
      propertyTypes={{
        options: [
          { key: "house", label: "House" },
          { key: "apartment", label: "Apartment" },
          { key: "cabin", label: "Cabin" },
        ],
        value: propertyTypes,
        onChange: setPropertyTypes,
      }}
      primaryAction={{ label: "Show results", action: () => undefined }}
      secondaryAction={{ label: "Clear all", action: () => undefined }}
    />
  );
}

All filter sections are optional. Only sections with provided configuration are rendered, separated by dividers. State is fully managed by the consumer.

Custom section labels

Each filter section accepts an optional label prop to override the default heading.

<SearchFiltersModal
  trigger={<Button variant="outline">Filters</Button>}
  price={{ ...priceConfig, label: "Nightly rate" }}
  amenities={{ ...amenitiesConfig, label: "Must-haves" }}
  rooms={{ ...roomsConfig, label: "Sleeping arrangements" }}
  propertyTypes={{ ...propertyTypesConfig, label: "Stay type" }}
/>

Custom content

Use children to add custom filter sections below the built-in ones.

<SearchFiltersModal
  trigger={<Button variant="outline">Filters</Button>}
  price={priceConfig}
>
  <div className="flex flex-col gap-2">
    <span className="font-semibold text-body-sm">Rating</span>
    {/* custom rating filter UI */}
  </div>
</SearchFiltersModal>

SearchBar integration

Use getSearchFiltersLabel to derive a summary string from filter state for the SearchBar's filtersValue prop. Returns undefined when no filters are active, falling through to the default placeholder.

import { SearchFiltersContent, SearchFiltersModal, getSearchFiltersLabel } from '@wandercom/design-system-web/blocks/search-filters';

<SearchBar
  filtersValue={getSearchFiltersLabel({
    amenities,
    bedrooms,
    bathrooms,
    propertyTypes,
    priceRange,
    priceMin: 0,
    priceMax: 1000,
  })}
  filtersContent={
    <SearchFiltersContent
      price={priceConfig}
      amenities={amenitiesConfig}
      rooms={roomsConfig}
      propertyTypes={propertyTypesConfig}
    />
  }
  desktopFiltersContent={
    <SearchFiltersModal
      trigger={<Button variant="outline">Filters</Button>}
      price={priceConfig}
      amenities={amenitiesConfig}
      rooms={roomsConfig}
      propertyTypes={propertyTypesConfig}
      primaryAction={{ label: "Show results", action: handleApply }}
      secondaryAction={{ label: "Clear all", action: handleClear }}
    />
  }
/>

Props

SearchFiltersModalProps

open?:

boolean
Controlled open state of the modal.

onOpenChange?:

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

trigger?:

ReactNode
Trigger element that opens the modal when clicked.

title?:

string
Title for the modal header. Defaults to "Filters".

price?:

PriceFilter
Configuration for the price distribution slider section.

amenities?:

AmenitiesFilter
Configuration for the amenities toggle group section.

rooms?:

RoomsFilter
Configuration for the bedrooms/bathrooms number inputs section.

propertyTypes?:

PropertyTypesFilter
Configuration for the property type toggle group section.

primaryAction?:

{ label: string; action: () => void }
Primary footer action (e.g., "Show 24 results").

secondaryAction?:

{ label: string; action: () => void }
Secondary footer action (e.g., "Clear all").

className?:

string
Additional CSS classes to apply to the modal.

children?:

ReactNode
Custom content rendered after the built-in filter sections.

PriceFilter

data?:

{ value: number; count: number }[]
Distribution data for histogram visualization.

value?:

[number, number]
Current price range [min, max].

onChange?:

(value: [number, number]) => void
Callback when price range changes.

min?:

number
Minimum possible price.

max?:

number
Maximum possible price.

step?:

number
Price step increment.

currency?:

string
Currency symbol or ISO code.

label?:

string
Section heading. Defaults to "Price per night".

AmenitiesFilter

options:

{ key: string; label: string; icon?: ReactNode }[]
Available amenities to display as toggles.

value:

string[]
Currently selected amenity keys.

onChange:

(value: string[]) => void
Callback when selection changes.

label?:

string
Section heading. Defaults to "Amenities".

RoomsFilter

bedrooms?:

number | null
Current bedrooms count.

bathrooms?:

number | null
Current bathrooms count.

onBedroomsChange?:

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

onBathroomsChange?:

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

label?:

string
Section heading. Defaults to "Rooms and beds".

PropertyTypesFilter

options:

{ key: string; label: string; icon?: ReactNode }[]
Available property types to display as toggles.

value:

string[]
Currently selected property type keys.

onChange:

(value: string[]) => void
Callback when selection changes.

label?:

string
Section heading. Defaults to "Property type".
SearchFilters