Home Assistant card components
Embed working Home Assistant dashboard cards in any page. The cards run against a mocked backend in the browser. No Home Assistant server is necessary.
<ha-embed-provider> holds the mocked backend and its entities. These components render dashboard content with the backend of a provider:
<ha-embed-card>renders one card.<ha-embed-section>renders one section, or a section strategy.<ha-embed-view>renders one view, a view strategy, or one view of a dashboard.<ha-embed-dashboard>renders a dashboard, or a dashboard strategy, with its tabs.
All cards in the same provider share their entities. When you change an entity in one card, the other cards show the change.
Color scheme of this page and its cards:
Use on your site
Load the script from the demo site. The script loads all other files from the demo site too.
<script type="module" src="https://demo.home-assistant.io/embed.js"></script>One card
Put the entities in a script tag inside the provider. Put the card configuration in a script tag inside the card. Both take JSON with type="application/json" or YAML with type="text/yaml". The card configuration is the same as in a dashboard. YAML needs its indentation. If your site minifies HTML, make sure the minifier keeps the whitespace in script tags, or use JSON.
<ha-embed-provider>
<script type="text/yaml">
- entity_id: light.kitchen
state: "on"
attributes:
friendly_name: Kitchen
supported_color_modes: [brightness]
color_mode: brightness
brightness: 180
</script>
<ha-embed-card>
<script type="text/yaml">
type: tile
entity: light.kitchen
tap_action:
action: toggle
features:
- type: light-brightness
</script>
</ha-embed-card>
</ha-embed-provider>The entities attribute of the provider and the config attribute of the card also take JSON.
Cards with shared state
These three cards are in one provider. Turn on a light in one card. The other cards update. The log below shows the events of the provider.
Use the cards to see events here.Cards outside a provider
A card does not have to be inside a provider. A card outside a provider uses the first provider on the page. Put one provider with all entities at the top of the page, and put the cards anywhere below it. If the page has no provider, the card adds an empty provider to the page.
To use a different provider, set the provider attribute of the card to the ID of the provider.
<ha-embed-provider id="living-room">…</ha-embed-provider>
<p>Some text about the coffee maker.</p>
<ha-embed-card provider="living-room">
<script type="text/yaml">
type: tile
entity: switch.coffee_maker
</script>
</ha-embed-card>These two cards are not inside a provider. The first card uses the first provider on this page, the kitchen light at the top. The second card uses the provider with the ID shared above.
The other components find their provider the same way, and take the same provider attribute.
Sections
<ha-embed-section> renders a section the same way as in a sections view. The configuration is the same as in a dashboard, so headings span the top, and a strategy generates the section. The section takes the width of the component.
<ha-embed-section>
<script type="text/yaml">
cards:
- type: heading
heading: Kitchen
- type: tile
entity: light.kitchen
- type: tile
entity: switch.kettle
</script>
</ha-embed-section>Views
<ha-embed-view> renders a view, with its badges and layout. A strategy generates the view. Give the component the width the view needs: masonry and sidebar views pick their columns from the width of the page, not the component.
<ha-embed-view>
<script type="text/yaml">
type: sections
badges:
- entity: sensor.outside_temperature
sections:
- cards: …
</script>
</ha-embed-view>To show one view of a dashboard, or of a dashboard strategy, put the dashboard in dashboard and the path or index of the view in view. Without view, the first view shows.
<ha-embed-view>
<script type="text/yaml">
dashboard:
strategy:
type: custom:my-dashboard
view: overview
</script>
</ha-embed-view>Dashboards
<ha-embed-dashboard> renders a dashboard with its tabs. A strategy generates the dashboard. Each dashboard keeps its own tab, so switching tabs does not change the address of your page. The dashboard scrolls inside the component, under its toolbar. The component is 600 pixels high; set its height to change that. Set view to the path or index of the first view to show.
<ha-embed-dashboard style="height: 480px">
<script type="text/yaml">
views:
- title: Home
path: home
cards: …
- title: Climate
path: climate
cards: …
</script>
</ha-embed-dashboard>Light and dark mode
The cards follow the light or dark mode of the browser. To choose the mode, set the color-scheme attribute of the provider to light or dark. Use this when your site has its own switch for dark mode. The buttons at the top of this page do this. The cards keep their styles inside. They do not change the styles of your page.
<ha-embed-provider color-scheme="dark">…</ha-embed-provider>Change entities from the page
Set the entities property to change entities from JavaScript. The new list replaces all entities. To change one entity, start from the current states.
const provider = document.querySelector("ha-embed-provider");
provider.entities = Object.values(provider.hass.states).map((entity) =>
entity.entity_id === "sensor.outside_temperature"
? { ...entity, state: "18.1" }
: entity
);To use the actions of an entity, call a service. The provider updates the state and the cards.
provider.hass.callService("switch", "toggle", {
entity_id: "switch.coffee_maker",
});Listen to changes
The provider fires a states-changed event when the state of one or more entities changes. The event has the states of all entities. The event bubbles.
provider.addEventListener("states-changed", (ev) => {
const { states } = ev.detail;
console.log(states["light.living_room"].state);
});Read the current state of all entities with provider.hass.states.
Areas, devices, calendars and energy
Some cards need data that is not an entity. To set it, put an object in the script tag of the provider instead of the list. The object has the list in entities, and any of these keys:
areas: a list of areas, each with anarea_idand aname. Without it, eacharea_idof the entities and devices becomes an area.devices: a list of devices, each with anidand aname, and optionally amanufacturer,modelandarea_id. Entities belong to a device with theirdevice_id, and cards and strategies then find the device and its name.calendars: the events of calendar entities, by entity ID. Each event has asummary, astartand anend. Calendars without events here get sample events around today.energy: the energy settings, the same as theenergy/get_prefsresponse. Without it, energy is not set up.
Weather entities get a forecast made up from their condition and temperature, or their forecast attribute when they have one. Set supported_features to show forecasts.
<ha-embed-provider>
<script type="text/yaml">
entities:
- entity_id: calendar.family
state: "off"
attributes:
friendly_name: Family
areas:
- area_id: kitchen
name: Kitchen
calendars:
calendar.family:
- summary: Dentist
start: "2026-11-02T15:30:00+00:00"
end: "2026-11-02T16:15:00+00:00"
</script>
…
</ha-embed-provider>This calendar card uses the sample events.
Custom cards
Custom cards work too. Load the script of the custom card on the page, and use custom: and the element name as the card type. The custom card gets the same mocked backend as the other cards. window.loadCardHelpers() is available to create built-in cards.
The mocked backend only knows a part of the Home Assistant API. History and statistics are made up from the current state of each entity. Templates are not rendered, they come back as they are. The logbook is empty. Other requests get an error. From another site, the script of the custom card needs CORS headers, like the embed script.
<script type="module" src="https://example.com/embed-demo-custom-card.js"></script>
<ha-embed-card>
<script type="text/yaml">
type: custom:embed-demo-custom-card
entity: light.office
</script>
</ha-embed-card>This is the code of the custom card:
class EmbedDemoCustomCard extends HTMLElement {
setConfig(config) {
if (!config.entity) {
throw new Error("Set an entity");
}
this._config = config;
}
set hass(hass) {
this._hass = hass;
this._render();
}
getCardSize() {
return 3;
}
async _render() {
if (!this.shadowRoot) {
this.attachShadow({ mode: "open" }).innerHTML = `
<style>
ha-card { padding: 16px; }
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 8px;
background: var(--accent-color);
color: var(--text-accent-color, #fff);
font-size: 12px;
}
.tile { margin: 12px 0; }
</style>
<ha-card>
<span class="badge">Custom card</span>
<p>
This is a custom card. It is not part of Home Assistant. The page
defines it, and it creates the tile card below with the card
helpers.
</p>
<div class="tile"></div>
<button>Toggle from the custom card</button>
</ha-card>`;
this.shadowRoot.querySelector("button").addEventListener("click", () =>
this._hass.callService("homeassistant", "toggle", {
entity_id: this._config.entity,
})
);
}
// Keep the promise, so that a second update does not create a second tile.
if (!this._tile) {
this._tile = window.loadCardHelpers().then((helpers) => {
const tile = helpers.createCardElement({
type: "tile",
entity: this._config.entity,
});
this.shadowRoot.querySelector(".tile").append(tile);
return tile;
});
}
(await this._tile).hass = this._hass;
}
}
customElements.define("embed-demo-custom-card", EmbedDemoCustomCard);Reference
<ha-embed-provider>
| Name | Type | Description |
|---|---|---|
entities | Attribute and property | A list of entities. Each entity has an entity_id, a state, and attributes. The optional keys area_id, device_id, and platform set the entity registry entry. A new list replaces all entities. A JSON or YAML script child sets the first list. |
color-scheme | Attribute and property (colorScheme) | light or dark. Without it, the cards follow the browser setting. |
hass | Property, read-only | The mocked Home Assistant object that the cards use. |
states-changed | Event | Fires when the state of one or more entities changes. The detail has states, the states of all entities by entity ID. |
<ha-embed-card>
| Name | Type | Description |
|---|---|---|
config | Attribute and property | The card configuration, the same as in a dashboard. A JSON or YAML script child sets the first configuration. |
provider | Attribute and property | The ID of the provider to use. Without it, the card uses the provider around it, else the first provider on the page. The other components take it too. |
<ha-embed-section>
| Name | Type | Description |
|---|---|---|
config | Attribute and property | The section configuration or section strategy, the same as in a dashboard. A JSON or YAML script child sets the first configuration. |
<ha-embed-view>
| Name | Type | Description |
|---|---|---|
config | Attribute and property | The view configuration or view strategy, the same as in a dashboard. A JSON or YAML script child sets the first configuration. |
dashboard | Attribute and property | A dashboard configuration or dashboard strategy, to show one of its views instead of config. A script child with a dashboard key, and optionally view, sets it. |
view | Attribute and property | The path or index of the view of dashboard to show. Without it, the first view. |
<ha-embed-dashboard>
| Name | Type | Description |
|---|---|---|
config | Attribute and property | The dashboard configuration or dashboard strategy. A JSON or YAML script child sets the first configuration. |
view | Attribute and property | The path or index of the first view to show. Without it, the first view. |
The mocked backend knows the actions of lights, switches, climate, covers, fans, locks, media players, and more. Actions that open dialogs, such as more-info, do nothing. Dashboards can't be edited, and their toolbar only shows their tabs.