Multi-Select Combo Box allows the user to choose one or more values from a filterable list of options presented in an overlay.
The component supports the same features as the regular Combo Box, such as lazy loading or allowing custom typed values.
import { css, html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/multi-select-combo-box';
import { getCountries } from 'Frontend/demo/domain/DataService';
import type Country from 'Frontend/generated/com/vaadin/demo/domain/Country';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('multi-select-combo-box-basic')
export class Example extends LitElement {
static override styles = css`
vaadin-multi-select-combo-box {
width: 300px;
}
`;
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
// tag::snippet[]
@state()
private items: Country[] = [];
protected override async firstUpdated() {
this.items = await getCountries();
}
protected override render() {
return html`
<vaadin-multi-select-combo-box
label="Countries"
item-label-path="name"
item-id-path="id"
.items="${this.items}"
></vaadin-multi-select-combo-box>
`;
}
// end::snippet[]
}
Country.ts
/**
* This module is generated from com.vaadin.demo.domain.Country.
* All changes to this file are overridden. Consider editing the corresponding Java file if necessary.
* @see {@link file:///srv/jenkins/workspace/docs/docs-site-v23/docs/src/main/java/com/vaadin/demo/domain/Country.java}
*/
export default interface Country {
name: string;
abbreviation: string;
id: number;
}
Country.tsMultiSelectComboBoxBasic.java
package com.vaadin.demo.component.multiselectcombobox;
import com.vaadin.demo.domain.Country;
import com.vaadin.demo.domain.DataService;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("multi-select-combo-box-basic")
public class MultiSelectComboBoxBasic extends Div {
public MultiSelectComboBoxBasic() {
// tag::snippet[]
MultiSelectComboBox<Country> comboBox = new MultiSelectComboBox<>(
"Countries");
comboBox.setItems(DataService.getCountries());
comboBox.setItemLabelGenerator(Country::getName);
add(comboBox);
// end::snippet[]
comboBox.setWidth("300px");
}
}
Country.java
package com.vaadin.demo.domain;
import javax.annotation.Nonnull;
// tag::snippet[]
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 id == other.id;
}
}
// end::snippet[]
The overlay opens when the user clicks the field using a pointing device.
Using the Up and Downarrow keys or typing a character (that occurs in at least one of the options) when the field is focused also opens the popup.
Common Combo Box Features
Multi-Select Combo Box supports the following features from the regular Combo Box.
All the linked examples and code snippets can be applied by replacing the Combo Box with a Multi-Select Combo Box.
Popup Width, using the --vaadin-multi-select-combo-box-overlay-width style, instead of --vaadin-combo-box-overlay-width
Selection
The component allows selecting multiple values, each of which is displayed as a chip inside the component.
If there isn’t enough space in the component to display chips for all selected values, then some values are collapsed into an overflow chip.
The example below shows a Multi-Select Combo Box with multiple preselected values, some of which are collapsed into the overflow chip:
import { css, html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/multi-select-combo-box';
import { getCountries } from 'Frontend/demo/domain/DataService';
import type Country from 'Frontend/generated/com/vaadin/demo/domain/Country';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('multi-select-combo-box-selection')
export class Example extends LitElement {
static override styles = css`
vaadin-multi-select-combo-box {
width: 300px;
}
`;
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
@state()
private items: Country[] = [];
protected override async firstUpdated() {
this.items = await getCountries();
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<vaadin-multi-select-combo-box
label="Countries"
item-label-path="name"
item-id-path="id"
.items="${this.items}"
.selectedItems="${this.items.slice(0, 4)}"
></vaadin-multi-select-combo-box>
<!-- end::snippet[] -->
`;
}
}
Country.ts
/**
* This module is generated from com.vaadin.demo.domain.Country.
* All changes to this file are overridden. Consider editing the corresponding Java file if necessary.
* @see {@link file:///srv/jenkins/workspace/docs/docs-site-v23/docs/src/main/java/com/vaadin/demo/domain/Country.java}
*/
export default interface Country {
name: string;
abbreviation: string;
id: number;
}
Country.tsMultiSelectComboBoxSelection.java
package com.vaadin.demo.component.multiselectcombobox;
import com.vaadin.demo.domain.Country;
import com.vaadin.demo.domain.DataService;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("multi-select-combo-box-selection")
public class MultiSelectComboBoxSelection extends Div {
public MultiSelectComboBoxSelection() {
MultiSelectComboBox<Country> comboBox = new MultiSelectComboBox<>(
"Countries");
List<Country> countries = DataService.getCountries();
comboBox.setItems(countries);
// tag::snippet[]
comboBox.select(countries.subList(0, 4));
// end::snippet[]
comboBox.setItemLabelGenerator(Country::getName);
comboBox.setWidth("300px");
add(comboBox);
}
}
Country.java
package com.vaadin.demo.domain;
import javax.annotation.Nonnull;
// tag::snippet[]
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 id == other.id;
}
}
// end::snippet[]
When the overlay is closed, items can be removed one by one (starting with the most recently selected item) using the Backspace key.
The first Backspace press moves focus to the last chip; the second press removes that chip, and the corresponding item, from the selection.
Selection Change
The following example demonstrates how to listen for selection changes:
Use the selected-items-changed event to react to the user changing the selection.
Source code
multi-select-combo-box-selection-change.ts
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/horizontal-layout';
import '@vaadin/multi-select-combo-box';
import type { MultiSelectComboBoxSelectedItemsChangedEvent } from '@vaadin/multi-select-combo-box';
import '@vaadin/text-area';
import { getCountries } from 'Frontend/demo/domain/DataService';
import type Country from 'Frontend/generated/com/vaadin/demo/domain/Country';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('multi-select-combo-box-selection-change')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
@state()
private items: Country[] = [];
protected override async firstUpdated() {
this.items = await getCountries();
}
// tag::snippet[]
@state()
private selectedCountries: Country[] = [];
private get selectedCountriesText(): string {
return this.selectedCountries.map((country) => country.name).join(', ');
}
protected override render() {
return html`
<vaadin-horizontal-layout theme="spacing">
<vaadin-multi-select-combo-box
label="Countries"
item-label-path="name"
item-id-path="id"
.items="${this.items}"
.selectedItems="${this.selectedCountries}"
@selected-items-changed="${(e: MultiSelectComboBoxSelectedItemsChangedEvent<Country>) => {
this.selectedCountries = e.detail.value;
}}"
></vaadin-multi-select-combo-box>
<vaadin-text-area
label="Selected Countries"
readonly
.value="${this.selectedCountriesText}"
></vaadin-text-area>
</vaadin-horizontal-layout>
`;
}
// end::snippet[]
}
Country.ts
/**
* This module is generated from com.vaadin.demo.domain.Country.
* All changes to this file are overridden. Consider editing the corresponding Java file if necessary.
* @see {@link file:///srv/jenkins/workspace/docs/docs-site-v23/docs/src/main/java/com/vaadin/demo/domain/Country.java}
*/
export default interface Country {
name: string;
abbreviation: string;
id: number;
}
Country.tsMultiSelectComboBoxSelectionChange.java
package com.vaadin.demo.component.multiselectcombobox;
import com.vaadin.demo.domain.Country;
import com.vaadin.demo.domain.DataService;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.router.Route;
import java.util.List;
import java.util.stream.Collectors;
@Route("multi-select-combo-box-selection-change")
public class MultiSelectComboBoxSelectionChange extends Div {
public MultiSelectComboBoxSelectionChange() {
MultiSelectComboBox<Country> comboBox = new MultiSelectComboBox<>(
"Countries");
List<Country> countries = DataService.getCountries();
comboBox.setItems(countries);
comboBox.setItemLabelGenerator(Country::getName);
// tag::snippet[]
TextArea selectedCountries = new TextArea("Selected Countries");
selectedCountries.setReadOnly(true);
comboBox.addValueChangeListener(e -> {
String selectedCountriesText = e.getValue().stream()
.map(Country::getName).collect(Collectors.joining(", "));
selectedCountries.setValue(selectedCountriesText);
});
// end::snippet[]
HorizontalLayout layout = new HorizontalLayout(comboBox,
selectedCountries);
add(layout);
}
}
Country.java
package com.vaadin.demo.domain;
import javax.annotation.Nonnull;
// tag::snippet[]
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 id == other.id;
}
}
// end::snippet[]
Read-Only
The component can be set to read-only, which prevents the user from modifying its value.
A read-only Multi-Select Combo Box still allows opening the overlay, which then shows only the selected values, instead of all the options.
This can be useful if selected values have been collapsed into the overflow chip.
import { css, html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/multi-select-combo-box';
import { getCountries } from 'Frontend/demo/domain/DataService';
import type Country from 'Frontend/generated/com/vaadin/demo/domain/Country';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('multi-select-combo-box-read-only')
export class Example extends LitElement {
static override styles = css`
vaadin-multi-select-combo-box {
width: 300px;
}
`;
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
@state()
private items: Country[] = [];
protected override async firstUpdated() {
this.items = await getCountries();
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<vaadin-multi-select-combo-box
label="Countries"
item-label-path="name"
item-id-path="id"
.items="${this.items}"
.selectedItems="${this.items.slice(0, 4)}"
readonly
></vaadin-multi-select-combo-box>
<!-- end::snippet[] -->
`;
}
}
MultiSelectComboBoxReadOnly.java
package com.vaadin.demo.component.multiselectcombobox;
import com.vaadin.demo.domain.Country;
import com.vaadin.demo.domain.DataService;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("multi-select-combo-box-read-only")
public class MultiSelectComboBoxReadOnly extends Div {
public MultiSelectComboBoxReadOnly() {
MultiSelectComboBox<Country> comboBox = new MultiSelectComboBox<>(
"Countries");
List<Country> countries = DataService.getCountries();
comboBox.setItems(countries);
comboBox.select(countries.subList(0, 4));
comboBox.setItemLabelGenerator(Country::getName);
// tag::snippet[]
comboBox.setReadOnly(true);
// end::snippet[]
comboBox.setWidth("300px");
add(comboBox);
}
}
Internationalization (i18n)
Multi-Select Combo Box allows localizing the following messages.
These texts are only used in screen reader announcements, and can’t be observed visually.
Message
Default
Description
cleared
"Selection cleared"
Announced by screen readers when the clear button is clicked
focused
" focused. Press Backspace to remove"
Announced by screen readers when a chip is focused
selected
" added to selection"
Announced by screen readers when an item is added to the selection.
deselected
" removed from selection"
Announced by screen readers when an item is removed from the selection.
total
"{count} items selected"
Announced by screen readers to inform about the total number of selected items.
The string must contain a {count} placeholder, which is replaced with the actual count of selected items by the component.
The following example demonstrates how to localize the component’s messages into German:
import { css, html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/multi-select-combo-box';
import type { MultiSelectComboBoxI18n } from '@vaadin/multi-select-combo-box';
import { getCountries } from 'Frontend/demo/domain/DataService';
import type Country from 'Frontend/generated/com/vaadin/demo/domain/Country';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('multi-select-combo-box-basic')
export class Example extends LitElement {
static override styles = css`
vaadin-multi-select-combo-box {
width: 300px;
}
`;
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
@state()
private items: Country[] = [];
protected override async firstUpdated() {
this.items = await getCountries();
}
// tag::snippet[]
private i18n: MultiSelectComboBoxI18n = {
cleared: 'Alle Einträge entfernt',
focused: ' ausgewählt. Drücke Rücktaste zum Entfernen',
selected: ' hinzugefügt',
deselected: ' entfernt',
total: '{count} Einträge ausgewählt',
};
protected override render() {
return html`
<vaadin-multi-select-combo-box
label="Länder"
item-label-path="name"
item-id-path="id"
.items="${this.items}"
.i18n="${this.i18n}"
></vaadin-multi-select-combo-box>
`;
}
// end::snippet[]
}
MultiSelectComboBoxI18nDemo.java
package com.vaadin.demo.component.multiselectcombobox;
import com.vaadin.demo.domain.Country;
import com.vaadin.demo.domain.DataService;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.combobox.MultiSelectComboBoxI18n;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
@Route("multi-select-combo-box-i18n")
public class MultiSelectComboBoxI18nDemo extends Div {
public MultiSelectComboBoxI18nDemo() {
MultiSelectComboBox<Country> comboBox = new MultiSelectComboBox<>(
"Länder");
comboBox.setItems(DataService.getCountries());
comboBox.setItemLabelGenerator(Country::getName);
comboBox.setWidth("300px");
add(comboBox);
// tag::snippet[]
MultiSelectComboBoxI18n i18n = new MultiSelectComboBoxI18n();
i18n.setCleared("Alle Einträge entfernt");
i18n.setFocused(" ausgewählt. Drücke Rücktaste zum Entfernen");
i18n.setSelected(" hinzugefügt");
i18n.setDeselected(" entfernt");
i18n.setTotal("{count} Einträge ausgewählt");
comboBox.setI18n(i18n);
// end::snippet[]
}
}
Best Practices
Multi-Select Combo Box supports lazy loading for large datasets.
It reduces the initial load time, and consumes less bandwidth and resources.
For consistency, the default width of the Multi-Select Combo Box matches that of other input fields.
You should increase the width of the component when using items with long labels, or if you expect users to select several items, to avoid collapsing selected items into the overflow chip.