import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/select';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('select-basic')
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 = [
{
label: 'Most recent first',
value: 'recent',
},
{
label: 'Rating: high to low',
value: 'rating-desc',
},
{
label: 'Rating: low to high',
value: 'rating-asc',
},
{
label: 'Price: high to low',
value: 'price-desc',
},
{
label: 'Price: low to high',
value: 'price-asc',
},
];
protected override render() {
return html`
<!-- tag::snippet[] -->
<vaadin-select
label="Sort by"
.items="${this.items}"
.value="${this.items[0].value}"
></vaadin-select>
<!-- end::snippet[] -->
`;
}
}
SelectBasic.java
package com.vaadin.demo.component.select;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.router.Route;
@Route("select-basic")
public class SelectBasic extends Div {
public SelectBasic() {
// tag::snippet[]
Select<String> select = new Select<>();
select.setLabel("Sort by");
select.setItems("Most recent first", "Rating: high to low",
"Rating: low to high", "Price: high to low",
"Price: low to high");
select.setValue("Most recent first");
add(select);
// end::snippet[]
}
}
The drop-down can be opened with a click, up/down arrow keys, or by typing the initial character of one of the options.
Common Input Field Features
Select includes all
Text Field and
shared input field features.
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/select';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('select-dividers')
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()
// tag::snippet[]
private items = [
{
label: 'Most recent first',
value: 'recent',
},
{
component: 'hr',
},
{
label: 'Rating: high to low',
value: 'rating-desc',
},
{
label: 'Rating: low to high',
value: 'rating-asc',
},
{
component: 'hr',
},
{
label: 'Price: high to low',
value: 'price-desc',
},
{
label: 'Price: low to high',
value: 'price-asc',
},
];
// end::snippet[]
protected override render() {
return html`
<vaadin-select
label="Sort by"
.items="${this.items}"
.value="${this.items[0].value!}"
></vaadin-select>
`;
}
}
SelectDividers.java
package com.vaadin.demo.component.select;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Hr;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.router.Route;
@Route("select-dividers")
public class SelectDividers extends Div {
public SelectDividers() {
// tag::snippet[]
Select<String> select = new Select<>();
select.setLabel("Sort by");
select.setItems("Most recent first", "Rating: high to low",
"Rating: low to high", "Price: high to low",
"Price: low to high");
select.addComponents("Most recent first", new Hr());
select.addComponents("Rating: low to high", new Hr());
select.setValue("Most recent first");
add(select);
// end::snippet[]
}
}
Note
Use Combo Box for long lists
For large data sets, it’s preferable to use Combo Box instead of Select, as it allows users to filter the list of options.
Disabled Items
Items can be disabled.
This prevents users from selecting them, while still showing that these items would be available for selection under different circumstances.
package com.vaadin.demo.component.select;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.router.Route;
@Route("select-placeholder")
public class SelectPlaceholder extends Div {
public SelectPlaceholder() {
// tag::snippet[]
Select<String> select = new Select<>();
select.setLabel("Size");
select.setItems("XS", "S", "M", "L", "XL");
select.setPlaceholder("Select size");
add(select);
// end::snippet[]
}
}
Empty Selection Item (Flow Only)
An empty item can be set as the first option.
Use it in cases where you want to allow users to clear their selection.
The value of the empty item is represented as null.
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/select';
import type { SelectItem } from '@vaadin/select';
import { applyTheme } from 'Frontend/generated/theme';
import { getPeople } from 'Frontend/demo/domain/DataService';
@customElement('select-complex-value-label')
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: SelectItem[] = [];
protected override async firstUpdated() {
const people = (await getPeople({ count: 5 })).people;
// tag::snippet[]
this.items = people.map((person) => ({
label: `${person.firstName} ${person.lastName}`,
value: `${person.id}`,
}));
// end::snippet[]
}
protected override render() {
return html`<vaadin-select label="Assignee" .items="${this.items}"></vaadin-select>`;
}
}
Person.ts
import Address from './Address';
/**
* This module is generated from com.vaadin.demo.domain.Person.
* 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/Person.java}
*/
export default interface Person {
firstName: string;
lastName: string;
email: string;
birthday: string;
id: number;
subscriber: boolean;
membership: string;
pictureUrl: string;
profession: string;
address: Address;
managerId?: number;
manager: boolean;
status: string;
}
Person.tsSelectComplexValueLabel.java
package com.vaadin.demo.component.select;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("select-complex-value-label")
public class SelectComplexValueLabel extends Div {
public SelectComplexValueLabel() {
// tag::snippet[]
Select<Person> select = new Select<>();
select.setLabel("Assignee");
// Display full name of the person as item text and selected value label
select.setItemLabelGenerator(Person::getFullName);
List<Person> people = DataService.getPeople(5);
select.setItems(people);
// end::snippet[]
add(select);
}
}
Person.java
package com.vaadin.demo.domain;
import java.util.Date;
import javax.annotation.Nonnull;
// tag::snippet[]
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;
}
}
// end::snippet[]
When using custom item renderers with rich content, a label can be set to represent the item value when it’s selected.
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/item';
import '@vaadin/list-box';
import '@vaadin/select';
import { selectRenderer } from '@vaadin/select/lit.js';
import { applyTheme } from 'Frontend/generated/theme';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
import { getPeople } from 'Frontend/demo/domain/DataService';
const formatPersonFullName = (person: Person) => `${person.firstName} ${person.lastName}`;
@customElement('select-custom-renderer-label')
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 people: Person[] = [];
protected override async firstUpdated() {
this.people = (await getPeople({ count: 5 })).people;
}
protected override render() {
return html`
<vaadin-select
label="Assignee"
${selectRenderer(
() => html`
<vaadin-list-box>
${this.people.map(
(person) => html`
<!-- tag::snippet[] -->
<!-- Use the label attribute to display full name of the person as selected value label -->
<vaadin-item value="${person.id}" label="${formatPersonFullName(person)}">
<div style="display: flex; align-items: center;">
<img
src="${person.pictureUrl}"
alt="Portrait of ${formatPersonFullName(person)}"
style="width: var(--lumo-size-m); margin-right: var(--lumo-space-s);"
/>
<div>
${formatPersonFullName(person)}
<div
style="font-size: var(--lumo-font-size-s); color: var(--lumo-secondary-text-color);"
>
${person.profession}
</div>
</div>
</div>
</vaadin-item>
<!-- end::snippet[] -->
`
)}
</vaadin-list-box>
`,
this.people
)}
></vaadin-select>
`;
}
}
Person.ts
import Address from './Address';
/**
* This module is generated from com.vaadin.demo.domain.Person.
* 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/Person.java}
*/
export default interface Person {
firstName: string;
lastName: string;
email: string;
birthday: string;
id: number;
subscriber: boolean;
membership: string;
pictureUrl: string;
profession: string;
address: Address;
managerId?: number;
manager: boolean;
status: string;
}
Person.tsSelectCustomRendererLabel.java
package com.vaadin.demo.component.select;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.FlexLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("select-custom-renderer-label")
public class SelectCustomRendererLabel extends Div {
public SelectCustomRendererLabel() {
// tag::snippet[]
Select<Person> select = new Select<>();
select.setLabel("Assignee");
// Use a custom renderer for items in the dropdown
select.setRenderer(SelectCustomRendererLabel.createPersonRenderer());
// Display full name of the person as selected value label
select.setItemLabelGenerator(Person::getFullName);
List<Person> people = DataService.getPeople(5);
select.setItems(people);
// end::snippet[]
add(select);
}
private static ComponentRenderer<FlexLayout, Person> createPersonRenderer() {
return new ComponentRenderer<>(person -> {
FlexLayout wrapper = new FlexLayout();
wrapper.setAlignItems(FlexComponent.Alignment.CENTER);
// NOTE
// We are using inline styles here to keep the example simple.
// We recommend placing CSS in a separate style sheet and to
// encapsulating the styling in a new component.
Image image = new Image();
image.setSrc(person.getPictureUrl());
image.setAlt("Portrait of " + person.getFirstName() + " "
+ person.getLastName());
image.setWidth("var(--lumo-size-m)");
image.getStyle().set("margin-right", "var(--lumo-space-s)");
Div info = new Div();
info.setText(person.getFirstName() + " " + person.getLastName());
Div profession = new Div();
profession.setText(person.getProfession());
profession.getStyle()
.set("color", "var(--lumo-secondary-text-color)")
.set("font-size", "var(--lumo-font-size-s)");
info.add(profession);
wrapper.add(image, info);
return wrapper;
});
}
}
Person.java
package com.vaadin.demo.domain;
import java.util.Date;
import javax.annotation.Nonnull;
// tag::snippet[]
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;
}
}
// end::snippet[]
Note
Flow-specific
When using setItemLabelGenerator() in combination with setEmptySelectionAllowed(), ensure that the implementation is capable of handling null values, as the empty selection item has the value null.
Source code
Java
select.setEmptySelectionAllowed(true);
select.setItemLabelGenerator(person -> {
if (person == null) {
return "No assignee";
}
return person.getFullName();
});
The same applies when using a data source that may contain null values.
Custom Item Presentation
Items can be rendered with rich content instead of plain text.
This can be useful to provide information in a more legible fashion than appending it to the item text.
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/item';
import '@vaadin/list-box';
import '@vaadin/select';
import { selectRenderer } from '@vaadin/select/lit.js';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('select-presentation')
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 people: Person[] = [];
protected override async firstUpdated() {
const { people } = await getPeople({ count: 4 });
this.people = people;
}
protected override render() {
return html`
<!-- tag::snippet[] -->
<vaadin-select
label="Choose doctor"
${selectRenderer(this.renderer, this.people)}
></vaadin-select>
<!-- end::snippet[] -->
`;
}
private renderer = () => html`
<vaadin-list-box>
${this.people.map(
(person) => html`
<vaadin-item value="${person.id}">
<!--
NOTE
We are using inline styles here to keep the example simple.
We recommend placing CSS in a separate style sheet and
encapsulating the styling in a new component.
-->
<div style="display: flex; align-items: center;">
<img
src="${person.pictureUrl}"
alt="Portrait of ${person.firstName} ${person.lastName}"
style="width: var(--lumo-size-m); margin-right: var(--lumo-space-s);"
/>
<div>
${person.firstName} ${person.lastName}
<div
style="font-size: var(--lumo-font-size-s); color: var(--lumo-secondary-text-color);"
>
${person.profession}
</div>
</div>
</div>
</vaadin-item>
`
)}
</vaadin-list-box>
`;
}
SelectPresentation.java
package com.vaadin.demo.component.select;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.orderedlayout.FlexLayout;
import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("select-presentation")
public class SelectPresentation extends Div {
private List<Person> items = DataService.getPeople(5);
public SelectPresentation() {
// tag::snippet[]
Select<Person> select = new Select<>();
select.setLabel("Choose doctor");
select.setRenderer(new ComponentRenderer<>(person -> {
FlexLayout wrapper = new FlexLayout();
wrapper.setAlignItems(Alignment.CENTER);
// NOTE
// We are using inline styles here to keep the example simple.
// We recommend placing CSS in a separate style sheet and to
// encapsulating the styling in a new component.
Image image = new Image();
image.setSrc(person.getPictureUrl());
image.setAlt("Portrait of " + person.getFirstName() + " "
+ person.getLastName());
image.setWidth("var(--lumo-size-m)");
image.getStyle().set("margin-right", "var(--lumo-space-s)");
Div info = new Div();
info.setText(person.getFirstName() + " " + person.getLastName());
Div profession = new Div();
profession.setText(person.getProfession());
profession.getStyle().set("font-size", "var(--lumo-font-size-s)");
profession.getStyle().set("color",
"var(--lumo-secondary-text-color)");
info.add(profession);
wrapper.add(image, info);
return wrapper;
}));
select.setItems(items);
add(select);
// end::snippet[]
}
}
Best Practices
Set a Default Value
Where applicable, set the most common choice as the default value.
Don’t Use as a Menu
Select is an input field component, not a generic menu component.
Use Menu Bar to create overlays for actions.