Skip to main content
For development, import FilterBar from @servicetitan/anvil2/beta. For design, find it in the Anvil2 file.

Live Demo

Anatomy

The Filter Bar consists of several elements that work together to provide filtering capabilities.
Image of the anatomy of the toolbar filters
  1. Filter Button - Interactive button that opens a popover or drawer containing filter options
  2. Selected Filter - Visual indicator showing when a filter has an active selection
  3. Selected Item (for multi select) - Displays the count of selected items in a multi-select filter, using a chip
  4. Label - Text identifier that describes what the filter controls
  5. Selected Item (for single select) - Displays the currently selected option for single-select filters
  6. All Filters - Button that opens the filter drawer for batch editing of all filters. Appears whenever at least one filter is unreachable from the toolbar.
  7. Search Field (Optional) - Input field for filtering available options within single-select and multi-select filters
  8. Popover of filter items - Dropdown panel that appears when a filter button is clicked, containing the filter’s selection interface
  9. Apply and cancel filter (Optional) - Action buttons that appear at the bottom of filter popovers when controlled filtering is enabled

Options

The Filter Bar supports the following configurations to accommodate various filtering scenarios and user interaction patterns.

Types of Filters

Boolean

Boolean filters provide a simple toggle mechanism for on/off states. Users click the filter button to activate or deactivate the filter, with the button’s visual state indicating the current selection.
Content Guidelines
  • The boolean label stays the same across states. Think of this like the label of a Checkbox, which does not change when it is checked or unchecked.

Single Select

Single select filters display a dropdown list of options where users can choose one item. The selected option appears in the filter button label, and clicking the button opens a popover with the full list of available options.
Content Guidelines
  • Single selection contains a label, followed by a : , a space, then the selected item. A Chip does not appear in any single selection scenario.
  • Example “Group by”, “Group by: Job”

Multi Select

Multi select filters allow users to select multiple options from a list. The filter button displays a chip with the count of selected items when selections are active. Clicking the button opens a popover where users can select or deselect multiple options.
Content Guidelines
  • Multi selection contains a label, followed by a Chip. Multi selection never shows the selected items in the Toolbar itself.
  • Example “Group by”, “Group by (1)”, “Group by (2)“

Date

Date filters provide a calendar interface for selecting a single date. The filter button displays the selected date in the configured format, and clicking it opens a popover with a calendar picker. Set minimum, maximum, or unavailable dates to disable out-of-range days in the calendar. Users can still type a date outside those bounds, in which case the field shows an error message.
Date Range
Date range filters enable users to select a start date and an end date. The filter button displays both dates when selected, and clicking it opens a popover with dual calendar pickers for selecting the range. Set minimum, maximum, or unavailable dates to disable out-of-range days in the calendars. Users can still type dates outside those bounds, in which case the field shows an error message.
Date List
A Date List filter renders a single-select list of date “buckets” (e.g. “Today”, “Last 7 days”) followed by four menu options (On…, Before…, After…, Custom Range…). Consumer-supplied options resolve to a date or range and commit immediately on click; menu options open a Dialog with the appropriate date picker. The popover never shows Apply/Cancel since picking an option is itself the confirming action. On… matches the selected day. Before… matches days before it, and After… matches days after it.
Content Guidelines
  • The Calendar icon and label are always present.
  • For the label, use “Date” if there is no specific name that would be more descriptive.
  • Single selection examples:
  • “Date”, “Date: Jan 3, 2025”
  • “Last Modifed On”, “Last Modified On: Nov 1, 2026”
  • Date range examples:
  • “Date”, “Date: Jan 3 – Jan 10, 2025”
  • “Date”, “Date: Dec 31, 2025 – Jan 1, 2026”
  • Date list examples:
  • “Due date” (no selection)
  • “Due date: Today (Jan 28, 2026)” (consumer single-date option)
  • “Due date: Last 7 days (Jan 28 – Feb 4, 2026)” (consumer range option)
  • “Due date: On (Sep 12, 2026)” (menu On…, Before…, After… after dialog)
  • “Due date: Jan 28 – Feb 4, 2026” (menu Custom Range… after dialog — option name omitted since the range is self-explanatory)
Date Format guidelines
  • Month as 3 letter abbreviation
  • Date without a leading zero
  • Comma separation
  • Full numeric year, omitted if a date range for the first value and both dates are in the same year.
  • An en dash (–) between dates
Why does the format not use MM/DD/YYYY?
Accessibility standards require explanation of the format on the page when using ambiguous formats like 01/05/2026. Filter Button avoids this requirement by spelling out the month part. Users do not type directly into Filter Button, so the format remains clear and accessible.
Why does the format not spell out the whole month?
Filter Bar prioritizes horizontal space efficiency over verbosity. In context, the date icon further emphasizes a user is reading a month.

Custom

Custom filters provide complete flexibility for implementing specialized filtering interfaces. Developers define custom render functions for both the popover and drawer views, allowing any type of filter control to be integrated into the toolbar.
Content Guidelines
  • Custom filters must use a label. It may either use selection text or a Chip, depending on the intent of the custom filter.

Apply and Cancel

By default, filter selections apply immediately when a user makes a choice. When controlled filtering is enabled, Apply and Cancel buttons appear at the bottom of filter popovers. Users can make multiple changes in draft state, then click Apply to submit all changes at once or Cancel to discard them. The filter drawer always operates in controlled mode with Apply and Cancel buttons in the footer. See Filter Fetching for more information on when to use Apply and Cancel. Single select and multi select filters can include an optional search field at the top of their popovers. The search field allows users to filter the available options by typing, making it easier to find specific items in large lists. The implementing team controls how the search filters the items through callback functions.

Behavior

The Filter Bar responds to user interaction and space constraints with distinct behaviors for filter management and layout adaptation.

All Filters

The Filter Bar surfaces an “All Filters” button that opens the filter drawer for managing multiple filters at once. The button only appears when at least one filter is unreachable from the toolbar — that is, when a filter is configured as drawer-only, when an inline filter has overflowed, or when the container is too narrow to show inline filters at all. When every filter fits inline and none are drawer-only, the button does not render. While present, the button shows a chip with the count of active filters that need the drawer to access.

Drawer of Filters

The filter drawer is a side panel that displays all available filters in a unified interface. It maintains draft state for all filters until the user clicks Apply, enabling batch updates. The drawer includes a Clear All button that resets all filters to their default states. This batch filtering approach allows users to make multiple filter changes before committing them.

Drawer-only Filters

Setting drawerOnly: true on a filter keeps it accessible only through the filter drawer; it never renders inline in the toolbar, even when there is room for it. Use this to surface lower-priority filters or filters whose UI does not fit the toolbar shape without crowding the primary filters. The flag applies to every filter type — boolean, single select, multi select, date, date range, date list, and custom. A drawer-only custom filter only requires drawerRender — buttonRender is forbidden, since the filter never appears inline.

Wrapping

Filters that don’t fit on a single line automatically wrap to the next line so every filter remains visible.

Responsive Behavior

Below a 640px container width, inline filters are hidden and only the “All Filters” drawer trigger button remains visible. The Filter Bar uses container queries to detect available space and switch to drawer-only mode. Set disableCollapse to keep filters inline at every container width.

Keyboard Interaction

Users can navigate the Filter Bar using standard keyboard controls.

When to use

Use the Filter Bar when needing to apply the filter pattern to content.

How to use

Where and how filters are represented

The All Filter acts as a central hub for all the available filters for end users. It always opens into a Drawer. It’s always labeled as ‘Filters’.

Grouping Filters

On the page, filters exists as part of a larger Toolbar
  • In general, show most-used filters first.
  • Rarely used filters do not have to be shown on the page and may be placed within the Drawer layout instead.
  • If a filter group is used consistently within a product area, consider consistency in ordering across the area.
  • Avoid changing the placement or conditions of actions such as the ‘Clear All’, ‘Filters’, and search bar.
  • Multiple filter groups may exist if a page contains multiple layers of filterable content and each section needs its own unique filtering.

Filter Fetching

Filter fetching refers to when filters get applied to an area. There are three types of filter fetching: instant, apply per-filter, and batch.

Instant filter

Instant filtering begins the process of filtering results the moment a user makes a selection within a filter. This can only occur in the Toolbar filter view and not in the Drawer. This is the generally preferred option for filtering as it reduces the amount of steps users must take.

Apply per-filter

Apply per-filter sets individual Apply + Cancel actions onto each individual filter. This is a useful method when performance is particularly important.

Batch filter

Batch filtering waits to apply a filter only after clicking a standalone Apply action. Batch filtering is not made available in the standard Toolbar view, while being the only method used in the Drawer view.

Which fetching type is best for your case?

  • Instant filtering is the generally preferred method, as it provides real-time feedback to users.
  • Apply per-filter is preferred when performance is an issue, or if a user needs to build out a filter before needing to apply it.
  • Instant and apply per-filter fetching should not be mixed. If per-filter is ever needed in one filter, the whole filter set should also use this method.
  • Filters in the Drawer always use batch apply. No individual filter in the Drawer should have its own apply action.
  • Batch filtering on the page is considered an edge case that requires a custom implementation.

Wrapping

Wrap filters when a table or other filterable content lives inside a smaller surface than a full page, such as a Drawer or Dialog. Wrapping keeps filters inline so users never need an All Filters button, which would open a second Drawer over the current surface.

Content Guidelines

  • All Filters need to have a label. This is the equivalent of a label found in traditional form elements.
  • See each individual filter type for specific content guidelines.

Code

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

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 several prebuilt filter types to pick from:

Boolean

Search

A pinned, always-visible search field. Unlike other filter types, it never renders as a trigger button or popover, and it participates in the same state as every other filter: a non-empty value reveals the “Clear Filters” button, and clearing all filters clears the search text.FilterBar always renders it first among the inline filters, regardless of where it sits in the filters array. Keyboard navigation treats it as a fixed edge of the filter sequence, so that position is the only one that matches how arrow keys move.

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.Single select filters are backed by SelectMenuSync in the toolbar and SelectFieldSync in the drawer. Pass a static options array; client-side filtering happens automatically via match-sorter. To customize filtering, pass a filter function or a MatchSorterOptions config.

Multi Select

Multi select filters are backed by MultiSelectMenuSync (toolbar) and MultiSelectFieldSync (drawer). Same options/filter model as single select, with selectedOptions as an array. selectAll and selectFiltered are supported and managed automatically — pass true (or { label }) to enable.

Single Date Selection

Range Date Selection

Date Constraints

Both date and dateRange filters accept minDate, maxDate (ISO 8601 strings), and unavailable to restrict which dates are selectable in the calendar. These constraints apply in both the toolbar popover and the drawer.Typing a value outside the constraints is still allowed, but the field shows an error message. Provide getErrorMessage to override the default copy:For a dateRange filter, getErrorMessage also receives a field ("start" or "end") identifying the offending endpoint, plus a "startAfterEnd" reason when the start date is after the end date.

Date List Selection

A Date List filter renders a single-select picker of date “buckets” that resolve to a concrete date or range. Pass the buckets you want as options; FilterBar automatically renders On…, Before…, After…, and Custom Range… below them. Selecting a menu option opens a Dialog with the appropriate date picker. Pass customRange.minDate, customRange.maxDate, and customRange.unavailable to constrain the Custom Range picker the same way as a dateRange filter. On, Before, After, and consumer-supplied presets are not validated against those bounds. Typing a value outside customRange is still allowed: the field shows the default dateRange constraint message, and Apply remains available once both endpoints are present. dateList does not accept getErrorMessage.An option with value: null is treated as no filter applied — selecting it clears the filter and the trigger button shows only the filter label. The four menu option ids (on, before, after, customRange) are reserved — don’t reuse them in your own options.on, before, and after set selectedOption to the operator and the ISO date ("YYYY-MM-DD") the user picked, e.g. { id: "before", label: "Before", value: "2026-09-12" }. selectedOption.value is not shifted by a day.
  • On matches the selected day (recordDate === selectedOption.value).
  • Before matches days before it (recordDate < selectedOption.value).
  • After matches days after it (recordDate > selectedOption.value).
If the records you’re filtering use timestamps (Date or ISO datetime strings) rather than date-only strings, compare against the start of the selected day and the start of the next day so After excludes later times on the selected day:

Async Select Selection

An Async Select filter is a single-select filter whose options are loaded asynchronously by your loadOptions function. In the toolbar it renders as a popover-style menu backed by SelectMenu; in the drawer it renders as a labeled form field backed by SelectField. Both surfaces consume the same loadOptions configuration, including SelectMenu’s full lazy loader union (page, offset, group) and eager loading.Use this for filters whose option set is too large for an in-memory singleSelect, or where the options are server-backed (typeahead lookups, dynamic catalogs, etc.). All optional SelectMenu configuration — pinned, cache, virtualize, disableSearch, displayMenuAs, groupToString, etc. — is accepted directly on the filter object.

Async Multi Select Selection

An Async Multi Select filter is the multi-selection counterpart of Async Select — backed by MultiSelectMenu in the toolbar and MultiSelectField in the drawer. Same loadOptions configuration, same lazy loader union; the committed value is an array of selected options.The selectAll and selectFiltered props from MultiSelectMenu are supported. Their onClick handlers are consumer-owned (you decide what “select all” means against your data source), and their checkState is derived from the current selectedOptions on each render — rebuild the filter object whenever the selection changes so the checkbox state stays in sync.

Tree

A Tree filter selects nodes from a hierarchical, static options tree. In the toolbar it renders a popover-style menu backed by TreeSelectMenuSync; in the drawer it renders a labeled form field backed by TreeSelectFieldSync. Client-side search happens automatically (parents of matches are preserved); customize it with a filter function or MatchSorterOptions config.The committed value is stored as selectedNodes — an array of node values, even in selectionMode: "single". All optional TreeSelectMenu configuration (selectionMode, valueConsistsOf, defaultExpandLevel, expandedIds, virtualize, disableSearch, displayMenuAs, popoverWidth, searchPlaceholder) is accepted directly on the filter object.

Async Tree

An Async Tree filter is the asynchronous counterpart of Tree — backed by TreeSelectMenu in the toolbar and TreeSelectField in the drawer. Instead of a static options tree, you supply a loadOptions callback that is called for the root nodes and again with a parentNode for lazy branch expansion.Use this for trees too large to load up front, or whose branches are server-backed. The committed value is stored as selectedNodes, the same shape as the sync tree filter. All optional TreeSelectMenu configuration — cache, virtualize, displayMenuAs, initialLoad, selectionMode, valueConsistsOf, defaultExpandLevel, etc. — is accepted directly on the filter object.These filter types come with preset filter drawer variants.

Custom Filters

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.

Drawer-only Filters

Set drawerOnly: true on any filter to keep it accessible only through the filter drawer. The filter never renders inline in the toolbar, even when there is room for it. The drawerOnly flag is available on every filter type — boolean, singleSelect, multiSelect, date, dateRange, dateList, asyncSelect, asyncMultiSelect, tree, asyncTree, numericRange, textInput, search, and custom.For a custom filter with drawerOnly: true, buttonRender is forbidden — only drawerRender is needed, since the filter never renders inline.The drawer’s “Filters” trigger appears whenever at least one filter is unreachable from the toolbar — that is, when a filter is drawer-only or the container is too narrow (below 640px) to show inline filters. When every filter fits inline and none are drawer-only, the trigger does not render.

Filters with Apply/Cancel Triggers

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, numeric range, text input, 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.

Clear Button

Single select, multi select, async select, async multi select, date, date range, date list, and numeric range filters all render a “Clear” button in their popover footer. Clicking Clear resets the filter to its empty state, fires onFilterChange, and closes the menu. The behavior is built-in — the adapter wires up the clear handler, footer layout, and close behavior, and consumers cannot opt out per filter.Footer layout adapts to what else is present:
  • Clear alone — full-width row at the bottom of the popover.
  • Clear + Apply/Cancel — Clear sits to the left of the Apply/Cancel row (multi select, async multi select, date, date range, and numeric range filters when controlledFiltering is on).
Clear is also wired to any consumer-provided “Add new item” affordance: with both present, Add-new stacks full-width above the Clear / Apply-Cancel row.Clear in confirmation mode is immediate — it commits an empty value regardless of any in-progress draft. The Filter Drawer has its own “Clear filters” trigger and is unaffected by this footer change.

Filters with Additional Toolbar Items

Compose FilterBar and Toolbar as siblings in a horizontal Flex row. Because both components default to growing within a flex container, constrain the Toolbar and let the FilterBar take the remaining space — otherwise the Toolbar expands and collapses the filters.Key layout details:
  • Put FilterBar and Toolbar next to each other (not nested). Tab moves between the two toolbars; arrow keys move within each.
  • Set style={{ flex: "0 0 auto", width: "fit-content" }} on Toolbar so it only takes the width of its controls.
  • Set flexGrow={1} on FilterBar (from layout utility props) so filters use the remaining row width.
  • Reverse the sibling order if actions belong on the left; keep the same flex sizing rules.
See also the Toolbar.Filters to FilterBar migration guide.

Responsive Behavior

FilterBar always wraps filters that don’t fit on a single line. Below a 640px container width, inline filters are hidden and only the drawer trigger is shown. Set disableCollapse to keep filters inline at every container width.

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 ”.

Testing

FilterBar’s interactive sub-elements carry a stable data-anv-part attribute so end-to-end tests can target them directly. The library owns the part names; you supply the instance identifier.Attach your own data-* attribute (such as data-cy or data-testid) to the FilterBar. It is forwarded to the root element, letting you scope queries to a single instance:
Each filter trigger also carries a data-id matching the filter’s id, so you can target a specific filter’s trigger with [data-cy="job-filters"] [data-id="amount"].Select, multi-select, and tree filters render the corresponding fields, which expose their own parts (input, option, chip, overflow-chip, and so on) — see the SelectField and MultiSelectField docs.
Filter menus, drawers, dialogs, and popovers render in the browser’s top layer, outside the FilterBar root. Descendant queries scoped to your wrapper attribute will not match parts inside an open menu or drawer (min-input, date-calendar, apply-button, etc.). Open the filter first, then target those parts by their part attribute directly. Only one menu or drawer is open at a time.
Last modified on October 27, 2026