> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Select Filter Rework

> FilterBar's singleSelect and multiSelect filters now use the platform's SelectMenu / MultiSelectMenu family. This guide covers the API changes and migration steps.

export const VersionStatus = ({version}) => {
  const isUnreleased = version === "unreleased";
  return <Badge color={isUnreleased ? "orange" : "green"}>
      {isUnreleased ? "Unreleased" : `v${version}`}
    </Badge>;
};

<VersionStatus version="3.0.7" />

<Note>
  This guide covers changes to the **beta** FilterBar component. These APIs may
  continue to evolve before the stable release.
</Note>

## Overview

FilterBar's `singleSelect` and `multiSelect` filters now render the platform's
existing select components instead of their own popover bodies:

* `singleSelect` → `SelectMenuSync` (toolbar) + `SelectFieldSync` (drawer)
* `multiSelect` → `MultiSelectMenuSync` (toolbar) + `MultiSelectFieldSync` (drawer)

Each filter's prop shape is now (mostly) `SelectMenuSyncProps` /
`MultiSelectMenuSyncProps`. Anything those components support — grouping,
virtualization, pinned options, "Select All" / "Select Filtered",
dialog-on-mobile, rich option content — is available on the filter without
per-filter glue code. See the [SelectMenu](/docs/web/components/select-menu/code)
and [MultiSelectMenu](/docs/web/components/multi-select-menu/code) docs for
the full surface.

## Migration Guide

### Field renames

| Old             | New               |
| --------------- | ----------------- |
| `items`         | `options`         |
| `selectedItem`  | `selectedOption`  |
| `selectedItems` | `selectedOptions` |

```tsx theme={null}
// Before
{
  type: "singleSelect",
  id: "status",
  label: "Status",
  items: [{ id: "active", label: "Active" }],
  selectedItem: undefined,
}

// After
{
  type: "singleSelect",
  id: "status",
  label: "Status",
  options: [{ id: "active", label: "Active" }],
  selectedOption: undefined,
}
```

### Search props removed

`hasSearch`, `onSearch`, `onSearchClear`, and `searchValue` are gone.
`SelectMenuSync` ships a search field by default and filters client-side via
match-sorter. Drop the callbacks.

```tsx theme={null}
// Before
{
  type: "multiSelect",
  id: "categories",
  items: options,
  selectedItems: [],
  hasSearch: true,
  onSearch: handleSearch,
  onSearchClear: handleClear,
}

// After
{
  type: "multiSelect",
  id: "categories",
  options: allCategories,
  selectedOptions: [],
}
```

To hide the search field, set `disableSearch: true`. To customize filtering,
pass a `filter` function or `MatchSorterOptions`:

```tsx theme={null}
filter: (options, searchValue) =>
  options.filter((o) => o.label.toLowerCase().startsWith(searchValue.toLowerCase())),

// or
filter: { keys: ["label", "extra.email"] },
```

### Custom option shapes (the `Item` generic)

The old filter was generic over a consumer-defined `Item extends { id, label }`.
Options now use the fixed `SelectMenuOption` shape — stash custom domain data
in `extra`:

```tsx theme={null}
// Before
items: [{ id: "alice", label: "Alice", email: "alice@example.com" }];

// After
options: [
  { id: "alice", label: "Alice", extra: { email: "alice@example.com" } },
];
```

Read it back via `selectedOption.extra` in your `onFilterChange` handler.

### Server-backed options

If you were using `onSearch` to hit an API, switch to `asyncSelect` /
`asyncMultiSelect` (backed by `SelectMenu` / `MultiSelectMenu`):

```tsx theme={null}
{
  type: "asyncSelect",
  id: "owner",
  label: "Owner",
  loadOptions: async (searchValue) => fetchOwners(searchValue),
}
```

The async variants support eager loading plus three lazy modes (`page`,
`offset`, `group`).

## Option shape

```tsx theme={null}
type SelectMenuOption = {
  id: string | number;
  label: string;
  searchText?: string;
  group?: string | number;
  disabled?: boolean;
  extra?: Record<string, unknown>;
  content?: {
    title?: string;
    description?: string;
    chips?: Array<Pick<ChipProps, "label" | "color" | "textWrap">>;
    avatar?: Pick<AvatarProps, "name" | "status" | "color" | "image">;
    icon?: Pick<IconProps, "svg" | "color"> & { label?: string };
  };
};
```

## Breaking Changes

<Warning>
  These changes require code updates before upgrading. The previous API is no
  longer supported.
</Warning>

* **Field renames**: `items` → `options`, `selectedItem` → `selectedOption`,
  `selectedItems` → `selectedOptions`.
* **Search props removed**: `hasSearch`, `onSearch`, `onSearchClear`,
  `searchValue`. The search field is built-in; client-side filtering uses
  match-sorter. Override with `disableSearch` or `filter`.
* **`Item` generic removed**. Use `SelectMenuOption` and put custom data in
  `extra`.
