Documentation versions (currently viewingVaadin 24)

Create a Form Component for Editing Contacts

Shows how to compose reusable components with a basic layout and components.

The list view now has a grid to display Contact objects. To complete the view, you need to create a form for editing contacts — like the one in the screenshot here:

A screenshot of the application highlighting the contact editing form

This part covers creating a new component, as well as importing and using a custom component.

Components Using Composition

Vaadin Flow is a component-based framework. In the previous parts here, you worked with several components, like Grid, TextField, and VerticalLayout. However, the real power of the component-based architecture is in the ability to create your own components.

Instead of building an entire view in a single class, your view can be composed of smaller components that each handle different parts of the view. The advantage of this approach is that individual components are easier to understand and test. The top-level view is used mainly to orchestrate the components.

Form Component

The form component you’ll create needs text fields for the first and last name, an email field, and two select fields: one to select the company, and another to select the contact status.

Start by creating a new file,, in the com.example.application.views.list package. If you’re using IntelliJ, copy the code below and paste it into the views package. IntelliJ automatically creates the file.

Create a file automatically by pasting a class definition into a Java package in IntelliJ IDEA.
package com.example.application.views.list;

import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField;

import java.util.List;

public class ContactForm extends FormLayout { // (1)
  TextField firstName = new TextField("First name"); // (2)
  TextField lastName = new TextField("Last name");
  EmailField email = new EmailField("Email");
  ComboBox<Status> status = new ComboBox<>("Status");
  ComboBox<Company> company = new ComboBox<>("Company");

  Button save = new Button("Save");
  Button delete = new Button("Delete");
  Button close = new Button("Cancel");

  public ContactForm(List<Company> companies, List<Status> statuses) {
    addClassName("contact-form"); // (3)


    add(firstName, // (4)

  private HorizontalLayout createButtonsLayout() {
    save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); // (5)

    save.addClickShortcut(Key.ENTER); // (6)

    return new HorizontalLayout(save, delete, close); // (7)
  1. ContactForm extends FormLayout: a responsive layout that shows form fields in one or two columns, depending on the viewport width.

  2. Creates all the UI components as fields in the component.

  3. Gives the component a CSS class name, so you can style it later.

  4. Adds all the UI components to the layout. The buttons require a bit of extra configuration. Create and call a new method, createButtonsLayout().

  5. Makes the buttons visually distinct from each other using built-in theme variants.

  6. Defines keyboard shortcuts: Enter to save and Escape to close the editor.

  7. Returns a HorizontalLayout containing the buttons to place them next to each other.

Add Form to Main View

The next step is to add the form you created to the main view. To do this, change ListView as follows:

public class ListView extends VerticalLayout {
    Grid<Contact> grid = new Grid<>(Contact.class);
    TextField filterText = new TextField();
    ContactForm form; // (1)

    public ListView() {
        configureForm(); // (2)

        add(getToolbar(), getContent()); // (3)

    private Component getContent() {
        HorizontalLayout content = new HorizontalLayout(grid, form);
        content.setFlexGrow(2, grid); // (4)
        content.setFlexGrow(1, form);
        return content;

    private void configureForm() {
        form = new ContactForm(Collections.emptyList(), Collections.emptyList()); // (5)

    // Remaining methods omitted
  1. Creates a reference to the form so you have access to it from other methods.

  2. Create a method for initializing the form.

  3. Change the add() method to call getContent(). The method returns a HorizontalLayout that wraps the form and the grid, showing them next to each other.

  4. Use setFlexGrow() to specify that the Grid should have twice the space of the form.

  5. Initialize the form with empty company and status lists: you’ll add these on the next part.

You can now build the project to reload the browser. You should see the form on the right side of the grid.

The form component is visible to the right of the grid.

Now that you have the view built, it’s time to connect it to the backend.

migration assistance

Download free e-book.
The complete guide is also available in an easy-to-follow PDF format.

Open in a
new tab
export class RenderBanner extends HTMLElement {
  connectedCallback() {

  renderBanner() {
    let bannerWrapper = document.getElementById('tocBanner');

    if (bannerWrapper) {

    let tocEl = document.getElementById('toc');

    // Add an empty ToC div in case page doesn't have one.
    if (!tocEl) {
      const pageTitle = document.querySelector(
        'main > article > header[class^=PageHeader-module--pageHeader]'
      tocEl = document.createElement('div');

      pageTitle?.insertAdjacentElement('afterend', tocEl);

    // Prepare banner container
    bannerWrapper = document.createElement('div'); = 'tocBanner';

    // Banner elements
    const text = document.querySelector('.toc-banner-source-text')?.innerHTML;
    const link = document.querySelector('.toc-banner-source-link')?.textContent;

    const bannerHtml = `<div class='toc-banner'>
          <a href='${link}'>
            <div class="toc-banner--img"></div>
            <div class='toc-banner--content'>${text}</div>

    bannerWrapper.innerHTML = bannerHtml;

    // Add banner image
    const imgSource = document.querySelector('.toc-banner-source .image');
    const imgTarget = bannerWrapper.querySelector('.toc-banner--img');

    if (imgSource && imgTarget) {