# Show a live demo of your card

> Embed working dashboard cards, including your own custom card, on any web page, with the mocked backend of the Home Assistant demo.

Source: https://contributors.home-assistant.io/frontend/custom-ui/demo/

Last verified on 2026-11-05, against https://github.com/home-assistant/frontend/tree/dev/demo/src/embed.

Screenshots show what a card looks like. A live demo shows how it works: people can turn on a light, move a slider, and see the other cards follow. The [Home Assistant demo](https://demo.home-assistant.io/) publishes elements that put real dashboard cards on any web page, like your README site or your blog. They run against a mocked backend in the browser, so no Home Assistant server is needed.

- `<ha-embed-provider>` holds the mocked backend and its entities.
- `<ha-embed-card>` renders one card, with the backend of a provider.
- `<ha-embed-section>` renders one section, or a [section strategy](https://contributors.home-assistant.io/frontend/custom-ui/strategy/#a-section-strategy), the same way.
- `<ha-embed-view>` renders one view, a [view strategy](https://contributors.home-assistant.io/frontend/custom-ui/strategy/#a-view-strategy), or one view of a dashboard.
- `<ha-embed-dashboard>` renders a dashboard, or a [dashboard strategy](https://contributors.home-assistant.io/frontend/custom-ui/strategy/#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.

Try it here: the hallway tile and the thermostat card below it share one hallway thermostat. Change the target temperature in the tile or the mode in the thermostat card, and the other card follows. Each card has one card feature. The trend graph of the outside temperature is made up by the demo from its current state.

Live demo of dashboard cards, from the embed of the Home Assistant demo.

```html
<ha-embed-provider
  style="
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 8px;
    align-items: start;
    max-width: 500px;
  "
>
  <script type="application/json">
    [
      {
        "entity_id": "climate.hallway",
        "state": "heat",
        "attributes": {
          "friendly_name": "Hallway",
          "current_temperature": 19.5,
          "temperature": 21,
          "min_temp": 7,
          "max_temp": 30,
          "target_temp_step": 0.5,
          "hvac_modes": [
            "heat",
            "cool",
            "off"
          ],
          "supported_features": 1
        }
      },
      {
        "entity_id": "sensor.outside_temperature",
        "state": "12.3",
        "attributes": {
          "friendly_name": "Outside",
          "device_class": "temperature",
          "state_class": "measurement",
          "unit_of_measurement": "°C"
        }
      }
    ]
  </script>
  <ha-embed-card style="grid-column: span 6">
    <script type="application/json">
      {
        "type": "tile",
        "entity": "sensor.outside_temperature",
        "features": [
          {
            "type": "trend-graph"
          }
        ],
        "grid_options": {
          "columns": 6
        }
      }
    </script>
  </ha-embed-card>
  <ha-embed-card style="grid-column: span 6">
    <script type="application/json">
      {
        "type": "tile",
        "entity": "climate.hallway",
        "features": [
          {
            "type": "target-temperature"
          }
        ],
        "grid_options": {
          "columns": 6
        }
      }
    </script>
  </ha-embed-card>
  <ha-embed-card style="grid-column: span 12">
    <script type="application/json">
      {
        "type": "thermostat",
        "entity": "climate.hallway",
        "features": [
          {
            "type": "climate-hvac-modes",
            "hvac_modes": [
              "heat",
              "cool",
              "off"
            ]
          }
        ]
      }
    </script>
  </ha-embed-card>
</ha-embed-provider>
```

The [components page](https://demo.home-assistant.io/components.html) of the demo shows all of this working, with every option.

## Load the script

Add the script of the demo to your page. It loads all its other files from the demo site too:

```html
<script
  type="module"
  src="https://demo.home-assistant.io/embed.js"
></script>
```

## Add a card

Put the entities in a script tag directly inside the provider, and the card configuration in a script tag directly 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.

```html
<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
      features:
        - type: light-brightness
    </script>
  </ha-embed-card>
</ha-embed-provider>
```

Each entity has an `entity_id`, a `state`, and `attributes`. The optional keys `area_id`, `device_id`, and `platform` set its entry in the entity registry. Without `last_changed`, the entity changed at a random recent time.

Entities keep all their attributes, like in Home Assistant, so attributes that only your integration sets are there too. Entities with a specialized mock, including lights, climate, covers, people, and device trackers, work out some attributes themselves, and those win over what you set: a light that is off has no brightness, for example.

YAML needs its indentation. If your site minifies HTML, make sure the minifier keeps the whitespace in script tags, or use JSON. The `entities` attribute of the provider and the `config` attribute of the card take JSON too.

## Show your custom card

Load the script of your card on the page, after the script of the demo, and use its `custom:` type, the same as in a dashboard:

```html
<script
  type="module"
  src="https://demo.home-assistant.io/embed.js"
></script>
<script
  type="module"
  src="https://example.com/my-card.js"
></script>

<ha-embed-card>
  <script type="text/yaml">
    type: custom:my-card
    entity: light.kitchen
  </script>
</ha-embed-card>
```

Your card gets the same mocked `hass` object as the built-in cards, and `window.loadCardHelpers()` works for the [card helpers](https://contributors.home-assistant.io/frontend/custom-ui/card/#card-helpers). The card helpers only exist once the script of the demo has run. Module scripts run in the order they are on the page, so keep the script of your card after it, in case your card uses them as soon as its script loads. Many built-in cards, like the map card, only load when a card first needs them, so create built-in cards through the card helpers instead of by their element name. When the script of your card is on another site than your page, that site needs to send CORS headers for it.

A custom strategy loads the same way. Load its script after the script of the demo, and use its `custom:` type in the `strategy` of a section, view, or dashboard.

## Show a section

`<ha-embed-section>` renders a section the same way as in a sections view. Its configuration is the same as in a dashboard: a `title` becomes a heading card when there is no `strategy`, headings span the top, and a `strategy` generates the section. The section takes the width of the element. This example uses the provider from [Add a card](https://contributors.home-assistant.io/frontend/custom-ui/demo/#add-a-card), with a `switch.kettle` added to it:

```html
<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>
```

Try it here: a section with a heading, two tiles you can turn on and off, and a wide tile below them. The ceiling light belongs to a device in `devices`.

Live demo of a dashboard section, from the embed of the Home Assistant demo.

```html
<ha-embed-provider>
  <script type="application/json">
    {
      "entities": [
        {
          "entity_id": "light.kitchen_ceiling",
          "state": "on",
          "device_id": "kitchen_ceiling",
          "attributes": {
            "friendly_name": "Ceiling",
            "supported_color_modes": [
              "brightness"
            ],
            "color_mode": "brightness",
            "brightness": 200
          }
        },
        {
          "entity_id": "switch.kettle",
          "state": "off",
          "area_id": "kitchen",
          "attributes": {
            "friendly_name": "Kettle",
            "device_class": "outlet"
          }
        },
        {
          "entity_id": "sensor.kitchen_temperature",
          "state": "21.4",
          "area_id": "kitchen",
          "attributes": {
            "friendly_name": "Kitchen temperature",
            "device_class": "temperature",
            "state_class": "measurement",
            "unit_of_measurement": "°C"
          }
        }
      ],
      "areas": [
        {
          "area_id": "kitchen",
          "name": "Kitchen"
        }
      ],
      "devices": [
        {
          "id": "kitchen_ceiling",
          "name": "Kitchen ceiling light",
          "area_id": "kitchen"
        }
      ]
    }
  </script>
  <ha-embed-section>
    <script type="application/json">
      {
        "cards": [
          {
            "type": "heading",
            "heading": "Kitchen"
          },
          {
            "type": "tile",
            "entity": "light.kitchen_ceiling",
            "tap_action": {
              "action": "toggle"
            }
          },
          {
            "type": "tile",
            "entity": "switch.kettle",
            "tap_action": {
              "action": "toggle"
            }
          },
          {
            "type": "tile",
            "entity": "sensor.kitchen_temperature",
            "grid_options": {
              "columns": 12
            }
          }
        ]
      }
    </script>
  </ha-embed-section>
</ha-embed-provider>
```

A section strategy that groups entities by device or area needs those in the provider. See [Areas, devices, calendars, and energy](https://contributors.home-assistant.io/frontend/custom-ui/demo/#areas-devices-calendars-and-energy).

## Show a view

`<ha-embed-view>` renders a view, with its badges and layout, and a `strategy` generates the view. Give the element the width the view needs: masonry and sidebar views pick their number of columns from the width of the page, not of the element. This example uses the provider from [Add a card](https://contributors.home-assistant.io/frontend/custom-ui/demo/#add-a-card):

```html
<ha-embed-view>
  <script type="text/yaml">
    type: sections
    badges:
      - entity: light.kitchen
    sections:
      - cards:
          - type: tile
            entity: light.kitchen
  </script>
</ha-embed-view>
```

Try it here: a sections view with a badge for the outside temperature, and two sections side by side when there is room.

Live demo of a dashboard view, from the embed of the Home Assistant demo.

```html
<ha-embed-provider>
  <script type="application/json">
    [
      {
        "entity_id": "light.living_room",
        "state": "off",
        "attributes": {
          "friendly_name": "Living room",
          "supported_color_modes": [
            "brightness"
          ],
          "color_mode": "brightness",
          "brightness": 255
        }
      },
      {
        "entity_id": "switch.coffee_maker",
        "state": "on",
        "attributes": {
          "friendly_name": "Coffee maker",
          "device_class": "outlet"
        }
      },
      {
        "entity_id": "climate.hallway",
        "state": "heat",
        "attributes": {
          "friendly_name": "Hallway",
          "current_temperature": 19.5,
          "temperature": 21,
          "min_temp": 7,
          "max_temp": 30,
          "target_temp_step": 0.5,
          "hvac_modes": [
            "heat",
            "off"
          ],
          "supported_features": 1
        }
      },
      {
        "entity_id": "sensor.outside_temperature",
        "state": "12.3",
        "attributes": {
          "friendly_name": "Outside",
          "device_class": "temperature",
          "state_class": "measurement",
          "unit_of_measurement": "°C"
        }
      }
    ]
  </script>
  <ha-embed-view>
    <script type="application/json">
      {
        "type": "sections",
        "max_columns": 2,
        "badges": [
          {
            "entity": "sensor.outside_temperature"
          }
        ],
        "sections": [
          {
            "cards": [
              {
                "type": "heading",
                "heading": "Living room"
              },
              {
                "type": "tile",
                "entity": "light.living_room",
                "tap_action": {
                  "action": "toggle"
                }
              },
              {
                "type": "tile",
                "entity": "switch.coffee_maker",
                "tap_action": {
                  "action": "toggle"
                }
              }
            ]
          },
          {
            "cards": [
              {
                "type": "heading",
                "heading": "Hallway"
              },
              {
                "type": "thermostat",
                "entity": "climate.hallway"
              }
            ]
          }
        ]
      }
    </script>
  </ha-embed-view>
</ha-embed-provider>
```

A view strategy and a dashboard strategy both use the `strategy` key. 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.

```yaml
dashboard:
  strategy:
    type: custom:my-dashboard
view: overview
```

## Show a dashboard

`<ha-embed-dashboard>` renders a whole dashboard with its tabs, and a `strategy` generates the dashboard. The dashboard scrolls inside the element, under its toolbar. The element is 600 pixels high; set its `height` to change that. To start on another view than the first, set its `view` attribute to the path or index of that view. This example uses the provider from [Add a card](https://contributors.home-assistant.io/frontend/custom-ui/demo/#add-a-card):

```html
<ha-embed-dashboard style="height: 480px">
  <script type="text/yaml">
    views:
      - title: Home
        path: home
        cards:
          - type: tile
            entity: light.kitchen
      - title: Kitchen
        path: kitchen
        cards:
          - type: light
            entity: light.kitchen
  </script>
</ha-embed-dashboard>
```

Try it here: a dashboard with two tabs. Switch between them, or select the hallway tile on the first tab to go to the second.

Live demo of a dashboard with its tabs, from the embed of the Home Assistant demo.

```html
<ha-embed-provider>
  <script type="application/json">
    [
      {
        "entity_id": "light.living_room",
        "state": "off",
        "attributes": {
          "friendly_name": "Living room",
          "supported_color_modes": [
            "brightness"
          ],
          "color_mode": "brightness",
          "brightness": 255
        }
      },
      {
        "entity_id": "switch.coffee_maker",
        "state": "on",
        "attributes": {
          "friendly_name": "Coffee maker",
          "device_class": "outlet"
        }
      },
      {
        "entity_id": "climate.hallway",
        "state": "heat",
        "attributes": {
          "friendly_name": "Hallway",
          "current_temperature": 19.5,
          "temperature": 21,
          "min_temp": 7,
          "max_temp": 30,
          "target_temp_step": 0.5,
          "hvac_modes": [
            "heat",
            "off"
          ],
          "supported_features": 1
        }
      }
    ]
  </script>
  <ha-embed-dashboard>
    <script type="application/json">
      {
        "views": [
          {
            "title": "Home",
            "path": "home",
            "type": "sections",
            "max_columns": 2,
            "sections": [
              {
                "cards": [
                  {
                    "type": "heading",
                    "heading": "Living room"
                  },
                  {
                    "type": "tile",
                    "entity": "light.living_room",
                    "tap_action": {
                      "action": "toggle"
                    }
                  },
                  {
                    "type": "tile",
                    "entity": "switch.coffee_maker",
                    "tap_action": {
                      "action": "toggle"
                    }
                  }
                ]
              },
              {
                "cards": [
                  {
                    "type": "heading",
                    "heading": "Hallway"
                  },
                  {
                    "type": "tile",
                    "entity": "climate.hallway",
                    "tap_action": {
                      "action": "navigate",
                      "navigation_path": "climate"
                    }
                  }
                ]
              }
            ]
          },
          {
            "title": "Climate",
            "path": "climate",
            "type": "sections",
            "sections": [
              {
                "cards": [
                  {
                    "type": "thermostat",
                    "entity": "climate.hallway"
                  }
                ]
              }
            ]
          }
        ]
      }
    </script>
  </ha-embed-dashboard>
</ha-embed-provider>
```

The toolbar only shows the tabs, or the title of the view, and the back button of a subview. There is no edit mode. Each dashboard keeps its own view, so the address of your page stays the same, and two dashboards on one page don't follow each other.

Links in the dashboard work like this:

- A link to one of the views of the dashboard, like `navigation_path: climate` or `/my-dashboard/climate`, shows that view.
- The back button of a subview goes to its `back_path` when that is a view of this dashboard. Otherwise it goes to the view before it, or the first view when there is none, so a `back_path` to another page does not leave the demo.
- A link to an anchor on your page, like `#setup`, scrolls the page to it.
- Other `navigate` actions do nothing. A `url` action opens its page, the same as in a dashboard.

## Load the demo when it comes into view

On a page with several demos, or with a demo far down the page, you can wait to load the script of the demo until a demo comes near, so the rest of the page loads first. The elements read their script tags when they are set up, so the cards still find their entities and configuration when the script comes later. Watch every card, section, view, and dashboard on the page, then import the script of the demo first, and the script of your card after it:

```js
const loadDemo = async () => {
  await import("https://demo.home-assistant.io/embed.js");
  await import("https://example.com/my-card.js");
};

const observer = new IntersectionObserver(
  (entries) => {
    if (entries.some((entry) => entry.isIntersecting)) {
      observer.disconnect();
      loadDemo();
    }
  },
  { rootMargin: "400px 0px" },
);
for (const demo of document.querySelectorAll(
  "ha-embed-card, ha-embed-section, ha-embed-view, ha-embed-dashboard",
)) {
  observer.observe(demo);
}
```

Cards, sections, views, and dashboards inside a provider count too. `querySelectorAll` does not look inside shadow roots, so for demos inside your own components, observe those components instead. It does not watch providers: a provider at the top of the page would load the demo right away, even when its cards are far below. Run it from a module script, so the elements of the page are there when it looks for them. Leave out the script tags of the demo and your card when you load them this way.

## Place cards anywhere on the page

A card does not have to be inside a provider. A card outside a provider uses the first provider on the page. A card inside one of your own components uses the provider around that component, even through its shadow root. Put one provider with all entities at the top of the page, and the cards anywhere below it, between your text. If the page has no provider, the card adds an empty one. Sections, views, and dashboards find their provider the same way, and take the same `provider` attribute.

To use another provider, set the `provider` attribute of the card to the ID of that provider:

```html
<ha-embed-provider id="living-room">…</ha-embed-provider>

<ha-embed-card provider="living-room">…</ha-embed-card>
```

Set `provider` before the card is added to the page. The card picks its provider when it is added, and does not follow a later change.

## Show several projects on one site

If you maintain several cards or strategies, you don't need a demo site for each. Give each project a demo config, with the entities in `entities` and what to show in `cards`, `sections`, `views`, or `dashboard`. The configs can all live in the host site, or each project can keep its own, like a `demo.yml` next to its code:

```yaml
entities:
  - entity_id: light.kitchen
    state: "on"
    attributes:
      friendly_name: Kitchen
cards:
  - type: custom:my-card
    entity: light.kitchen
```

The site then renders each config: the entities go into a provider, and each card, section, view, or dashboard into its element, as YAML in their script tags. Give each project its own provider, so their entities stay apart, and load the script of each card after the script of the demo.

## Light and dark

The cards follow the light or dark mode of the browser. When your site has its own switch for dark mode, set the `color-scheme` attribute of the provider to `light` or `dark`. The cards keep their styles inside, and do not change the styles of your page. The one exception is the Roboto font: the script adds its font faces to the page, so text of your page that uses Roboto can look different once the script has loaded.

## 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 an `area_id` and a `name`. Without it, each `area_id` of the entities and devices becomes an area.
- `devices`: a list of devices, each with an `id` and a `name`, and optionally a `manufacturer`, a `model`, and an `area_id`. An entity belongs to a device through its `device_id`, so cards and strategies find the device and its name.
- `calendars`: the events of calendar entities, by entity ID. Each event has a `summary`, a `start`, and an `end`. Calendars that are not listed here get sample events around today. A calendar with an empty list has no events.
- `energy`: the energy settings, the keys you need from the `energy/get_prefs` response. Without it, energy is not set up.

```yaml
entities:
  - entity_id: calendar.family
    state: "off"
    attributes:
      friendly_name: Family
  - entity_id: light.kitchen_ceiling
    state: "on"
    device_id: kitchen_ceiling
    attributes:
      friendly_name: Ceiling
areas:
  - area_id: kitchen
    name: Kitchen
devices:
  - id: kitchen_ceiling
    name: Kitchen ceiling light
    manufacturer: Acme
    model: Smart bulb
    area_id: kitchen
calendars:
  calendar.family:
    - summary: Dentist
      start: "2026-11-02T15:30:00+00:00"
      end: "2026-11-02T16:15:00+00:00"
```

Calendar cards only show the events in the dates they display, so change these dates to ones near the day of your demo. To always have events, leave the calendar out of `calendars` and use the sample events. They are set around the day the script of the demo loads, and loading the page again sets them again.

Weather entities get a forecast made up from their condition and temperature, or from their `forecast` attribute when they have one. Set `supported_features` to show forecasts.

## Change and watch entities from your page

To change one entity, update it in `provider.hass.mockEntities`. The other entities stay as they are, and so does what the entity remembers, like the brightness of a light that is off. Run it from a module script, after the provider has set up its entities. This turns off the kitchen light from [Add a card](https://contributors.home-assistant.io/frontend/custom-ui/demo/#add-a-card):

```js
await customElements.whenDefined("ha-embed-provider");
const provider = document.querySelector("ha-embed-provider");
await provider.updateComplete;

provider.hass.mockEntities["light.kitchen"].update({ state: "off" });
```

To use an action of an entity, call it in the same module script. The provider updates the state and the cards:

```js
provider.hass.callService(
  "light",
  "turn_on",
  { brightness_pct: 50 },
  { entity_id: "light.kitchen" },
);
```

Setting the `entities` property of the provider replaces all entities, and they start again from what you set.

The provider fires a `states-changed` event when the state of one or more entities changes. Its detail has `states`, the states of all entities by entity ID.

## What the mocked backend can do

The mocked backend only knows a part of the Home Assistant API. Keep these limits in mind when you pick what to show:

- The actions of lights, switches, climate, covers, fans, locks, media players, and more change the state of the entity.
- Actions only reach entities that they target by entity ID. An action that targets an area or a device does nothing.
- History and statistics are made up from the current state of each entity. A few energy sensors of the demo, like `sensor.solar_production`, get made-up statistics of their own instead, so their state does not change their graph.
- Templates are not rendered. Template subscriptions fill in simple `{{ variable }}` references from the variables you pass and return the rest as it is. The REST `template` endpoint returns an error.
- The logbook is empty.
- The `toggle`, `perform-action`, and `url` actions of a card work. Actions that open a dialog, like `more-info`, do nothing. An action with a `confirmation` does nothing, because it waits for a dialog that does not open, unless the confirmation exempts the demo user.
- A `navigate` action to an anchor on your page, like `#setup`, scrolls there. A dashboard also follows links to its own views, as [Show a dashboard](https://contributors.home-assistant.io/frontend/custom-ui/demo/#show-a-dashboard) describes, and so does a view given a `dashboard`, as in [Show a view](https://contributors.home-assistant.io/frontend/custom-ui/demo/#show-a-view). Other `navigate` actions do nothing.
- Actions the backend does not handle, or for entities it does not have, do nothing. They do not return an error.
- Other requests that wait for an answer, over WebSocket or REST, get an error.

The mocks live in [`demo/src/stubs/`](https://github.com/home-assistant/frontend/tree/dev/demo/src/stubs) of the [frontend repository](https://github.com/home-assistant/frontend). If your card needs a request they do not know, see [the gallery and the demo](https://contributors.home-assistant.io/frontend/development/gallery-and-demo/#embed-cards-on-other-sites) for where to add it.
