Home AssistantHome AssistantContributors
On this page

Every piece of the interface is a custom element: a class registered under a tag name, with its own template and styles in a shadow root. The frontend writes them with Lit, a small library that adds reactive properties and efficient templates to standard web components.

A component

A typical component looks like this:

TypeScript
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";

@customElement("ha-example-row")
export class HaExampleRow extends LitElement {
  @property() public label?: string;

  @state() private _expanded = false;

  protected render() {
    if (!this.label) {
      return nothing;
    }

    return html`
      <button @click=${this._toggle}>${this.label}</button>
      ${this._expanded ? html`<slot></slot>` : nothing}
    `;
  }

  private _toggle() {
    this._expanded = !this._expanded;
  }

  static styles = css`
    :host {
      display: block;
      padding: var(--ha-space-2);
    }
  `;
}

declare global {
  interface HTMLElementTagNameMap {
    "ha-example-row": HaExampleRow;
  }
}
  • @property() is public API, set by whoever uses the element. @state() is internal. Changing either one schedules a render.
  • render() returns a template. Lit only updates the parts of the DOM that changed.
  • Styles are scoped to the component by the shadow root.
  • The HTMLElementTagNameMap declaration gives other code the type of the element.

The Lit documentation covers the rest: lifecycle, directives, and reactive controllers.

Naming

Tag names tell you where a component belongs:

Shared components and Web Awesome

The shared components in src/components/ are the building blocks of every screen: buttons, inputs, dialogs, pickers, and controls. Many of them, like ha-button, ha-dialog, ha-dropdown, ha-switch, ha-slider, and ha-tooltip, wrap a component from Web Awesome. The frontend uses its own build of Web Awesome, @home-assistant/webawesome.

A wrapper extends the Web Awesome class, and adds the theme and the behavior Home Assistant needs:

TypeScript
import Button from "@home-assistant/webawesome/dist/components/button/button";

@customElement("ha-button")
export class HaButton extends Button {
  // Home Assistant variants, styles, and theme variables
}

Always use the ha-* element, never the Web Awesome element directly. The wrapper is where the theme, the translations, and fixes for Home Assistant live, and it is what keeps every screen consistent.

Older parts of the frontend still use Material Web Components (mwc-*). They are being replaced. Don't add new ones.

Each shared component has a page in the Design area, with usage guidance, live examples, and its attributes. Look there before you build something new.

Mixins and controllers

Some behavior is shared through mixins and reactive controllers instead of base classes:

Was this page helpful?

Last updated on .