> ## 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 Components Reach Stable — A First for Anvil2 Beta

export const BlogMeta = ({date, author, tags}) => {
  const formatList = items => {
    if (!Array.isArray(items)) return items;
    if (items.length === 1) return items[0];
    if (items.length === 2) return `${items[0]} & ${items[1]}`;
    return `${items.slice(0, -1).join(", ")}, & ${items[items.length - 1]}`;
  };
  return <div className="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
      <span>{date}</span>
      <span>|</span>
      <span>{formatList(author)}</span>
      {tags && tags.length > 0 && <>
          <span>|</span>
          <div className="flex flex-wrap gap-1.5">
            {tags.map(tag => <span key={tag} className="px-2 py-1 rounded bg-gray-100 dark:bg-neutral-800 text-xs">
                {tag}
              </span>)}
          </div>
        </>}
    </div>;
};

<BlogMeta date="June 23, 2026" author="Derek Watson" tags={["Engineering", "New Features", "Web"]} />

Today marks a first for Anvil2: a set of components has graduated from beta to stable. SelectField, MultiSelectField, SelectMenu, and MultiSelectMenu are now available from the `@servicetitan/anvil2` stable package export. At the same time, `Combobox` is formally deprecated.

<Tip>
  If you're importing any of these components from `@servicetitan/anvil2/beta`, update your imports now. The beta export remains available until **Anvil2 4.0** but will log a deprecation warning in development.
</Tip>

## What's changing

### Four select components are now stable

The following components are exported from `@servicetitan/anvil2`:

| Component                                                              | Use when                                                 |
| ---------------------------------------------------------------------- | -------------------------------------------------------- |
| [`SelectField`](/docs/web/components/select-field/code)                | Single-select with a search input in a form field layout |
| [`SelectFieldSync`](/docs/web/components/select-field/code)            | Same as `SelectField`, with sync option loading          |
| [`MultiSelectField`](/docs/web/components/multi-select-field/code)     | Multi-select with chips in a form field layout           |
| [`MultiSelectFieldSync`](/docs/web/components/multi-select-field/code) | Same as `MultiSelectField`, with sync option loading     |
| [`SelectMenu`](/docs/web/components/select-menu/code)                  | Single-select without a form field wrapper               |
| [`SelectMenuSync`](/docs/web/components/select-menu/code)              | Same as `SelectMenu`, with sync option loading           |
| [`MultiSelectMenu`](/docs/web/components/multi-select-menu/code)       | Multi-select without a form field wrapper                |
| [`MultiSelectMenuSync`](/docs/web/components/multi-select-menu/code)   | Same as `MultiSelectMenu`, with sync option loading      |

### Combobox is deprecated

`Combobox` is deprecated and will be removed in **Anvil2 5.0**. It will continue to work in the meantime, but the console will log a one-time warning in development to remind you to migrate.

The select field components are a direct replacement. They cover everything Combobox handles, plus lazy loading, grouping, and multi-select.

## Why this is a milestone

The beta lifecycle exists so we can ship components into production before their APIs are locked down. That gives you access to new capabilities early and gives us room to refine based on real usage. The flip side is that using beta components comes with the expectation of occasional migration work.

Graduating to stable is how that lifecycle is supposed to end. It's a signal that the component has been through enough real-world usage that we're confident in the API, and we're committing to backward compatibility going forward.

The select components are the first components to complete that journey in Anvil2. There are more components in beta today, and they'll each follow the same path as they stabilize.

## How to migrate

### Update import paths

For most consumers, the migration is a find-and-replace:

```tsx theme={null}
// Before
import { SelectField, SelectFieldSync } from "@servicetitan/anvil2/beta";
import { MultiSelectField } from "@servicetitan/anvil2/beta";

// After
import { SelectField, SelectFieldSync } from "@servicetitan/anvil2";
import { MultiSelectField } from "@servicetitan/anvil2";
```

No prop changes are required. The components are identical at the API level — only the import path changes.

### Migrate from Combobox to SelectField

The Combobox API is structured quite differently from the select components. Combobox uses a render-prop pattern with several required sub-components (`Combobox.SearchField`, `Combobox.Content`, `Combobox.List`, `Combobox.Item`) and accepts generic typed items with separate `itemToString` and `itemToKey` converters. The select components have a flat, declarative API — you pass an `options` array and they handle rendering internally.

```tsx theme={null}
// Before (Combobox)
import { Combobox } from "@servicetitan/anvil2";

<Combobox
  items={items}
  itemToString={(item) => (item ? item.name : "")}
  itemToKey={(item) => (item ? item.id : null)}
  selectedItem={selectedItem}
  onChange={setSelectedItem}
  filterOptions={{ keys: ["name"] }}
>
  <Combobox.SearchField label="Label" />
  <Combobox.Content>
    {({ items }) => (
      <Combobox.List>
        {items.map((item, i) => (
          <Combobox.Item key={item.id} item={item} index={i}>
            {item.name}
          </Combobox.Item>
        ))}
      </Combobox.List>
    )}
  </Combobox.Content>
</Combobox>

// After (SelectFieldSync)
import { SelectFieldSync } from "@servicetitan/anvil2";

<SelectFieldSync
  label="Label"
  options={items.map((item) => ({ value: item.id, label: item.name }))}
  value={selectedItem?.id ?? null}
  onChange={(value) => setSelectedItem(items.find((i) => i.id === value) ?? null)}
/>
```

<Note>
  Because the migration involves more than swapping an import, a dedicated migration guide and codemod will be published before Anvil2 5.0. We'll announce that in [#ask-designsystem](https://servicetitan.enterprise.slack.com/archives/CBSRGHTRS) when it's ready. In the meantime, the [SelectField code docs](/docs/web/components/select-field/code) and [MultiSelectField code docs](/docs/web/components/multi-select-field/code) are the best reference for the target API.
</Note>

For the full migration details for each promoted component, see the individual beta-changes pages:

* [SelectField stable promotion →](/docs/web/components/select-field/beta-changes/stable-promotion)
* [MultiSelectField stable promotion →](/docs/web/components/multi-select-field/beta-changes/stable-promotion)
* [SelectMenu stable promotion →](/docs/web/components/select-menu/beta-changes/stable-promotion)
* [MultiSelectMenu stable promotion →](/docs/web/components/multi-select-menu/beta-changes/stable-promotion)

## Breaking change timeline

| Change                                  | Version        |
| --------------------------------------- | -------------- |
| Beta export deprecated (warning logged) | Now            |
| Beta export removed                     | Anvil2 **4.0** |
| `Combobox` deprecated (warning logged)  | Now            |
| `Combobox` removed                      | Anvil2 **5.0** |

<Info>
  The 4.0 release is planned for early August. When future releases are planned, we'll communicate timelines well in advance through [#ask-designsystem](https://servicetitan.enterprise.slack.com/archives/CBSRGHTRS).
</Info>

## Questions?

Reach out in [#ask-designsystem](https://servicetitan.enterprise.slack.com/archives/CBSRGHTRS) or check the [component docs](/docs/web/components/select-field/code) for full API details.
