Docs

Integrating Web Components

How to create new HTML tags with custom names.

Web Components are a collection of web standards that allow you to create new HTML tags with custom names. They’re reusable and provide full encapsulation of styles and markup. See Introduction to Web Components for more information on how it works. This page, however, explains how to integrate it into your projects.

To use a Web Component in Vaadin, you first have to load the HTML, JavaScript, and CSS files needed by the component. This is explained below. Then you need to employ a Java API to configure the component, from which to listen for events. See Creating a Java API for a Web Component for more information on that.

The Web Component’s client-side files — typically JavaScript module files — are available using npm, which Vaadin supports by default. It automatically installs and uses npm packages. It also serves the static files to the browser.

Tip
Using pnpm or bun instead of npm
Vaadin also supports using pnpm (known as performant npm) or bun. See Configuring npm/pnpm/bun for details.
Tip
Packaging and publishing components
For step-by-step guides on packaging a component as a reusable add-on or publishing it to the Vaadin Directory, see Package a Component and Publish a Component.

Annotations

Every web component integration requires three annotations on the Java class:

@Tag defines the HTML element name. It must match the name the web component registers with customElements.define().

@NpmPackage declares the npm package to install. Vaadin runs npm install automatically during development builds.

@JsModule specifies the JavaScript module to import. This triggers the web component’s registration in the browser.

The following is an example of annotations for the mwc-slider Web Component:

Source code
Java
@Tag("mwc-slider")
@NpmPackage(value = "@material/mwc-slider",
            version = "0.27.0")
@JsModule("@material/mwc-slider/slider.js")

The @Tag annotation here defines the name of the HTML element. The @JsModule and @NpmPackage annotations define the import of the JavaScript module.

Adding Frontend Files

Your component may require in-project frontend files, such as additional JavaScript modules. In which case, add them to the src/main/resources/META-INF/frontend directory so that they’re packaged in the component JAR if you choose to make an add-on of your component.

As a example, you might use the @JsModule annotation to add a local JavaScript or TypeScript module like so:

Source code
Java
@JsModule("./my-local-module.js")
Note
Use explicit relative paths in CSS imports
When importing CSS files within other CSS files, always use explicit relative paths (e.g., @import "./second.css"; instead of @import "second.css";). Some tools like Tailwind CSS require this notation to resolve imports correctly. See Theming Issues for more details.

TypeScript Support

Vaadin fully supports TypeScript for client-side files. You can use .ts files with the @JsModule annotation exactly as you would use .js files:

Source code
Java
@JsModule("./my-component.ts")

TypeScript provides type safety for property definitions, event data, and configuration objects, which is especially valuable for complex components. It also enables better IDE support with auto-completion and inline documentation.

When using Lit to define Web Components, TypeScript enables typed decorators for properties and events:

Source code
TypeScript
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('my-component')
class MyComponent extends LitElement {
  @property({ type: String })
  label: string = '';

  @property({ type: Number })
  count: number = 0;

  render() {
    return html`<span>${this.label}: ${this.count}</span>`;
  }
}

See Wrap a Web Component for a complete example, and Advanced Web Component Integration for guidance on wrapping npm packages with TypeScript and using advanced Lit features.

Why @NpmPackage Is Declared on Components

You might wonder why the @NpmPackage annotation is placed directly on component classes rather than in a centralized configuration file. This design is intentional:

Automatic tree-shaking

Only npm packages referenced by components that are actually used in the application are installed. If a component class is never referenced, its npm dependencies are excluded from the bundle.

Self-contained add-ons

When creating a reusable add-on (e.g., for the Vaadin Directory), the component class carries its own dependency declarations. Consumers don’t need to manually configure npm packages — adding the Java dependency is sufficient.

Clear dependency graph

Each component explicitly declares what it needs. This makes it straightforward to understand which client-side libraries a component relies on, and avoids hidden or implicit dependencies.

Updated