> Markdown version of [Combo Box](https://vaadin.com/docs/next/components/combo-box). Section index: [llms.txt](https://vaadin.com/docs/next/components/llms.txt)

# Combo Box

Combo Box allows the user to choose a value from a filterable list of options presented in an overlay. It supports lazy loading and can be configured to accept custom typed values.

**Lit** — `combo-box-basic.ts`

```html
<vaadin-combo-box
  label="Country"
  item-label-path="name"
  item-value-path="id"
  .items="${this.items}"
></vaadin-combo-box>
```

**Lit** — `Country.ts`

```typescript
interface Country {
    name: string;
    abbreviation: string;
    id: number;
}
export default Country;
```

**Flow** — `ComboBoxBasic.java`

```java
ComboBox<Country> comboBox = new ComboBox<>("Country");
comboBox.setItems(DataService.getCountries());
comboBox.setItemLabelGenerator(Country::getName);
add(comboBox);
```

**Flow** — `Country.java`

```java
public class Country {

    @Nonnull
    private String name;

    @Nonnull
    private String abbreviation;

    @Nonnull
    private Integer id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public void setAbbreviation(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Country)) {
            return false;
        }
        Country other = (Country) obj;
        return Objects.equals(id, other.id);
    }
}
```

**React** — `combo-box-basic.tsx`

```tsx
<ComboBox label="Country" itemLabelPath="name" itemValuePath="id" items={items.value} />
```

The overlay opens when the user clicks the field using a pointing device. Using the Up/Down arrow keys or typing a character — found in at least one of the options — when the field is focused also opens the popup.

> **Note: Selection Uses equals() and hashCode()**
>
> Combo Box matches the selected value to its items using `equals()` and `hashCode()`, not reference identity. So `setValue()` preselects an item only if the value you pass is *equal* to one of the items given to `setItems()`. The `Country` class above overrides both methods (based on its `id`), which is why a matching value is preselected.
>
> This matters for entity types: a JPA entity that doesn’t override `equals()` and `hashCode()` won’t preselect, because a freshly loaded instance isn’t equal to the option instance the component holds — so the field silently stays empty. Either override both methods (for example, based on the entity’s ID), or pass the exact item instances the component already holds.

## <a id="custom-value-entry"></a>Custom Value Entry

Combo Box can be configured to allow entering custom values that aren’t included in the list of options.

**Lit** — `combo-box-custom-entry-1.ts`

```html
<vaadin-combo-box
  allow-custom-value
  label="Browser"
  helper-text="Select or type a browser"
  .items="${this.items}"
></vaadin-combo-box>
```

**Flow** — `ComboBoxCustomEntry1.java`

```java
ComboBox<String> comboBox = new ComboBox<>("Browser");
comboBox.setAllowCustomValue(true);
add(comboBox);
```

**React** — `combo-box-custom-entry-1.tsx`

```tsx
<ComboBox
  allowCustomValue
  label="Browser"
  helperText="Select or type a browser"
  items={items}
/>
```

Allowing custom entry is useful when you need to present the most common choices, but still give users the freedom to enter their own options.

Custom values can also be stored and added to the list of options:

**Lit** — `combo-box-custom-entry-2.ts`

```html
<vaadin-combo-box
  allow-custom-value
  label="Browser"
  helper-text="Select or type a browser"
  .items="${this.items}"
  @custom-value-set="${(event: ComboBoxCustomValueSetEvent) => {
    this.items = [...this.items, event.detail];
  }}"
></vaadin-combo-box>
```

**Flow** — `ComboBoxCustomEntry2.java`

```java
ComboBox<String> comboBox = new ComboBox<>("Browser");
comboBox.setAllowCustomValue(true);
comboBox.addCustomValueSetListener(e -> {
    String customValue = e.getDetail();
    items.add(customValue);
    comboBox.setItems(items);
    comboBox.setValue(customValue);
});
add(comboBox);
```

**React** — `combo-box-custom-entry-2.tsx`

```tsx
<ComboBox
  allowCustomValue
  label="Browser"
  helperText="Select or type a browser"
  items={items.value}
  onCustomValueSet={(event) => {
    items.value = [...items.value, event.detail];
  }}
/>
```

## <a id="custom-item-presentation"></a>Custom Item Presentation

See [Select, Custom Item Presentation.](https://vaadin.com/docs/next/components/select.md#custom-item-presentation)

Items can be customized to display more information than a single line of text.

**Lit** — `combo-box-presentation.ts`

```html
<vaadin-combo-box
  label="Choose doctor"
  item-label-path="displayName"
  .filteredItems="${this.filteredItems}"
  style="--vaadin-combo-box-overlay-width: 16em"
  @filter-changed="${this.filterChanged}"
  ${comboBoxRenderer(this.renderer, [])}
></vaadin-combo-box>

...

private renderer: ComboBoxLitRenderer<Person> = (person) => html`
  <div class="person-item">
    <img
      src="${person.pictureUrl}"
      alt="Portrait of ${person.firstName} ${person.lastName}"
      style="width: 2.25rem;"
    />
    <span>${person.firstName} ${person.lastName}</span>
    <span>${person.profession}</span>
  </div>
`;
```

**Lit** — `Person.ts`

```typescript
import type Address_1 from "./Address.js";
interface Person {
    firstName: string;
    lastName: string;
    email: string;
    birthday: string;
    id: number;
    subscriber: boolean;
    membership: string;
    pictureUrl: string;
    profession: string;
    address: Address_1;
    managerId?: number;
    manager: boolean;
    status: string;
    fullName?: string;
}
export default Person;
```

**Flow** — `ComboBoxPresentation.java`

```java
ComboBox<Person> comboBox = new ComboBox<>("Choose doctor");
comboBox.setItems(filter, DataService.getPeople());
comboBox.setItemLabelGenerator(
        person -> person.getFirstName() + " " + person.getLastName());
comboBox.setRenderer(createRenderer());
comboBox.getStyle().set("--vaadin-combo-box-overlay-width", "16em");
add(comboBox);

...

private Renderer<Person> createRenderer() {
    return LitRenderer
            .<Person> of(
                    """
                            <div class="person-item">
                              <img src="${item.pictureUrl}" alt="Portrait of ${item.firstName} ${item.lastName}" style="width: 2.25rem;" />
                              <span>${item.firstName} ${item.lastName}</span>
                              <span>${item.profession}</span>
                            </div>
                            """)
            .withProperty("pictureUrl", Person::getPictureUrl)
            .withProperty("firstName", Person::getFirstName)
            .withProperty("lastName", Person::getLastName)
            .withProperty("profession", Person::getProfession);
}
```

**Flow** — `Person.java`

```java
public class Person {

    @Nonnull
    private String firstName;

    @Nonnull
    private String lastName;

    @Nonnull
    private String email;

    @Nonnull
    private Date birthday;

    @Nonnull
    private Integer id;

    @Nonnull
    private Boolean subscriber;

    @Nonnull
    private String membership;

    @Nonnull
    private String pictureUrl;

    @Nonnull
    private String profession;

    @Nonnull
    private Address address;

    private Integer managerId;

    @Nonnull
    private Boolean manager;

    @Nonnull
    private String status;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public boolean isSubscriber() {
        return subscriber;
    }

    public void setSubscriber(boolean subscriber) {
        this.subscriber = subscriber;
    }

    public String getMembership() {
        return membership;
    }

    public void setMembership(String membership) {
        this.membership = membership;
    }

    public String getPictureUrl() {
        return pictureUrl;
    }

    public void setPictureUrl(String pictureUrl) {
        this.pictureUrl = pictureUrl;
    }

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return id == other.id;
    }

    public Integer getManagerId() {
        return managerId;
    }

    public void setManagerId(Integer managerId) {
        this.managerId = managerId;
    }

    public boolean isManager() {
        return manager;
    }

    public void setManager(boolean manager) {
        this.manager = manager;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
```

**React** — `combo-box-presentation.tsx`

```tsx
function renderPerson({ item: person }: { item: Person }) {
  return (
    <div className="person-item">
      <img
        src={person.pictureUrl}
        alt={`Portrait of ${person.firstName} ${person.lastName}`}
        style={{ width: '2.25rem' }}
      />
      <span>
        {person.firstName} {person.lastName}
      </span>
      <span>{person.profession}</span>
    </div>
  );
}

function Example() {
  const allItems = useSignal<Person[]>([]);
  const filteredItems = useSignal<Person[]>([]);

  useEffect(() => {
    getPeople().then(({ people }) => {
      const items = people.map((person) => ({
        ...person,
        displayName: `${person.firstName} ${person.lastName}`,
      }));
      allItems.value = items;
      filteredItems.value = items;
    });
  }, []);

  const filterChanged = (e: ComboBoxFilterChangedEvent) => {
    const filter = e.detail.value;
    filteredItems.value = allItems.value.filter(({ firstName, lastName, profession }) =>
      `${firstName} ${lastName} ${profession}`.toLowerCase().includes(filter.toLowerCase())
    );
  };

  return (
    <ComboBox
      label="Choose doctor"
      itemLabelPath="displayName"
      filteredItems={filteredItems.value}
      style={{ '--vaadin-combo-box-overlay-width': '16em' } as React.CSSProperties}
      onFilterChanged={filterChanged}
      renderer={renderPerson}
    />
  );
}
```

`person-item.css`

```css
.person-item {
    display: grid;
    grid-template-columns: min-content auto;
    grid-template-rows: auto auto;
    grid-template-areas:
        "avatar name"
        "avatar title";
    gap: 0 var(--vaadin-gap-s);
    align-items: center;
    line-height: 1.2;

    & > :is(vaadin-avatar, img) {
        grid-area: avatar;
    }

    & > span:first-of-type {
        grid-area: name;
    }

    & > span:last-of-type {
        grid-area: title;
        font-size: 0.875rem;
        font-weight: 400;
        color: var(--vaadin-text-color-secondary);
    }
}
```

Use a custom filter to allow the user to search by the rendered properties. It’s recommended to make filtering case insensitive.

## <a id="item-class-names"></a>Item Class Names

Items can be styled dynamically, based on application logic and the data in the combo box, through custom class names.

**Lit** — `combo-box-item-class-name.ts`

```typescript
protected override render() {
  return html`
    <vaadin-combo-box
      label="Fruit"
      .items="${this.items}"
      .itemClassNameGenerator="${this.classNameGenerator}"
    ></vaadin-combo-box>
  `;
}

protected classNameGenerator(item: string): string {
  switch (item) {
    case 'Apple':
      return 'coral';
    case 'Banana':
      return 'gold';
    case 'Orange':
      return 'orange';
    case 'Pear':
      return 'yellowgreen';
    default:
      return '';
  }
}
```

**Flow** — `ComboBoxItemClassName.java`

```java
ComboBox<String> comboBox = new ComboBox<>("Fruit");
comboBox.setItems(List.of("Apple", "Banana", "Orange", "Pear"));
comboBox.setClassNameGenerator((item) -> {
    switch (item) {
    case "Apple":
        return "coral";
    case "Banana":
        return "gold";
    case "Orange":
        return "orange";
    case "Pear":
        return "yellowgreen";
    default:
        return "";
    }
});
```

**React** — `combo-box-item-class-name.tsx`

```typescript
const items = ['Apple', 'Banana', 'Orange', 'Pear'];

const classNameGenerator = (item: string) => {
  switch (item) {
    case 'Apple':
      return 'coral';
    case 'Banana':
      return 'gold';
    case 'Orange':
      return 'orange';
    case 'Pear':
      return 'yellowgreen';
    default:
      return '';
  }
};

return <ComboBox label="Fruit" items={items} itemClassNameGenerator={classNameGenerator} />;
```

`combo-box-item-class-name.css`

```css
/* Add this to your global CSS, for example in: */
/* src/main/resources/META-INF/resources/styles.css */

vaadin-combo-box-item.coral {
  color: coral;
}

vaadin-combo-box-item.gold {
  color: gold;
}

vaadin-combo-box-item.orange {
  color: orange;
}

vaadin-combo-box-item.yellowgreen {
  color: yellowgreen;
}
```

## <a id="auto-open"></a>Auto Open

The overlay opens automatically when the field is focused using a pointer (i.e., mouse or touch), or when the user types in the field. You can disable this so that the overlay opens only when the toggle button or the `Up`/`Down` arrow keys are pressed.

**Lit** — `combo-box-auto-open.ts`

```html
<vaadin-combo-box
  auto-open-disabled
  label="Country"
  item-label-path="name"
  item-value-path="id"
  .items="${this.items}"
></vaadin-combo-box>
```

**Lit** — `Country.ts`

```typescript
interface Country {
    name: string;
    abbreviation: string;
    id: number;
}
export default Country;
```

**Flow** — `ComboBoxAutoOpen.java`

```java
ComboBox<Country> comboBox = new ComboBox<>("Country");
comboBox.setAutoOpen(false);
add(comboBox);
```

**Flow** — `Country.java`

```java
public class Country {

    @Nonnull
    private String name;

    @Nonnull
    private String abbreviation;

    @Nonnull
    private Integer id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public void setAbbreviation(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Country)) {
            return false;
        }
        Country other = (Country) obj;
        return Objects.equals(id, other.id);
    }
}
```

**React** — `combo-box-auto-open.tsx`

```tsx
<ComboBox
  autoOpenDisabled
  label="Country"
  itemLabelPath="name"
  itemValuePath="id"
  items={items.value}
/>
```

## <a id="popup-width"></a>Popup Width

The width of the popup is, by default, the same width as the input field. The popup width can be overridden to any fixed width in cases where the default width is too narrow.

**Lit** — `combo-box-popup-width.ts`

```html
<vaadin-combo-box
  style="--vaadin-combo-box-overlay-width: 350px"
  label="Employee"
  item-label-path="displayName"
  item-value-path="id"
  .items="${this.items}"
></vaadin-combo-box>
```

**Lit** — `Person.ts`

```typescript
import type Address_1 from "./Address.js";
interface Person {
    firstName: string;
    lastName: string;
    email: string;
    birthday: string;
    id: number;
    subscriber: boolean;
    membership: string;
    pictureUrl: string;
    profession: string;
    address: Address_1;
    managerId?: number;
    manager: boolean;
    status: string;
    fullName?: string;
}
export default Person;
```

**Flow** — `ComboBoxPopupWidth.java`

```java
ComboBox<Person> comboBox = new ComboBox<>("Employee");
comboBox.getStyle().set("--vaadin-combo-box-overlay-width", "350px");
add(comboBox);
```

**Flow** — `Person.java`

```java
public class Person {

    @Nonnull
    private String firstName;

    @Nonnull
    private String lastName;

    @Nonnull
    private String email;

    @Nonnull
    private Date birthday;

    @Nonnull
    private Integer id;

    @Nonnull
    private Boolean subscriber;

    @Nonnull
    private String membership;

    @Nonnull
    private String pictureUrl;

    @Nonnull
    private String profession;

    @Nonnull
    private Address address;

    private Integer managerId;

    @Nonnull
    private Boolean manager;

    @Nonnull
    private String status;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public boolean isSubscriber() {
        return subscriber;
    }

    public void setSubscriber(boolean subscriber) {
        this.subscriber = subscriber;
    }

    public String getMembership() {
        return membership;
    }

    public void setMembership(String membership) {
        this.membership = membership;
    }

    public String getPictureUrl() {
        return pictureUrl;
    }

    public void setPictureUrl(String pictureUrl) {
        this.pictureUrl = pictureUrl;
    }

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return id == other.id;
    }

    public Integer getManagerId() {
        return managerId;
    }

    public void setManagerId(Integer managerId) {
        this.managerId = managerId;
    }

    public boolean isManager() {
        return manager;
    }

    public void setManager(boolean manager) {
        this.manager = manager;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
```

**React** — `combo-box-popup-width.tsx`

```tsx
<ComboBox
  style={{ '--vaadin-combo-box-overlay-width': '350px' } as React.CSSProperties}
  label="Employee"
  itemLabelPath="displayName"
  itemValuePath="id"
  items={items.value}
/>
```

## <a id="custom-filtering"></a>Custom Filtering

Combo Box’s filtering, by default, is configured to show only items that contain the entered value:

`combo-box-filtering-1.ts`

```html
<vaadin-combo-box
  label="Country"
  item-label-path="name"
  item-value-path="id"
  .items="${this.items}"
></vaadin-combo-box>
```

**React** — `combo-box-filtering-1.tsx`

```tsx
<ComboBox label="Country" itemLabelPath="name" itemValuePath="id" items={items.value} />
```

Custom filtering is also possible. For example, if you only want to show items that start with the user’s input you could do something like this:

**Lit** — `combo-box-filtering-2.ts`

```typescript
@customElement('combo-box-filtering-2')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    applyTheme(root);
    return root;
  }

  @state()
  private allItems: Country[] = [];

  @state()
  private filteredItems: Country[] = [];

  protected override async firstUpdated() {
    const countries = await getCountries();
    this.allItems = countries;
    this.filteredItems = countries;
  }

  protected override render() {
    return html`
      <vaadin-combo-box
        label="Country"
        item-label-path="name"
        item-value-path="id"
        .filteredItems="${this.filteredItems}"
        @filter-changed="${this.filterChanged}"
      ></vaadin-combo-box>
    `;
  }

  private filterChanged(event: ComboBoxFilterChangedEvent) {
    const filter = event.detail.value;
    this.filteredItems = this.allItems.filter(({ name }) =>
      name.toLowerCase().startsWith(filter.toLowerCase())
    );
  }
}
```

**Flow** — `ComboBoxFiltering2.java`

```java
ComboBox<Country> comboBox = new ComboBox<>("Country");

ItemFilter<Country> filter = (country, filterString) -> country
        .getName().toLowerCase().startsWith(filterString.toLowerCase());
comboBox.setItems(filter, DataService.getCountries());

add(comboBox);
```

**React** — `combo-box-filtering-2.tsx`

```tsx
<ComboBox
  label="Country"
  itemLabelPath="name"
  itemValuePath="id"
  filteredItems={filteredItems.value}
  onFilterChanged={filterChanged}
/>
```

## <a id="partial-match-behavior"></a>Partial Match Behavior (since V25.3)

Pressing `Enter` or blurring the field (for example, by using `Tab`) while filtering commits an item only when the typed filter matches its full label. If the filter matches the label only partially, the item has to be focused in the overlay first, for example with the `Down` arrow key.

To skip this step and speed up data entry, Combo Box can be configured to also commit partially matching items using the `setPartialMatchMode` method. This method supports the following modes:

| Mode          | Description                                                                |
| ------------- | -------------------------------------------------------------------------- |
| `NONE`        | Commits an item only when the filter matches its label exactly (default).  |
| `FIRST_MATCH` | Commits the first item in the filtered results.                            |
| `ONLY_MATCH`  | Commits the item only when it’s the only one left in the filtered results. |

The item that will be committed is highlighted in the overlay as the user types. Matching is case-insensitive, and an exact match is always preferred over partial matches.

The following example uses the `FIRST_MATCH` mode. Try typing `gre` and pressing `Enter`: this commits *Greece*, the first matching country, even though *Greenland* and *Grenada* also match the filter.

**Lit** — `combo-box-partial-match-mode.ts`

```html
<vaadin-combo-box
  partial-match-mode="first-match"
  label="Country"
  item-label-path="name"
  item-value-path="id"
  .items="${this.items}"
></vaadin-combo-box>
```

**Flow** — `ComboBoxPartialMatchMode.java`

```java
ComboBox<Country> comboBox = new ComboBox<>("Country");
comboBox.setPartialMatchMode(PartialMatchMode.FIRST_MATCH);
add(comboBox);
```

**React** — `combo-box-partial-match-mode.tsx`

```tsx
<ComboBox
  partialMatchMode="first-match"
  label="Country"
  itemLabelPath="name"
  itemValuePath="id"
  items={items.value}
/>
```

> **Note:** This feature isn’t supported when [custom values](#custom-value-entry) are allowed, since any partially matching input is itself a valid custom value. It also requires the overlay to be open, so that the user can see which items match the filter. For example, with [auto open](#auto-open) disabled, typing alone doesn’t highlight a partial match or set it to be selected until the overlay is opened.

## <a id="lazy-loading"></a>Lazy Loading

Combo Box supports lazy loading, which is useful when dealing with large datasets. This avoids loading the whole dataset into memory and only fetches data for the current filter and viewport.

**Lit** — `combo-box-lazy-loading.ts`

```typescript
async dataProvider(
  params: ComboBoxDataProviderParams,
  callback: ComboBoxDataProviderCallback<Country>
) {
  // Convert the params to Spring Data Pageable
  const { filter } = params;
  const pageable = {
    pageNumber: params.page,
    pageSize: params.pageSize,
    sort: { orders: [] },
  };
  // Call backend service with pageable and current combo box filter
  const pageItems = await ComboBoxCountryService.list(pageable, filter);

  // Estimate the total count of filtered items
  let filteredCount;
  if (pageItems.length === params.pageSize) {
    filteredCount = (params.page + 1) * params.pageSize + 1;
  } else {
    filteredCount = params.page * params.pageSize + pageItems.length;
  }

  callback(pageItems, filteredCount);
}

protected override render() {
  return html`
    <vaadin-combo-box
      .dataProvider="${this.dataProvider}"
      label="Country"
      item-label-path="name"
      item-value-path="id"
    ></vaadin-combo-box>
  `;
}
```

**Lit** — `ComboBoxCountryService.java`

```java
// @BrowserCallable and @AnonymousAllowed are only required if you want
// to use the service from a Hilla view
@BrowserCallable
@AnonymousAllowed
public class ComboBoxCountryService {
    private final ComboBoxCountryRepository repository;

    public ComboBoxCountryService(ComboBoxCountryRepository repository) {
        this.repository = repository;
    }

    public @NonNull List<@NonNull Country> list(Pageable pageable,
            String filter) {
        // Implement your data fetching and filtering logic here
        // For this example, we're using a Spring Data repository
        return repository.findByNameContainingIgnoreCase(filter, pageable);
    }
}
```

**Lit** — `ComboBoxCountryRepository.java`

```java
public interface ComboBoxCountryRepository extends JpaRepository<Country, Long> {
    List<Country> findByNameContainingIgnoreCase(String name, Pageable pageable);
}
```

**Flow** — `ComboBoxLazyLoading.java`

```java
public class ComboBoxLazyLoading extends Div {
    public ComboBoxLazyLoading(ComboBoxCountryService countryService) {
        ComboBox<Country> comboBox = new ComboBox<>("Country");
        comboBox.setItemLabelGenerator(Country::getName);
        // Connect the combo box to a Spring service that fetches data based
        // on a Spring Data pageable and the current combo box filter
        comboBox.setItemsPageable(countryService::list);
        add(comboBox);
    }

}
```

**Flow** — `ComboBoxCountryService.java`

```java
// @BrowserCallable and @AnonymousAllowed are only required if you want
// to use the service from a Hilla view
@BrowserCallable
@AnonymousAllowed
public class ComboBoxCountryService {
    private final ComboBoxCountryRepository repository;

    public ComboBoxCountryService(ComboBoxCountryRepository repository) {
        this.repository = repository;
    }

    public @NonNull List<@NonNull Country> list(Pageable pageable,
            String filter) {
        // Implement your data fetching and filtering logic here
        // For this example, we're using a Spring Data repository
        return repository.findByNameContainingIgnoreCase(filter, pageable);
    }
}
```

**Flow** — `ComboBoxCountryRepository.java`

```java
public interface ComboBoxCountryRepository extends JpaRepository<Country, Long> {
    List<Country> findByNameContainingIgnoreCase(String name, Pageable pageable);
}
```

**React** — `combo-box-lazy-loading.tsx`

```tsx
function Example() {
  // Create a data provider that calls a backend service with a
  // Spring Data pageable and the current combo box filter
  const dataProvider = useComboBoxDataProvider(
    ComboBoxCountryService.list.bind(ComboBoxCountryService)
  );

  return (
    <ComboBox dataProvider={dataProvider} label="Country" itemLabelPath="name" itemValuePath="id" />
  );
}
```

**React** — `ComboBoxCountryService.java`

```java
// @BrowserCallable and @AnonymousAllowed are only required if you want
// to use the service from a Hilla view
@BrowserCallable
@AnonymousAllowed
public class ComboBoxCountryService {
    private final ComboBoxCountryRepository repository;

    public ComboBoxCountryService(ComboBoxCountryRepository repository) {
        this.repository = repository;
    }

    public @NonNull List<@NonNull Country> list(Pageable pageable,
            String filter) {
        // Implement your data fetching and filtering logic here
        // For this example, we're using a Spring Data repository
        return repository.findByNameContainingIgnoreCase(filter, pageable);
    }
}
```

**React** — `ComboBoxCountryRepository.java`

```java
public interface ComboBoxCountryRepository extends JpaRepository<Country, Long> {
    List<Country> findByNameContainingIgnoreCase(String name, Pageable pageable);
}
```

## <a id="focus-selected-item-flow"></a>Focus Selected Item Flow (since V25.2)

By default, the overlay always opens at the top of the list. For long lists with the selected item far down, this makes it hard to see what’s selected without scrolling. To address this, you can enable `setFocusSelectedItem(true)` so the overlay opens scrolled to and focused on the selected item instead.

`ComboBoxFocusSelectedItem.java`

```java
ComboBox<Country> comboBox = new ComboBox<>("Country");
comboBox.setItems(DataService.getCountries());
comboBox.setItemLabelGenerator(Country::getName);
comboBox.setFocusSelectedItem(true);
```

For [lazy data providers](#lazy-loading), the lazy data view must also have an `ItemIndexProvider` configured so the selected item’s index can be resolved against the current sorting and filter. In-memory data works out of the box.

`ComboBoxFocusSelectedItemLazy.java`

```java
ComboBox<Country> comboBox = new ComboBox<>("Country");
comboBox.setItemLabelGenerator(Country::getName);

ComboBoxLazyDataView<Country> dataView = comboBox.setItems(
        query -> filteredCountries(query.getFilter())
                .skip(query.getOffset()).limit(query.getLimit()),
        query -> (int) filteredCountries(query.getFilter()).count());

// Resolves the selected item's index against the current filter so
// setFocusSelectedItem can scroll the overlay to it on open.
dataView.setItemIndexProvider((item, query) -> {
    int index = filteredCountries(
            query.getFilter().map(Object::toString)).toList()
            .indexOf(item);
    return index >= 0 ? index : null;
});

comboBox.setFocusSelectedItem(true);
```

See [Callback for an Item Index](https://vaadin.com/docs/next/flow/binding-data/data-provider.md#callback-for-an-item-index) for more on `ItemIndexProvider`.

## <a id="basic-features"></a>Basic Features

The following features, common to most input field components, are supported:

Label

The label is used to identify the input field. It supports plain-text content. In the Lumo theme its length is limited to the width of the field (and truncated with ellipsis), while in the Aura theme labels wrap to multiple lines. [Helpers](#helper) and [Tooltips](#tooltip) can be used to provide additional information that doesn’t fit into the label.

Visible labels are strongly recommended for all input fields. In cases where the built-in label cannot be used, an external element can be associated as the field’s label through the `aria-labelledby` attribute (`setAriaLabelledBy` in Flow). Fields without any visible label should include an invisible label for assistive technologies with the `aria-label` attribute (`setAriaLabel` in Flow).

Helper

Helpers are used to provide additional information that the user may need to enter in the field, such as format requirements or explanations of the field’s purpose below the field.

A [style variant](https://vaadin.com/docs/next/components/combo-box/styling.md#style-variants) is available for rendering the helper above the field.

In addition to plain text, helpers can contain components and HTML elements. However, complex and interactive content is likely to have accessibility issues.

Placeholder

The placeholder is text that’s displayed when the field is empty. Its primary purpose is to provide a short input hint (e.g., the expected format) in situations where a [Helper](#helper) cannot be used.

Placeholders should not be used as a replacement for a visible label. They can be mistaken for a manually entered value. See [Label](#label) for alternatives to the built-in field label.

Tooltip

Tooltips are small text pop-ups displayed on hover, and on keyboard-focus. They can be used to provide additional information about a field. This can be useful in situations where an always visible [Helper](#helper) is not appropriate. Helpers are generally recommended in favor of tooltips, though, as they provide much better discoverability and mobile support. See the [Tooltip](https://vaadin.com/docs/next/components/tooltip.md) documentation for more information.

Clear Button

The clear button — which is displayed when the field is not empty — clears the field’s current value. Although the button itself is not keyboard focusable, the clear action can be taken with the `Esc` key, when the field has focus. The clear button can be especially useful in search and filter fields, where users often need to clear the value. They’re less useful, however, in regular forms.

Prefix

A prefix element — rendered at the start of the field — can be used to display units, icons, and similar visual cues to the field’s purpose or format.

Prefix elements aren’t automatically announced by screen readers as part of the field. Text content (e.g., in a `Span` or `Div`) that should be announced can be associated with the field (since V25.3) using the `accessible-description-ref` attribute (`setAriaDescribedBy` in Flow). Focusable elements (e.g., buttons) are announced separately when focused.

```html
<vaadin-combo-box accessible-description-ref="currency-prefix">
  <span slot="prefix" id="currency-prefix">$</span>
</vaadin-combo-box>
```

```java
Span prefix = new Span("$");
field.setPrefixComponent(prefix);
field.setAriaDescribedBy(prefix);
```

Associated elements are announced in addition to the field’s helper and error message.

External & Invisible Labels (ARIA)

Visible labels are strongly recommended for all input fields. In situations where the built-in label cannot be used, an external element can be associated as the field’s label through its element `id`. Fields without any visible label should be provided an invisible label for assistive technologies like screen readers.

```html
<!-- Associates external element as label: -->
<label id="external-label">This is the label</label>
<vaadin-combo-box accessible-name-ref="external-label">...

<!-- Invisible label for screen readers: -->
<vaadin-combo-box accessible-name="This is the label">...
```

```java
// Associates external element as label:
NativeLabel label = new NativeLabel("This is the label");
label.setId("external-label");
field.setAriaLabelledBy("external-label");

// Invisible label for screen readers:
field.setAriaLabel("This is the label");
```

**Lit** — `combo-box-basic-features.ts`

```typescript
<vaadin-combo-box
  label="Label"
  helper-text="Helper text"
  placeholder="Placeholder"
  clear-button-visible
  .items="${['Value']}"
>
  <vaadin-tooltip slot="tooltip" text="Tooltip text"></vaadin-tooltip>
  <vaadin-icon slot="prefix" icon="vaadin:search"></vaadin-icon>
</vaadin-combo-box>
```

**Flow** — `ComboBoxBasicFeatures.java`

```java
ComboBox<String> field = new ComboBox<>();
field.setLabel("Label");
field.setHelperText("Helper text");
field.setPlaceholder("Placeholder");
field.setTooltipText("Tooltip text");
field.setClearButtonVisible(true);
field.setPrefixComponent(VaadinIcon.SEARCH.create());
```

**React** — `combo-box-basic-features.tsx`

```tsx
<ComboBox
  label="Label"
  helperText="Helper text"
  placeholder="Placeholder"
  clearButtonVisible
  items={['Value']}
>
  <Tooltip slot="tooltip" text="Tooltip text" />
  <Icon slot="prefix" icon="vaadin:search" />
</ComboBox>
```

## <a id="validation"></a>Validation

Combo Box provides a validation mechanism based on constraints. Constraints allow you to define criteria that the value must meet to be considered valid. Validation occurs typically when the user initiates a value change, for example by selecting an item from the overlay or through text input followed by `Enter`. If the value is invalid, the field is highlighted in red, and an error message appears underneath the input. Certain constraints, however, can proactively prevent users from entering characters that would result in an invalid value.

Below is a list of supported constraints with more detailed information:

Required

Required fields are marked with an indicator next to the label, and become invalid if their value is first entered and then cleared.

An instruction text at the top of the form explaining the required indicator is recommended. The indicator itself can be customized with the `--vaadin-input-field-required-indicator` style property.

Allowed Characters

A separate single-character, regular expression can be used to restrict the characters that can be entered into the field. Characters that don’t match the expression are rejected. However, values set programmatically are not subject to this restriction, even if they contain disallowed characters.

**Lit** — `combo-box-validation.ts`

```typescript
<vaadin-combo-box
  required
  allowed-char-pattern="[A-Z]"
  label="Country code"
  helper-text="2-letter uppercase ISO country code"
  error-message="Field is required"
  allow-custom-value
  .items="${['DE', 'FI', 'US']}"
></vaadin-combo-box>
```

**Flow** — `ComboBoxValidation.java`

```java
ComboBox<String> field = new ComboBox<>();
field.setRequiredIndicatorVisible(true);
field.setAllowedCharPattern("[A-Z]");
field.setI18n(new ComboBoxI18n()
        .setRequiredErrorMessage("Field is required"));
```

**React** — `combo-box-validation.tsx`

```tsx
<ComboBox
  required
  allowedCharPattern="[A-Z]"
  label="Country code"
  helperText="2-letter uppercase ISO country code"
  errorMessage="Field is required"
  allowCustomValue
  items={['DE', 'FI', 'US']}
/>
```

> **Note: Data Binding & Custom Validation**
>
> Flow and Hilla offer an advanced API called Binder that allows you to bind data and add custom validation rules for multiple fields, creating forms. You can learn more about Binder from the corresponding [Flow](https://vaadin.com/docs/next/flow/binding-data/components-binder-validation.md) and [Hilla](https://vaadin.com/docs/next/hilla/guides/forms/binder-validation.md) articles.

## <a id="read-only-disabled"></a>Read-Only & Disabled

Fields used to display values should be set to `read-only` mode to prevent editing. Read-only fields are focusable and visible to screen readers. They can display tooltips. Their values can be selected and copied.

Fields that are currently unavailable should be `disabled`. The reduced contrast of disabled fields makes them inappropriate for displaying information. They can’t be focused or display tooltips. They’re invisible to screen readers, and their values cannot be selected and copied.

Disabled fields can be useful in situations where they can become enabled based on some user action. Consider hiding fields entirely if there’s nothing the user can do to make them editable.

**Lit** — `combo-box-readonly-and-disabled.ts`

```typescript
<vaadin-combo-box readonly label="Read-only" .items="${['Value']}" value="Value">
</vaadin-combo-box>

<vaadin-combo-box disabled label="Disabled"></vaadin-combo-box>
```

**Flow** — `ComboBoxReadonlyAndDisabled.java`

```java
ComboBox<String> readonlyField = new ComboBox<>();
readonlyField.setReadOnly(true);
readonlyField.setLabel("Read-only");
readonlyField.setItems("Value");
readonlyField.setValue("Value");

ComboBox<String> disabledField = new ComboBox<>();
disabledField.setEnabled(false);
disabledField.setLabel("Disabled");
```

**React** — `combo-box-readonly-and-disabled.tsx`

```tsx
<ComboBox readonly label="Read-only" items={['Value']} value="Value" />

<ComboBox disabled label="Disabled" />
```

## <a id="usage-as-autocomplete-field"></a>Usage as Autocomplete Field

As the user is typing, the Combo Box filters out the options that don’t match. Once the correct value has been found, the user can use the Up/Down arrow keys to navigate the list and the Enter key to set the value, essentially using the Combo Box as an autocomplete field.

## <a id="best-practices"></a>Best Practices

Combo Box supports lazy loading for large datasets. It reduces the initial load time, and consumes less bandwidth and resources.

> **Note: Don’t use as a menu.**
>
> Combo Box is an input field component, not a generic menu component. Use the Menu Bar component to create overlays for actions.

## <a id="related-components"></a>Related Components

| Component                                                                     | Usage Recommendation                                                                      |
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| [Select](https://vaadin.com/docs/next/components/select.md)                   | Simpler overlay selection field without filtering, lazy loading or custom value entry.    |
| [Radio Button Group](https://vaadin.com/docs/next/components/radio-button.md) | Better accessibility than Combo Box, as all options are visible without user interaction. |
| [List Box](https://vaadin.com/docs/next/components/list-box.md)               | Scrollable inline list of options. Supports single and multi-select.                      |
| [Menu Bar](https://vaadin.com/docs/next/components/menu-bar.md)               | Overlay menus for items that trigger actions.                                             |

`1DFB67E2-2A7B-4339-8C20-FC546C664BB2`
