Building Custom Components

This section documents the current Vaadin release line — Vaadin 24 LTS / 25.x, Java 17+, Spring Boot 3 / Jakarta EE 10 — as published at the official Vaadin documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. Flow (server-side Java) is the authoring style used throughout, with Hilla / React shown where it differs; Vaadin 7 and the pre-Flow architecture appear only as migration contrast.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since Vaadin ships major releases roughly twice a year and its ecosystem iterates.

This section’s bibliography lists the reference material consulted while preparing these pages.

When a screen repeats a cluster of components with its own behaviour, promote it to a class. Flow gives four levels of custom component, from "just group these" to "a brand-new element". This page follows Build a Component and Creating components. It replaces the Vaadin 7 GWT widget / connector approach.

Compose with Composite

Composite<T> builds a component from other components without exposing the wrapper’s own layout API. The content is created once in initContent():

public class SearchBar extends Composite<HorizontalLayout> {

    private final TextField field = new TextField();
    private final Button button = new Button(VaadinIcon.SEARCH.create());

    public SearchBar() {
        field.setPlaceholder("Search…");
        field.setValueChangeMode(ValueChangeMode.LAZY);
        getContent().add(field, button);
        getContent().setAlignItems(FlexComponent.Alignment.END);
    }

    public void addSearchListener(Consumer<String> listener) {
        Runnable fire = () -> listener.accept(field.getValue());
        button.addClickListener(e -> fire.run());
        field.addValueChangeListener(e -> fire.run());
    }
}

Callers see SearchBar and addSearchListener(…​), not a HorizontalLayout. Extending a component directly (class PrimaryButton extends Button) is fine when you genuinely want its whole API.

Build from an element

For a component with no Flow counterpart, start from @Tag and the Element API (see The Element API and Web Components):

@Tag("hr")
public class ThematicBreak extends Component {
    public ThematicBreak() {
        getElement().setAttribute("role", "separator");
    }
}

A component with a value

A field participates in Binder by implementing HasValue. AbstractField<C, T> implements it over an element property; AbstractCompositeField<C, S, T> does the same but backed by a composed component; and CustomField<T> is the simplest — add child fields, then implement generateModelValue() and setPresentationValue(t):

public class MoneyField extends CustomField<Money> {

    private final NumberField amount = new NumberField();
    private final Select<Currency> currency = new Select<>();

    public MoneyField(String label) {
        setLabel(label);
        currency.setItems(Currency.getAvailableCurrencies());
        add(new HorizontalLayout(amount, currency));
    }

    @Override
    protected Money generateModelValue() {
        return amount.isEmpty() ? null
                : new Money(currency.getValue(), amount.getValue());
    }

    @Override
    protected void setPresentationValue(Money money) {
        amount.setValue(money == null ? null : money.amount());
        currency.setValue(money == null ? null : money.currency());
    }
}

// then it binds like any field
binder.forField(new MoneyField("Price")).bind(Product::getPrice, Product::setPrice);

Packaging an add-on

To share a component, build it as its own Maven artifact. The vaadin-archetype-addon archetype produces a project with the right layout and an addon assembly. Publish the jar and list it on the Vaadin Directory; consumers add the vaadin-addons repository and your dependency (see UI Component Libraries):

mvn -B archetype:generate \
  -DarchetypeGroupId=com.vaadin \
  -DarchetypeArtifactId=vaadin-archetype-addon \
  -DarchetypeVersion=LATEST \
  -DgroupId=org.example -DartifactId=money-field

Keep the frontend files under src/main/resources/META-INF/frontend (or reference them with @JsModule) so the consuming project’s build bundles them.

Wrapping client libraries

  • A JavaScript library — add it with @NpmPackage + @JsModule, then drive it from a @Tag-backed component with executeJs(…​) and DOM event listeners (see The Element API and Web Components).

  • A React component — extend ReactAdapterComponent, ship a small .tsx adapter under the frontend folder, and pass props and events across with setState(…​) / addStateChangeListener(…​). See Add a React component.

See also