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:

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:

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>

NameTypeDescription
entitiesAttribute and propertyA 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-schemeAttribute and property (colorScheme)light or dark. Without it, the cards follow the browser setting.
hassProperty, read-onlyThe mocked Home Assistant object that the cards use.
states-changedEventFires when the state of one or more entities changes. The detail has states, the states of all entities by entity ID.

<ha-embed-card>

NameTypeDescription
configAttribute and propertyThe card configuration, the same as in a dashboard. A JSON or YAML script child sets the first configuration.
providerAttribute and propertyThe 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>

NameTypeDescription
configAttribute and propertyThe section configuration or section strategy, the same as in a dashboard. A JSON or YAML script child sets the first configuration.

<ha-embed-view>

NameTypeDescription
configAttribute and propertyThe view configuration or view strategy, the same as in a dashboard. A JSON or YAML script child sets the first configuration.
dashboardAttribute and propertyA 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.
viewAttribute and propertyThe path or index of the view of dashboard to show. Without it, the first view.

<ha-embed-dashboard>

NameTypeDescription
configAttribute and propertyThe dashboard configuration or dashboard strategy. A JSON or YAML script child sets the first configuration.
viewAttribute and propertyThe 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.