Docs

Documentation versions (currently viewingVaadin 24)

Vaadin Component Type Definitions for TypeScript

Understanding how to use Vaadin components' TypeScript definitions with web components in TypeScript views.

Vaadin components come with TypeScript definitions located in d.ts files published to npm together with the source code.

Using proper types for the web components helps to make the client-side logic more reliable. Depending on the IDE you use, TypeScript definitions can also enable better code completion and auto-import.

When using Visual Studio Code, it’s recommended that you install the lit-plugin, which provides syntax highlighting and type-checking support for Lit templates. You can also use the lit-analyzer CLI tool by the same author for type checking.

Importing Type Declarations

Every TypeScript definition file exports type declarations, which describe the public API provided by the web component. To use the type declarations in your client-side views, you need to import them using the type-only import syntax:

import type { Dialog } from '@vaadin/dialog';

The type declarations are only used by the TypeScript compiler to analyze statically the code and collect information about types. They’re removed during the Vite compilation step and don’t end up in the resulting JavaScript bundle.

The following sections give examples of how to improve the developer experience when using Vaadin components by applying correct types for elements, their properties, and events.

The @query Decorator

Lit discourages manual DOM operations in favor of binding data to the component template declaratively using reactive properties. However, it can sometimes be convenient to get a reference to the component instance and use it for method calls.

The suggested way of storing a reference in Lit-based views is the @query decorator. This expects the type of the corresponding web component to be specified:

@query('#dialog')
private dialog!: Dialog;

Note the ! sign used in the property declaration. This is the definite assignment assertion operator. It tells the TypeScript compiler that the dialog property is initialized indirectly and doesn’t need a default value.

Event Listeners

When using event listeners in Lit-based views, it might be useful to get a reference to the event target and then access its value. One common case for this is handling a change event.

The Lit syntax for binding listeners doesn’t let TypeScript know the type of component to which the listener belongs. You need to handle this manually by specifying the correct type:

render() {
  return html`
    <vaadin-text-field @change="${this.onChange}"></vaadin-text-field>
  `;
}

onChange(event: Event) {
  const field = event.composedPath()[0] as TextField;
  // <vaadin-text-field> has a value property.
  console.log(field.value);
}

The as keyword is a type assertion, often called a "type cast". It’s a hint for the TypeScript compiler, forcing it to use the type provided explicitly. In this particular case, it’s safe, as the component that dispatches the event is known in advance.

Custom Events

Vaadin components offer type definitions for custom events, making it easier to use them in TypeScript. In particular, this allows you to get a proper type for the detail property, which has the type of any by default. To use the event types, you need to import them manually:

import type { TextFieldInvalidChangedEvent } from '@vaadin/text-field';

render() {
  return html`
    <vaadin-text-field
      label="Username"
      @invalid-changed="${this.onInvalidChanged}"
    ></vaadin-text-field>
  `;
}

onInvalidChanged(event: TextFieldInvalidChangedEvent) {
  // `detail` is an object with boolean `value` property.
  console.log(event.detail.value);
}

Renderer Functions

A renderer function is a class method used to render parts of the DOM of a component. Renderer functions accept one or more arguments. To access renderer functions without TypeScript warnings, you need to get them typed by importing and using the corresponding type declarations:

protected indexRenderer(
  root: HTMLElement,
  column: GridColumn,
  model: GridItemModel<Person>
) {
  render(html`<div>${model.index}</div>`, root);
}

The GridItemModel type declaration is exported by the @vaadin/grid npm package. It’s a TypeScript interface describing the properties available on the model, including index. The GridColumn declaration is the type of the <vaadin-grid-column> component.

See also the Grid content example, which demonstrates how to define renderer functions on the Grid columns to provide rich content based on the column.

Generic Types

Certain Vaadin components — namely Grid, Combo Box, CRUD, and Virtual List — support setting the items property as an array of objects. Typically, when you use a component, you know the expected type of the object in advance and can provide it, explicitly.

In TypeScript, this could be achieved by using generic types. For example, a Grid type can be specified when using a @query decorator:

@query('#grid')
private grid!: Grid<Person>;

This type isn’t inferred by the component, internally. Therefore, the same generic type needs to be passed to the model argument of the renderer function:

nameRenderer(
  root: HTMLElement,
  column: GridColumn,
  model: GridItemModel<Person>
) {
  // `model` is an object with an `item` property of type `Person`
  const person = model.item;
  render(html`<div>${person.firstName} ${person.lastName}</div>`, root);
}

A type argument can be also used in event listeners to detect changes of some properties:

onSelectedItemChanged(event: ComboBoxSelectedItemChangedEvent<Person>) {
  // `detail` is an object of a `value` property of type `Person`
  console.log(event.detail.value);
}

Generic type arguments can be passed to various properties and TypeScript interfaces listed below.

Combo Box Generic Types

The following Combo Box properties support generic types:

  • dataProvider

  • filteredItems

  • items

  • renderer

  • selectedItem

The following Combo Box interfaces support generic type arguments:

  • ComboBoxDataProvider

  • ComboBoxDataProviderCallback

  • ComboBoxItemModel

  • ComboBoxRenderer

  • ComboBoxSelectedItemChangedEvent

CRUD Generic Types

The following CRUD properties support generic types:

  • dataProvider

  • editedItem

  • items

The following CRUD interfaces support generic type arguments:

  • CrudCancelEvent

  • CrudDataProviderCallback

  • CrudDataProvider

  • CrudDeleteEvent

  • CrudEditEvent

  • CrudEditedItemChangedEvent

  • CrudItemsChangedEvent

  • CrudSaveEvent

Grid Generic Types

The following Grid properties support generic types:

  • activeItem

  • cellClassNameGenerator

  • dataProvider

  • dragFilter

  • dropFilter

  • expandedItems

  • items

  • rowDetailsRenderer

  • selectedItems

The following Grid column properties support generic types:

  • footerRenderer

  • headerRenderer

  • renderer

The following Grid interfaces support generic type arguments:

  • GridActiveItemChangedEvent

  • GridBodyRenderer

  • GridCellActivateEvent

  • GridCellClassNameGenerator

  • GridCellFocusEvent

  • GridColumnReorderEvent

  • GridColumnResizeEvent

  • GridDataProvider

  • GridDragAndDropFilter

  • GridDragStartEvent

  • GridDropEvent

  • GridExpandedItemsChangedEvent

  • GridEventContext

  • GridItemModel

  • GridRowDetailsRenderer

  • GridSelectedItemsChangedEvent

Virtual List Generic Types

The following Virtual List properties support generic types:

  • items

  • renderer

The following Virtual List interfaces support generic type arguments:

  • VirtualListItemModel

  • VirtualListRenderer

Registering Elements

When creating custom elements to use with client-side views, you might want to instruct TypeScript to use your definitions. This isn’t required, but sometimes it improves the developer experience and allows you to write less code.

For example, if you use the querySelector() and querySelectorAll() methods in your custom element, which return an Element instance of an array of them, the easiest workaround would probably be to use a type cast:

const items = this.renderRoot.querySelectorAll('color-item') as ColorItem[];
items.forEach(item => {
  // access item properties
});

However, this approach isn’t clean, as it requires you to write as ColorItem[] every time the method is called. A better alternative would be to register a class corresponding to the HTML tag name in the built-in HTMLElementTagNameMap interface:

declare global {
  interface HTMLElementTagNameMap {
    'color-item': ColorItem;
  }
}

Now, every time you call querySelector() or querySelectorAll() with a corresponding tag name, the TypeScript compiler can automatically infer the proper type, making the type cast no longer necessary:

const items = this.renderRoot.querySelectorAll('color-item');
items.forEach(item => {
  // access item properties
});

The TypeScript definitions for Vaadin components provide these registrations. This allows you to avoid writing type casts when using certain DOM methods. Apart from the query methods, this applies to other methods, such as createElement() and closest().