The Element API and Web 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.

Flow’s component classes are wrappers over a server-side representation of DOM elements. When no component exists for what you need — a raw element, a browser API call, or a third-party Web Component — the Element API gives you direct, type-checked access to that DOM from Java. This page follows the Element API guide and Integrating Web Components. It replaces the Vaadin 7 GWT connector / shared-state / RPC widget model.

Working with elements

Every Component exposes getElement(); you can also build elements directly:

Element wrapper = new Element("section");
wrapper.setAttribute("class", "panel");
wrapper.getStyle().set("padding", "var(--lumo-space-m)");
wrapper.setText("Hello");

Element input = new Element("input");
input.setProperty("placeholder", "Search…");   // a JS property, not an attribute
input.setAttribute("type", "search");

wrapper.appendChild(input);
getElement().appendChild(wrapper);

Attributes are strings in HTML; properties are live values on the DOM object. setProperty accepts String, boolean, double and JsonValue. getStyle() sets inline CSS.

DOM events

addEventListener wires a server-side listener to a client event; @EventData pulls fields off the event or element into the callback, and setFilter / debounce cut traffic:

input.addEventListener("input", e -> {
    String value = e.getEventData().getString("element.value");
    filter(value);
}).addEventData("element.value").debounce(300);

On a component, @DomEvent maps a custom element event to a typed ComponentEvent:

@DomEvent("selection-change")
public static class SelectionChangeEvent extends ComponentEvent<MyPicker> {
    private final String value;
    public SelectionChangeEvent(MyPicker source, boolean fromClient,
                                @EventData("event.detail.value") String value) {
        super(source, fromClient);
        this.value = value;
    }
}

Calling JavaScript

executeJs(expression, params…​) runs a snippet in the element’s context; $0, $1, … are the parameters, and the returned PendingJavaScriptResult resolves with the expression’s value:

getElement().executeJs("return navigator.language").then(String.class, lang ->
        applyLocale(lang));

getElement().executeJs("$0.scrollIntoView({behavior:'smooth'})", target.getElement());

@ClientCallable exposes a server method to client code by name:

@ClientCallable
public void onClientReady(String token) {
    session.setToken(token);
}
// on the client: this.$server.onClientReady(myToken)

Integrating a Web Component

To use a published custom element, declare its npm package and module and map its tag to a Java class. @NpmPackage adds the dependency; @JsModule imports the module; @Tag binds the element:

@Tag("emoji-picker")
@NpmPackage(value = "emoji-picker-element", version = "1.21.0")
@JsModule("emoji-picker-element/index.js")
public class EmojiPicker extends Component {

    private static final PropertyDescriptor<String, String> LOCALE =
            PropertyDescriptors.propertyWithDefault("locale", "en");

    public void setLocale(String locale) { LOCALE.set(this, locale); }
    public String getLocale() { return LOCALE.get(this); }

    public EmojiPicker() {
        getElement().addEventListener("emoji-click", e -> fireEvent(
                new EmojiSelectedEvent(this, true,
                        e.getEventData().getString("event.detail.unicode"))));
    }
}

PropertyDescriptor gives a typed getter/setter over a DOM property with a default. Reusable behaviour — HasSize, HasStyle, Focusable<T>, or your own — is added through mixin interfaces whose default methods call the Element API, so a component gains setWidth, addClassName, focus() and the like just by implementing them. @CssImport and @JavaScript load a stylesheet or a plain script into the page. getElement().getShadowRoot() (or attachShadow()) reaches a shadow tree when you build one server-side.

See also