Card editor and picker
Give your custom card a visual editor, a sensible default configuration, and a place in the card picker and its suggestions.
People expect to add and set up a card without writing YAML. Your custom card can take part in that: it can show up in the card picker, start with a working configuration, and have a visual editor.
Add an entry to window.customCards in your module:
window.customCards = window.customCards || [];
window.customCards.push({
type: "state-card-example",
name: "State card",
description: "Shows the state of one entity.",
preview: true,
documentationURL: "https://example.com/state-card",
});| Key | Required | Meaning |
|---|---|---|
type |
Yes | The tag name of your card, without custom:. |
name |
The name in the picker. | |
description |
A short description below the name. | |
preview |
Show a live preview of the card in the picker. Defaults to false. |
|
documentationURL |
Adds a help link to the card editor. | |
getEntitySuggestion |
Suggest your card for an entity. See below. |
Your cards appear in the picker in a section for community cards.
When someone picks your card, Home Assistant asks the static getStubConfig() for a configuration to start with. Return it without the type:
static getStubConfig(hass, entities, entitiesFallback) {
const light = entities.find((id) => id.startsWith("light."));
return { entity: light ?? "sun.sun" };
}entities holds the entities that are not on the dashboard yet, and entitiesFallback the ones that are. Both leave out entities that are unavailable or unknown, so either list can be empty: keep a fallback, like sun.sun in the example. Without getStubConfig(), the card starts with only its type.
When someone picks an entity in the card picker, Home Assistant suggests cards for it. Your card can join those suggestions, in a section for community cards below the built-in ones, with getEntitySuggestion on its window.customCards entry:
getEntitySuggestion: (hass, entityId) => {
if (!entityId.startsWith("light.")) {
return null;
}
return {
config: { type: "custom:state-card-example", entity: entityId },
};
},Return null when your card does not fit the entity, one suggestion, or a list of them for several variants. A suggestion has:
config(required): the configuration of the card, including the type withcustom:.label: a short name for the variant, shown after the name of the card. Only set it when you return more than one.
Only suggest your card where it makes sense. Check the domain, the device class, or the supported features of the entity. A card suggested for every entity makes the picker noisy for everyone.
For most cards, a form is enough. Return a schema from the static getConfigForm(), and Home Assistant renders the form with the same controls as the built-in cards:
static getConfigForm() {
return {
schema: [
{ name: "entity", required: true, selector: { entity: {} } },
{
type: "grid",
name: "",
schema: [
{
name: "name",
selector: { entity_name: {} },
context: { entity: "entity" },
},
{
name: "icon",
selector: { icon: {} },
context: { icon_entity: "entity" },
},
],
},
{ name: "theme", selector: { theme: {} } },
],
computeLabel: (schema) =>
schema.name === "icon" ? "Special icon" : undefined,
computeHelper: (schema) =>
schema.name === "entity" ? "The entity to show" : undefined,
assertConfig: (config) => {
if (config.other_option) {
throw new Error("'other_option' is not supported.");
}
},
};
}| Key | Required | Meaning |
|---|---|---|
schema |
Yes | The fields of the form. |
computeLabel(schema, localize) |
Returns the label of a field. Return undefined for the default: Home Assistant knows labels for common names like entity. |
|
computeHelper(schema, localize) |
Returns a longer help text, shown below a field. | |
assertConfig(config) |
Called on every change. Throw an error to turn off the visual editor, for example when someone writes YAML the form can't show. The visual editor is available again once the error is gone. |
A field has a name, the key in the configuration, and usually a selector. Selectors are the same ones that blueprints and the automation editor use, like entity, number, boolean, select, icon, and theme. The selectors documentation lists them all. Other options of a field:
required: the field must have a value.default: the value when the field is left empty.context: links the field to another field. In the example, the icon selector shows the icon of the entity picked in theentityfield.visible: show the field only under a condition, like{ field: "mode", value: "advanced" }.
Fields can be grouped:
type: "grid"puts itsschemaside by side. Setcolumn_min_width, like"200px", to control when it wraps.type: "expandable"puts itsschemain a section that opens and closes, with atitle.
Both have a name, and a flatten option. With flatten: true, or an empty name, the values of the group go into the main configuration. Otherwise they go into a sub-object under that name. The full set of options is in src/components/ha-form/types.ts.
The entity_name selector in the example lets people pick how the entity is named: by its own name, its device, area, or floor, or with their own text. Its value is exactly what hass.formatEntityName() accepts, so pass it straight through:
const name = this._hass.formatEntityName(stateObj, this._config.name);The selector has two options of its own:
entity_id: a fixed entity to preview the names with, instead of the one fromcontext.entity.default_name: the value shown while the field has no configured value (undefined). It takes the same shapes as the value: a string, one name item, or a list of them.
See the hass object reference for what it can do.
When a form is not enough, write your own editor element, and return it from the static getConfigElement():
static getConfigElement() {
return document.createElement("state-card-example-editor");
}Home Assistant calls setConfig() on the editor with the configuration, and sets hass on it, just like on a card. If your editor has a lovelace property, Home Assistant sets that too, with information about the dashboard. A plain element without that property doesn't get it. When the user changes something, fire a config-changed event with the complete new configuration:
class StateCardExampleEditor extends HTMLElement {
setConfig(config) {
this._config = config;
}
set hass(hass) {
this._hass = hass;
}
_changed(newConfig) {
this.dispatchEvent(
new CustomEvent("config-changed", {
detail: { config: newConfig },
bubbles: true,
composed: true,
})
);
}
}
customElements.define(
"state-card-example-editor",
StateCardExampleEditor
);When a card has both, getConfigElement() is used.