Multi-Element Cards - Combining several related elements into one card

Much of the time it makes sense to present and work with a single entity at a time. However, there are several cases where I’ve found that I want to either deal with several related entities at a time or one entity with several attributes that I want displayed in a compact format. One reason for the desire for compactness is that my primary screen for using Home Assistant is my phone. In addition, I have also used Edge’s “Install this site as an app” to have my HA interface displayed as a small window on my desktop and laptop screens for easy use.
Here’s how my interface looks:


The entire design is predicated on the custom:button-card add-on which you can read more about here:

I also owe a debt of gratitude to ktownsend for his write-up on the custom:button-card here:
Fun with cutom:button-card - Share your Projects! / Lovelace & Frontend - Home Assistant Community (home-assistant.io)
That’s where I get the nice containers and much of the individual entity button designs. However, you’ve probably noticed those three cards in the top row, the printer card, and maybe you’ve even noticed the hose bib card with the lock on it. These are all examples of multi-element cards and the point of this post is to tell you how I made those.
The cards at the top are (loosely) based on the Raspi sensor card example in the read.me of the custom:button-card github post. They use custom fields and the grid to place multiple elements. You can read up on the specifics in the github post. The code I’ll include here will give you some more examples. In addition there are several tricks I learned along the way which I’ll also explain.
Let’s begin with the Presence Card:
Presence card
This card combines a “family sensor” which tells me whether all, some, or none of the family members are home.

Here's how I code the family sensor
- platform: template
  sensors:
    family_sensor:
      value_template: >- 
        {% set items = states.person %}
        {% set all = items|length %}
        {% set home = items|map(attribute="state")|select("equalto","home")|list|length %}
        {{ "none home" if home == 0 else "all home" if home == all else "some home" }}
      icon_template: >-
        {% if is_state('family_sensor', 'none') %}
          mdi:home-circle-outline
        {% else %}
          mdi:home-circle
        {% endif %}

It’s in a file named /config/sensors.yaml and the following line in /config/configuration.yaml refers to it:
sensor: !include sensors.yaml

Below that is the current zone for each of the family members. (I have around nine different zones set up with common locations where one or more of us is likely to be. I also handle the not-in-any-zone condition of not_home.)

Here's how I code the Presence Card

We start off in the Lovelace Raw configuration. You can get there from the Overview tab in Home Assistant by tapping the three-dot menu at the top right, selecting Edit Dashboard, then tapping again on the three-dot menu, and selecting Raw Configuration Editor if you’re still using the “UI controlled” configuration. Otherwise open your main Lovelace configuration file in an editor. The “button_card_templates:” line proceeds all the custom:button cards. You might have others in there such as “standard:”, “wide:”, and so on but all of these cards except for the hose bib one don’t require any other templates.

button_card_templates:
  presence-tile:
    color_type: card
    aspect_ratio: 3/2
    tap_action:
      action: navigate
      navigation_path: /lovelace/map
    styles:
      card:
        - background-color: darkGreen
        - border-radius: 0%
        - padding: 1%
        - color: ivory
        - font-size: 12px
        - text-transform: capitalize
      grid:
        - grid-template-areas: '"n n n n" "i fam fam fam" "m1 m1 m1 m1" "m2 m2 m2 m2" "m3 m3 m3 m3"'
        - grid-template-columns: 1fr 1fr 1fr 1fr
        - grid-template-rows: min-content 1fr min-content min-content min-content
      name:
        - font-weight: bold
        - font-size: 13px
        - color: white
        - align-self: middle
        - justify-self: start
        - padding-bottom: 0px
      img_cell:
        - justify-content: start
        - align-items: start
        - margin: 0%
      icon:
        - color: yellow
        - width: 100%
        - margin-top: 0%
      custom_fields:
        fam:
          - font-size: 13px
          - align-self: middle
          - justify-self: start
        m1:
          - align-self: middle
          - justify-self: start
        m2:
          - align-self: middle
          - justify-self: start
        m3:
          - align-self: middle
          - justify-self: start
    custom_fields:
      fam: '[[[ return `<span>Family: ${entity.state}</span>` ]]]'
      m1: |
        [[[
          var icon = "mdi:map-marker";
          var color = "lightGrey";
          var status = states[variables.m1_entity].state;
          if (status != 'not_home') {
            icon = states['zone.'+status].attributes['icon'];
            color = "white";
          }
          return `<ha-icon icon="${icon}"
          style="width: 14px; height: 14px; color: ${color};"></ha-icon>
          <span>${variables.m1_name}: ${status}</span>`
        ]]]
      m2: |
        [[[
          var icon = "mdi:map-marker";
          var color = "lightGrey";
          var status = states[variables.m2_entity].state;
          if (status != 'not_home') {
            icon = states['zone.'+status].attributes['icon'];
            color = "white";
          }
          return `<ha-icon icon="${icon}"
          style="width: 14px; height: 14px; color: ${color};"></ha-icon>
          <span>${variables.m2_name}: ${status}</span>`
        ]]]
      m3: |
        [[[
          var icon = "mdi:map-marker";
          var color = "lightGrey";
          var status = states[variables.m3_entity].state;
          if (status != 'not_home') {
            icon = states['zone.'+status].attributes['icon'];
            color = "white";
          }
          return `<ha-icon icon="${icon}"
          style="width: 14px; height: 14px; color: ${color};"></ha-icon>
          <span>${variables.m3_name}: ${status}</span>`
        ]]]

Now that template is in place, all you have to do is create a card which references it like this:

              - entity: sensor.family_sensor
                name: Presence
                template: presence-tile
                type: 'custom:button-card'
                variables:
                  m1_entity: device_tracker.sm_n986u
                  m1_name: Russ
                  m2_entity: device_tracker.janettes_phone
                  m2_name: Janette
                  m3_entity: device_tracker.katies_phone
                  m3_name: Kate

Notice the variables. That’s the key to getting the related entity information to the template for displaying.
This code displays the entity information:

      m1: |
        [[[
          var icon = "mdi:map-marker";
          var color = "lightGrey";
          var status = states[variables.m1_entity].state;
          if (status != 'not_home') {
            icon = states['zone.'+status].attributes['icon'];
            color = "white";
          }
          return `<ha-icon icon="${icon}"
          style="width: 14px; height: 14px; color: ${color};"></ha-icon>
          <span>${variables.m1_name}: ${status}</span>`
        ]]]

If this particular entity is not in any recognized zone, it will display the map-marker icon and the status “Not_home”. Otherwise, it displays both the icon and name associated with the zone that entity is in. This requires that your “friendly name” for the zone be exactly the same as the zone name suffex. In other words, the zone designated zone.icc would have to have the friendly name icc, matching the text and case exactly. That’s because the state of the entity returns the friendly name of the zone and we use that with “zone.” prefixed onto it to find the icon.

Now, let’s look at the Thermostat card:
Thermostat card
In this case, there is only one entity, the thermostat, but there are all kinds of attributes (Set point, HVAC mode, HVAC action, etc.) that are displayed.
Tapping on the card does the default action, which brings up the “More-Info” dialog which allows you to change the Set Point, HVAC mode, and Fan mode.

Here's how I code the Thermostat card

Beginning with the template:

  climate-tile:
    color_type: card
    aspect_ratio: 3/2
    styles:
      card:
        - background-color: |
            [[[
              if (entity.state == 'heat') return 'darkRed';
              if (entity.state == 'cool') return 'darkBlue';
              if (entity.state == 'off') return 'darkSlateGrey';
              else return 'yellow';
            ]]]
        - border-radius: 0%
        - padding: 1%
        - color: ivory
        - font-size: 12px
        - text-transform: capitalize
      grid:
        - grid-template-areas: '"n n n n" "i temp temp temp" "stat stat stat stat" "fan fan fan fan"'
        - grid-template-columns: 1fr 1fr 1fr 1fr
        - grid-template-rows: min-content 1fr min-content min-content
      name:
        - font-weight: bold
        - font-size: 13px
        - color: white
        - align-self: middle
        - justify-self: start
        - padding-bottom: 0px
      img_cell:
        - justify-content: start
        - align-items: start
        - margin: 0%
      icon:
        - color: yellow
        - width: 100%
        - margin-top: 0%
      custom_fields:
        temp:
          - font-size: 13px
          - align-self: middle
          - justify-self: start
        stat:
          - align-self: middle
          - justify-self: start
        fan:
          - align-self: middle
          - justify-self: start
    custom_fields:
      temp: |
        [[[
          return `<span style="font-size: 24px;">${entity.attributes['current_temperature']}°F </span>
          <span>Set:${entity.attributes['temperature']}°F</span>`
        ]]]
      stat: |
        [[[
          var hvac_action = entity.attributes['hvac_action'];
          var icon = "hass:thermostat";
          var color = "lightGrey";
          if (entity.state == "heat") {
            icon = "hass:fire";
            if (hvac_action == 'heating')
              color = 'orange';
            else if (hvac_action == 'fan')
              color = 'yellow';
            else
              color = 'white';
          } else if (entity.state == 'cool') {
            icon = "hass:snowflake";
            if (hvac_action == 'cooling')
              color = 'lightSkyBlue';
            else if (hvac_action == 'fan')
              color = 'lightGreen';
            else
              color = 'white';
          }
          return `<ha-icon icon="${icon}"
          style="width: 14px; height: 14px; color: ${color};">
          </ha-icon><span> mode: ${entity.state} | ${hvac_action}</span>`
        ]]]
      fan: |
        [[[
          var fan_mode = entity.attributes['fan_mode'];
          var fan_action = entity.attributes['fan_action'];
          var color = "lightGrey";
          if (fan_mode == "on")
            if (fan_action == "running")
              color = "lightSkyBlue";
            else
              color = "white";
          else if (fan_mode == "auto")
            if (fan_action == "running")
              color = "lightSkyBlue";
            else
              color = "white";
          return `<ha-icon icon="mdi:fan"
          style="width: 14px; height: 14px; color: ${color};"></ha-icon>
          <span> fan: <span>${fan_mode} | <span>${fan_action}</span>`
        ]]]

and then referencing it as a card this way:

              - entity: climate.thermostat_2
                name: Thermostat
                template: climate-tile
                type: 'custom:button-card'

Note that the code not only displays the appropriate icon for HVAC mode (fire for heating, snowflake for cooling) but also color codes the icon based on whether the HVAC system is actively doing so or is passively idle. The background color of the card itself also changes so you have a quick visual cue as to whether you’re heating, cooling, or the system is off.
Your thermostat might have different attributes. If so, you’d have to recode this to match those instead of mine.
One of the nice things about using a template to do this is in the case where you have multiple thermostats and multiple HVAC units. The bulk of the code is in the template leaving just four lines to instantiate it.

Next, we have the Modes card:
Modes card
In my case, this shows two modes: It shows whether it’s Day or Night, and it shows an indicator of whether there are people moving about the house or whether we’re all in bed. The code allows you to manually change this indicator by tapping on the card.

Here's how I code the Mode card:

Beginning with the template:

  mode-tile:
    color_type: card
    aspect_ratio: 3/2
    tap_action:
      action: toggle
    styles:
      card:
        - background-color: darkSlateGrey
        - border-radius: 0%
        - padding: 1%
        - color: ivory
        - font-size: 12px
        - text-transform: capitalize
      grid:
        - grid-template-areas: '"n n m1 m1" "stat stat m2 m2" "stat stat m3 m3" "stat stat m4 m4"'
        - grid-template-columns: 1fr 1fr 1fr 1fr
        - grid-template-rows: 1fr 1fr 1fr 1fr
      name:
        - font-weight: bold
        - font-size: 13px
        - color: white
        - align-self: middle
        - justify-self: start
        - padding-bottom: 0px
      img_cell:
        - justify-content: start
        - align-items: start
        - margin: 0%
      icon:
        - color: yellow
        - width: 100%
        - margin-top: 0%
      custom_fields:
        stat:
          - font-size: 13px
          - align-self: middle
          - justify-self: start
        m1:
          - align-self: middle
          - justify-self: start
        m2:
          - align-self: middle
          - justify-self: start
        m3:
          - align-self: middle
          - justify-self: start
        m4:
          - align-self: middle
          - justify-self: start
    custom_fields:
      stat: |
        [[[
          var icon = "mdi:walk";
          var status = "about";
          if (entity.state == 'on') {
            icon = "mdi:bed";
            status = "in bed";
          }
          return `<ha-icon icon=${icon}
          style="width: 34px; height: 34px; color: deepskyblue;">
          </ha-icon></p><span> ${status}</span>`
        ]]]
      m2: |
        [[[
          var icon = "mdi:weather-sunny";
          var status = "Day";
          if (states['sun.sun'].state == "below_horizon") {
            icon = "mdi:moon-waning-crescent";
            status = "Night";
          }
          return `<ha-icon icon=${icon}
          style="width: 14px; height: 14px; color: yellow;">
          </ha-icon><span> ${status}</span>`
        ]]]

…and then referencing it thusly:

              - entity: input_boolean.in_bed
                name: Modes
                template: mode-tile
                type: 'custom:button-card'
                hold_action:
                  action: fire-dom-event
                  browser_mod:
                    command: call-service
                    service: browser_mod.more_info
                    service_data:
                      entity_id: sun.sun
                      deviceID: this

There’s a sneaky thing right at the end there. I’m using Thomas Loven’s hass-browser_mod:
hass-browser_mod/README.md at master · thomasloven/hass-browser_mod (github.com)
I installed this through HACS and it allows me to pop up the more-info dialog of a completely different entity, in this case sun.sun, which then displays information on sunrise and sunset. That’s using the hold action. The tap action simply toggles the About/In Bed indicator. Obviously, one can use all manner of automation to change this state based on movement or even under-the-mattress sensors.
The important thing is that I have some automations that only run if we’re all in bed and warn me about motion and opening doors and such.

Now let’s look at the Printer card:
Printer card
For whatever reason, instead of attributes of the main entity, ink levels are actually separate entities. In my case I have a sensor.epson_wf_3820_series and a sensor.epson_wf_3820_series_black_ink, a sensor.epson_wf_3820_series_cyan_ink, etc. So the code puts all five of these entities on one compact card.

Here's how I code the Printer card:

Beginning with the template:

  printer-tile:
    color_type: card
    aspect_ratio: 3/2
    styles:
      card:
        - background-color: |
            [[[
              if (entity.state == 'idle') return 'grey';
              else return 'yellow';
            ]]]
        - border-radius: 0%
        - padding: 1%
        - color: ivory
        - font-size: 10px
        - text-transform: capitalize
      grid:
        - grid-template-areas: '"n n n m1 m1" "i i i m2 m2" "i i i m3 m3" "stat stat stat m4 m4"'
        - grid-template-columns: 1fr 1fr 1fr 1fr 1fr
        - grid-template-rows: 1fr 1fr 1fr 1fr
      name:
        - font-weight: bold
        - font-size: 13px
        - color: white
        - align-self: middle
        - justify-self: start
        - padding-bottom: 0px
      img_cell:
        - justify-content: start
        - align-items: start
        - margin: 0%
      icon:
        - color: darkSlateGrey
        - width: 70%
        - margin-top: 0%
      custom_fields:
        stat:
          - font-size: 13px
          - align-self: middle
          - width: 70%
          - justify-self: middle
        m1:
          - align-self: middle
          - justify-self: start
        m2:
          - align-self: middle
          - justify-self: start
        m3:
          - align-self: middle
          - justify-self: start
        m4:
          - align-self: middle
          - justify-self: start
    custom_fields:
      stat: '[[[ return `<span>${entity.state}</span>` ]]]'
      m1: |
        [[[
          var level = states[variables.m1_entity].state;
          var color = 'crimson';
          if (level > 10) color = 'yellow';
          if (level > 25) color = 'lightGreen';
          if (level > 50) color = 'white';
          return `<ha-icon icon="mdi:water"
          style="width: 14px; height: 14px; color: black;">
          </ha-icon><span style="color: ${color};">${level}%</span>`
        ]]]
      m2: |
        [[[
          var level = states[variables.m2_entity].state;
          var color = 'crimson';
          if (level > 10) color = 'yellow';
          if (level > 25) color = 'lightGreen';
          if (level > 50) color = 'white';
          return `<ha-icon icon="mdi:water"
          style="width: 14px; height: 14px; color: cyan;">
          </ha-icon><span style="color: ${color};">${level}%</span>`
        ]]]
      m3: |
        [[[
          var level = states[variables.m3_entity].state;
          var color = 'crimson';
          if (level > 10) color = 'yellow';
          if (level > 25) color = 'lightGreen';
          if (level > 50) color = 'white';
          return `<ha-icon icon="mdi:water"
          style="width: 14px; height: 14px; color: magenta;">
          </ha-icon><span style="color: ${color};">${level}%</span>`
        ]]]
      m4: |
        [[[
          var level = states[variables.m4_entity].state;
          var color = 'crimson';
          if (level > 10) color = 'yellow';
          if (level > 25) color = 'lightGreen';
          if (level > 50) color = 'white';
          return `<ha-icon icon="mdi:water"
          style="width: 14px; height: 14px; color: yellow;">
          </ha-icon><span style="color: ${color};">${level}%</span>`
        ]]]

… and referencing it thusly:

                  - entity: sensor.epson_wf_3820_series
                    name: WF-3820
                    template: printer-tile
                    type: 'custom:button-card'
                    variables:
                      m1_entity: sensor.epson_wf_3820_series_black_ink
                      m2_entity: sensor.epson_wf_3820_series_cyan_ink
                      m3_entity: sensor.epson_wf_3820_series_magenta_ink
                      m4_entity: sensor.epson_wf_3820_series_yellow_ink

Note that the ink level color will change according to the level. If it’s 10% or below, it’s crimson. If it’s between 11% and 25%, it’s yellow and so on, giving you a quick visual cue about when you need to get more ink.

Finally, we get the Hose Bib card:
hose bib card hose bib card2
This is the only card that is not a template. I wasn’t able to get the secondary entity functionality to work within a template so this one is coded as a button. This button combines two functions. Tapping the button with toggle whether the tap is on or off. Holding the button will toggle whether the lock is on or off. The tap is automated to shut off after two hours unless the lock is on. (My wife uses the tap to water her gardens.)

Here's how I code the Hose Bib card:
                        - entity: switch.outside_tap
                          name: Bib
                          template: standard-button
                          type: 'custom:button-card'
                          aspect_ratio: 1/1.2
                          hold_action:
                            action: fire-dom-event
                            browser_mod:
                              command: call-service
                              service: homeassistant.toggle
                              service_data:
                                entity_id: switch.outside_water_valve_lock
                          styles:
                            grid:
                              - position: relative
                            custom_fields:
                              lock:
                                - border-radius: 50%
                                - position: absolute
                                - left: 0%
                                - top: 0%
                                - height: 24px
                                - width: 24px
                                - font-size: 8px
                                - line-height: 24px
                          custom_fields:
                            lock: |
                              [[[
                                var status = states['switch.outside_water_valve_lock'].state;
                                var icon = "mdi:lock";
                                var color = "darkCyan";
                                if (status == "off") {
                                  icon = "mdi:lock-open-variant";
                                  color = 'darkRed';
                                }
                                return `<ha-icon icon="${icon}"
                                style="width: 24px; height: 24px; color: ${color};">
                                </ha-icon>`
                              ]]]
                          state:
                            - value: 'on'
                              id: value_on
                              icon: 'mdi:water-pump'
                            - value: 'off'
                              id: value_off
                              icon: 'mdi:water-pump-off'

The button does require the standard button template. I got that from ktownsend in this post:
Fun with cutom:button-card - Share your Projects! / Lovelace & Frontend - Home Assistant Community (home-assistant.io)
There are a lot of other useful templates that he’s included as well.
Also note that the hold action toggles the other entity (switch.outside_water_valve_lock). This is a helper that I’ve defined just for this.
One final note: In addition to automatically turning the tap off after 2 hours if the lock isn’t on, I wanted some additional functionality: It makes no sense to turn the lock on when the tap isn’t on, so If the lock is set on, I automatically turn the tap on. Likewise, the lock should unlock if you manually turn the bib off. I use the following automations to accomplish all of this:

- alias: 'timing-Outside Tap off after 2 hours'
  trigger:
    platform: state
    entity_id: switch.outside_tap
    to: 'on'
    for: '02:00:00'
  condition:
    condition: state
    entity_id: switch.outside_water_valve_lock
    state: 'off'
  action:
    - service: switch.turn_off
      entity_id: switch.outside_tap
- alias: 'timing-Outside Tap lock off when Tap shut off'
  trigger:
    platform: state
    entity_id: switch.outside_tap
    to: 'off'
  action:
    - service: switch.turn_off
      entity_id: switch.outside_water_valve_lock
- alias: 'timing-Outside Tap on when Tap lock turned on'
  trigger:
    platform: state
    entity_id: switch.outside_water_valve_lock
    to: 'on'
  action:
    - service: switch.turn_on
      entity_id: switch.outside_tap
7 Likes