Components and Lit
Web components, Lit, and the shared ha-* components that wrap Web Awesome. The building blocks of every screen.
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 typical component looks like this:
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
HTMLElementTagNameMapdeclaration gives other code the type of the element.
The Lit documentation covers the rest: lifecycle, directives, and reactive controllers.
Tag names tell you where a component belongs:
ha-*: shared Home Assistant components insrc/components/, likeha-buttonorha-entity-picker.hui-*: dashboard components insrc/panels/lovelace/, likehui-tile-card. The prefix comes from the old name of dashboards, Lovelace, which still shows up in code.dialog-*: dialogs.more-info-*andstate-card-*: the views of the more-info dialog and the state cards, one per domain.
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:
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.
Some behavior is shared through mixins and reactive controllers instead of base classes:
SubscribeMixinfromsrc/mixins/subscribe-mixin.tsmanages WebSocket subscriptions for a component. See the data layer.DialogMixinfromsrc/dialogs/dialog-mixin.tshandles opening and closing for new dialogs.src/common/decorators/has decorators to consume contexts, like@consumeand@consumeEntityState, and to transform them, like@transform. See the hass object and contexts.