Skip to main content
Beta FeatureThis feature is currently in beta, and needs to be imported from @servicetitan/anvil2/beta.While we hope to minimize breaking changes, they may occur due to feedback we receive or other improvements. These will always be documented in the changelog and communicated in Slack.Please reach out in the #ask-designsystem channel with any questions or feedback!

Common Examples

Standard Filters

import { FilterBar } from "@servicetitan/anvil2/beta";

function ExampleComponent() {
  const filters = [
    // See filter objects below
  ];

  return (
    <FilterBar
      associatedContent="..."
      filters={filters}
      onFilterChange={(updatedFilters) => console.log(updatedFilters)}
    />
  );
}
A filter group is created by passing a filters object array to the FilterBar component. The component takes the object and builds the set of filters for you according to the design patterns. You can create a custom filter if needed, but there are currently 5 available prebuilt filter types to pick from:

Boolean

{
  type: "boolean",
  id: "booleanFilterId",
  label: "Boolean Filter",
  checked: false,
}

Single Select

The item list for single select is populated and controlled by the implementing team. This includes any filtering that needs to be done when a search field is present.Simple Example
{
  type: "singleSelect",
  id: "singleSelectFilterId",
  label: "Single Select Filter",
  items: [
    {
      id: "singleOption1",
      label: "Single Option 1"
    },
    ...
  ],
  simpleDrawerVariant
}
With Search ExampleSince population of the item list is controlled by implementing team, the Single Select search handler needs to have an empty search term conditional in it so the filter drawer draft state can properly reset the displayed items after selection but before applying the batch update.
{
  type: "singleSelect",
  id: "singleSelectFilterId",
  label: "Single Select Filter",
  items: [
    {
      id: "singleOption1",
      label: "Single Option 1"
    },
    ...
  ],
  hasSearch: true,
  onSearch: (searchTerm) => {
    if (!searchTerm) {
      setSingleSelectOptions(statusOptions);
    } else {
      const filtered = statusOptions.filter((option) =>
        option.label.toLowerCase().includes(searchTerm.toLowerCase()));
      setSingleSelectOptions(filtered);
    }
  },
  onSearchClear: handleMultiselectSearchClear,
}

Multi Select

The item list for single select is populated and controlled by the implementing team. This includes any filtering that needs to be done when a search field is present.Simple Example
{
  type: "multiSelect",
  id: "multiSelectFilterId",
  label: "Multi Select Filter",
  items: [
    {
      id: "multiOption1",
      label: "Multi Option 1"
    },
    ...
  ],
  simpleDrawerVariant
}
With Search ExampleSince population of the item list is controlled by implementing team, the Single Select search handler needs to have an empty search term conditional in it so the filter drawer draft state can properly reset the displayed items after selection but before applying the batch update.
{
  type: "multiSelect",
  id: "multiSelectFilterId",
  label: "Multi Select Filter",
  items: [
    {
      id: "multiOption1",
      label: "Multi Option 1"
    },
    ...
  ],
  selectedItems: [],
  hasSearch: true,
  onSearch: (searchTerm) => {
    if (!searchTerm) {
      setMultiSelectOptions(statusOptions);
    } else {
      const filtered = statusOptions.filter((option) =>
        option.label.toLowerCase().includes(searchTerm.toLowerCase()));
      setMultiSelectOptions(filtered);
    }
  },,
  onSearchClear: handleMultiselectSearchClear,
}

Single Date Selection

{
  type: "date",
  id: "dateFilterId",
  label: "Date Filter",
  mode: "mm/dd/yyyy",
}

Range Date Selection

{
  type: "dateRange",
  id: "dateRangeFilterId",
  label: "Date Range Filter",
  mode: "mm/dd/yyyy",
}
These filter types come with preset filter drawer variants.

Custom Filters

import { FilterBar } from "@servicetitan/anvil2/beta";

function ExampleComponent() {
  const [labelText, setLabelText] = useState("");

  const buttonComponent = ({ value, onChange }) => (
    <YourComponentHere value={value} onChange={onChange} />
  ))};

  const drawerComponent = ({ value, onChange }) => (
    <YourComponentHere value={value} onChange={onChange} />
  ))};

  const filters = [
    {
      type: "custom",
      id: "customFilterId",
      label: labelText,
      buttonRender: buttonComponent,
      drawerRender: renderComponent
    }
  ];

  return(
    <FilterBar
      associatedContent="..."
      filters={filters}
      onFilterChange={
        (updateFilters) => {
          console.log(updateFilters);
          setLabelText(....);
        }
      }
    />
  )
}
Provide a buttonRender, drawerRender, and custom label to create a custom filter. Label’s for custom filters can either be a string or a string with a chip count. Custom filters can be used by themselves or as part of a combined filter array.

Filters with Apply/Cancel Triggers

import { FilterBar } from "@servicetitan/anvil2/beta";

function ExampleComponent() {
  const filters = [
    ...
  ];

  return (
    <FilterBar
      associatedContent="..."
      filters={filters}
      onFilterChange={
        (updatedFilters) => console.log(updatedFilters)
      }
      controlledFiltering
    />
  )
}
By default, filters update as soon as a selection is made in the filter button. When controlledFiltering is set to true, Apply and Cancel buttons appear at the bottom of single select, multi select, single date selection, range date selection, and custom filter button popovers to control updating. This setting has no impact on the Filter Drawer, which always submits as a batch update upon clicking Apply.

Filters with Search Field

import { FilterBar } from "@servicetitan/anvil2/beta";
import { Toolbar } from "@servicetitan/anvil2/beta";
import { Flex } from "@servicetitan/anvil2";

function ExampleComponent() {
  const filters = [
    ...
  ];

  const handleToolbarSearch = (e) => {
    ...
  }

  const handleToolbarSearchClear = () => {
    ...
  }

  return (
    <Flex direction="column" gap={2}>
      <Toolbar associatedContent="...">
        <Toolbar.Search
          placeholder="Search..."
          onChange={handleToolbarSearch}
          onClear={handleToolbarSearchClear}
        />
      </Toolbar>
      <FilterBar
        associatedContent="..."
        filters={filters}
        onFilterChange={
          (updatedFilters) => console.log(updatedFilters)
        }
      />
    </Flex>
  )
}
Use a Toolbar.Search alongside FilterBar to include a search input with your filters. The search bar works independently from the filters themselves and has its own search props.

Filters with Additional Toolbar Items

import { FilterBar } from "@servicetitan/anvil2/beta";
import { Toolbar } from "@servicetitan/anvil2/beta";
import { Flex } from "@servicetitan/anvil2";

function ExampleComponent() {
  const filters = [
    ...
  ];

  return (
    <Flex direction="column" gap={2}>
      <FilterBar
        associatedContent="..."
        filters={filters}
        onFilterChange={
          (updatedFilters) => console.log(updatedFilters)
        }
      />
      <Toolbar associatedContent="...">
        <Toolbar.ControlGroup>
          <Toolbar.Button
            icon={{ before: AttachFile }}
            aria-label="File button"
            ...
          />
          // other base toolbar controls
        </Toolbar.ControlGroup>
      </Toolbar>
    </Flex>
  )
}
Use FilterBar alongside a Toolbar with Toolbar control options to have additional actions. The FilterBar and Toolbar are rendered as siblings and can be composed with Flex for layout.

Overflow Modes

import { FilterBar } from "@servicetitan/anvil2/beta";

function ExampleComponent() {
  const filters = [
    ...
  ];

  return (
    <>
      // Default wrap mode
      <FilterBar
        associatedContent="..."
        filters={filters}
        onFilterChange={
          (updatedFilters) => console.log(updatedFilters)
        }
      />

      // Collapsed overflow mode
      <FilterBar
        associatedContent="..."
        overflow="collapse"
        filters={filters}
        onFilterChange={
          (updatedFilters) => console.log(updatedFilters)
        }
      />
    </>
  )
}
The FilterBar overflow mode defaults to wrap. Set overflow="collapse" to collapse filters that do not fit within the available space.In collapse mode, a hidden filter does not get added to an overflow menu, it is only removed from the screen. All filters remain accessible through the filter drawer in any mode.

React Accessibility

Aria Labels

Add an associatedContent prop to the FilterBar for proper group identification for screen readers. The associatedContent value provides a11y context, generating labels like “Filters for ”.
Last modified on April 23, 2026