Skip to main content

Building Custom Dashboard Cards

Custom dashboard cards are standard custom elements. You define a class, register it with customElements.define(), and reference it from a dashboard with a custom: prefixed type:

type: custom:my-card

The frontend strips the custom: prefix and instantiates the element with document.createElement("my-card"). If the element is not defined within 2 seconds, an error card is shown in its place until the element upgrades.

The card contract

A card element must implement the following interface (from the frontend source, src/panels/lovelace/types.ts):

interface LovelaceCard extends HTMLElement {
apex?: ApexOS;
preview?: boolean;
layout?: string;
connectedWhileHidden?: boolean;
getCardSize(): number | Promise<number>;
getGridOptions?(): LovelaceGridOptions;
setConfig(config: LovelaceCardConfig): void;
}

setConfig(config) — required

Called with the card's configuration (the YAML/storage object for this card, including type). Called before the element is attached to the DOM, and again whenever the configuration changes. Throw an error from setConfig to reject invalid configuration; the frontend renders an error card with your message.

apex — the state object property

The frontend assigns the full application state object to the card's apex property whenever state changes. It contains the entity states, connection, user, locale, theme information, and the service-call / WebSocket helpers. Re-render from the apex setter (or, with Lit, declare it as a reactive property):

class MyCard extends HTMLElement {
set apex(apex) {
const stateObj = apex.states[this._config.entity];
// ...render
}

setConfig(config) {
if (!config.entity) {
throw new Error("You need to define an entity");
}
this._config = config;
}

getCardSize() {
return 3;
}
}
customElements.define("my-card", MyCard);

getCardSize() — layout hint

Returns the approximate height of the card in units of 50 px rows (a number or a Promise of one). Used by masonry-style views to balance columns.

getGridOptions() — sections layout hint (optional)

For sections-based views, return grid sizing constraints:

getGridOptions() {
return { columns: 6, rows: 2, min_columns: 3, max_rows: 4 };
}

(getLayoutOptions() is the deprecated predecessor and still recognized.)

Static members — editor integration

These are read from the card class (the constructor), not the instance:

  • getConfigElement() — return a custom element that edits the card's config. The editor element receives the same apex property, gets the current config through its own setConfig(config), and reports changes by firing a config-changed event with { config } in detail.
  • getConfigForm() — a lighter alternative: return { schema, assertConfig?, computeLabel?, computeHelper? } and the frontend renders a form-based editor for you.
  • getStubConfig(apex, entities, entitiesFallback) — return the initial config used when a user picks your card in the card picker.

Registering in the card picker

To make your card show up (with name and description) in the dashboard card picker, push an entry onto the global window.customCards array:

window.customCards = window.customCards || [];
window.customCards.push({
type: "my-card",
name: "My Card",
description: "A card that does the thing",
preview: true, // show a live preview in the picker
documentationURL: "https://example.com/my-card",
});

The entry type is:

interface CustomCardEntry {
type: string;
name?: string;
description?: string;
preview?: boolean;
documentationURL?: string;
getEntitySuggestion?: (apex, entityId) => CustomCardSuggestion | CustomCardSuggestion[] | null;
}

Parallel registries exist for other custom dashboard building blocks: window.customBadges (badge elements) and window.customCardFeatures (card features; entries may implement isSupported(apex, context) to control when they are offered).

Loading your card: dashboard resources

Card modules are delivered to the browser as dashboard resources. Each resource has a url and a type of module (recommended), js, or css. Resources are managed in the frontend's settings UI or via the WebSocket API (lovelace/resources, lovelace/resources/create|update|delete). The frontend resolves relative resource URLs against the instance base URL, loads module resources as ES modules, and caches js/css resources so each URL is only loaded once per session.

Events a card can fire

Cards communicate upward with composed, bubbling DOM events:

  • apex-more-info with detail: { entityId } — open the entity's more-info dialog.
  • ll-custom — fired by the fire-dom-event action for containing code to handle.
  • ll-rebuild — ask the view to re-create the card (e.g. after editing).

Backwards compatibility

The card API surface is apex (state property), window.apexConnection (connection promise), and apex-* app-level events. A built-in compatibility bridge — installed before any dashboard resource executes — keeps older community cards written against earlier community conventions working unmodified, so existing cards do not need a rewrite to run on ApexOS. New cards should target the apex names directly.