Stack-In-Card: Drop-in replacement for vertical-stack-in-card

Stack In Card by @RomRider

A replacement for vertical-stack-in-card and horizontal-stack-in-card with some more features.

It allows to group multiple cards into one card without the borders. By default, it will stack everything vertically.

GitHub Release
License
hacs_badge

Project Maintenance
GitHub Activity

Discord

Options

If a card inside the stack has the --keep-background CSS style defined, it will not replace the background. This is usefull for button-card for example. You can also define this CSS variable by using card-mod.

Name Type Requirement Description Default
type string Required custom:stack-in-card
title string Optional Header of the card
mode string Optional vertical or horizontal stack vertical
cards object Required The cards you want to embed none

Example

Simple Example

example

- type: custom:stack-in-card
  title: My Stack In Card
  mode: vertical
  cards:
    - type: horizontal-stack
      cards:
        - type: button
          entity: sun.sun
        - type: button
          entity: sun.sun
    - type: vertical-stack
      cards:
        - type: entities
          entities:
            - sun.sun

Example with button-card to keep the background

This will keep the background of the button even if stacked:

- type: custom:stack-in-card
  title: My Stack In Card
  mode: vertical
  cards:
    - type: custom:button-card
      entity: sun.sun
      color_type: card
      styles:
        card:
          - --keep-background: 'true'

Installation

Use HACS or follow this guide

resources:
  url: /local/stack-in-card.js
  type: module
14 Likes

Updating the HACS Repos to find it :slight_smile:

does styling work the same as with vertical-stack-in-card?
This following code produces this:

- type: custom:mod-card # robovac
  style: |
    ha-card {
      height: 115px;
      background-color: var(--primary-background-color);
      border-radius: 15px;
      box-shadow: 
        {% if is_state('sun.sun', 'above_horizon') %}
          -4px -4px 4px 0 rgba(255,255,255,.5),4px 4px 4px 0 rgba(0,0,0,.03);
        {% else %}
          -4px -4px 4px 0 rgba(50, 50, 50,.5),4px 4px 4px 0 rgba(0,0,0,.15);
        {% endif %}   
    }
  card:
    type: custom:stack-in-card
    mode: vertical
    cards:
      - type: entities
        entities:
          - entity: vacuum.robovac
            type: custom:multiple-entity-row
            name: Robovac
            show_state: false
            entities:
              - attribute: status
                name: Status
              - attribute: battery_level
                name: Battery
                unit: '%'
              - entity: counter.vacuum_counter
                name: Count
        style: |
          ha-card {
            height: 70px;
          }
      - type: horizontal-stack
        cards:
          - type: custom:button-card
            color: auto
            entity: vacuum.robovac
            size: 25%
            icon: mdi:play
            show_name: false
            tap_action:
              action: call-service
              service: vacuum.turn_on
              service_data:
                entity_id: vacuum.robovac
          - type: custom:button-card
            color: auto
            entity: vacuum.robovac
            size: 25%
            icon: mdi:pause
            show_name: false
            tap_action:
              action: call-service
              service: vacuum.pause
              service_data:
                entity_id: vacuum.robovac
          - type: custom:button-card
            color: auto
            entity: vacuum.robovac
            size: 25%
            icon: mdi:broom
            show_name: false
            tap_action:
              action: call-service
              service: vacuum.clean_spot
              service_data:
                entity_id: vacuum.robovac
          - type: custom:button-card
            color: auto
            entity: vacuum.robovac
            size: 25%
            icon: mdi:map-marker
            show_name: false
            tap_action:
              action: call-service
              service: vacuum.locate
              service_data:
                entity_id: vacuum.robovac
          - type: custom:button-card
            color: auto
            entity: vacuum.robovac
            size: 25%
            icon: mdi:home-map-marker
            show_name: false
            tap_action:
              action: call-service
              service: vacuum.return_to_base
              service_data:
                entity_id: vacuum.robovac

Edit: Oh got it… mod-card is no longer needed :slight_smile:

Edit 2: got it to work on this example but this config fails:

            - type: custom:stack-in-card
              style: |
                ha-card {
                  background-color: var(--primary-background-color);
                  border-radius: 15px;
                  box-shadow: 
                    {% if is_state('sun.sun', 'above_horizon') %}
                      -4px -4px 4px 0 rgba(255,255,255,.5),4px 4px 4px 0 rgba(0,0,0,.03);
                    {% else %}
                      -4px -4px 4px 0 rgba(50, 50, 50,.5),4px 4px 4px 0 rgba(0,0,0,.15);
                    {% endif %}
                }
              mode: vertical
              cards:
                - type: glance
                  title: Home Assistant
                  columns: 7
                  show_name: false
                  entities:
                    - entity: sensor.count_automations
                      name: Automations
                      icon: mdi:play-circle
                    - entity: sensor.count_binary_sensors
                      name: Binary Sensors
                      icon: mdi:transition-masked
                    - entity: sensor.count_lights
                      name: Lights
                      icon: mdi:lightbulb
                    - entity: sensor.count_scripts
                      name: Scripts
                      icon: mdi:script-text
                    - entity: sensor.count_sensors
                      name: Sensors
                      icon: mdi:counter
                    - entity: sensor.count_switches
                      name: Switches
                      icon: mdi:toggle-switch
                    - entity: sensor.count_zones
                      name: Zones
                - type: entities
                  show_header_toggle: false
                  entities:
                    - entity: sensor.potential_breaking_changes
                      name: Breaking changes
                      icon: mdi:alert-circle
                    - entity: input_select.log_level

Thanks for the new card!
If not using HACS, where can we find that stack-in-card.js?

I looked at that page but forgot to check releases button/section. Got it now.

Great! Replaced the old vertical-in-card and working perfectly. Thanks!

@RomRider I got a question, is it possible to have the “divider” working inside the stack-in-card?

My code:

people:
  card:
    type: custom:stack-in-card
    mode: vertical
    cards:
      - type: custom:decluttering-card
        template: title
        variables:
          - title: People
      - type: custom:decluttering-card
        template: andrea
      - type: divider

The error:
image

Also, I am seeing in the console a huge amount of errors like this one:

hui-stack-card.ts:109 Uncaught TypeError: Cannot read property 'replaceChild' of null
    at HTMLElement.value (hui-stack-card.ts:109)
    at HTMLElement.t.addEventListener.once (hui-stack-card.ts:97)
    at r (fire_event.ts:76)
    at create-element-base.ts:94
value @ hui-stack-card.ts:109
t.addEventListener.once @ hui-stack-card.ts:97
r @ fire_event.ts:76
(anonymous) @ create-element-base.ts:94
Promise.then (async)
a @ create-element-base.ts:92
l @ create-element-base.ts:154
Tt @ create-card-element.ts:53
value @ hui-stack-card.ts:89
(anonymous) @ hui-stack-card.ts:54
value @ hui-stack-card.ts:53
s @ create-element-base.ts:61
l @ create-element-base.ts:158
Tt @ create-card-element.ts:53
_createCard @ stack-in-card.js:784
async function (async)
_createCard @ stack-in-card.js:784
setConfig @ stack-in-card.js:758
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
_createCard @ decluttering-card.js:743
async function (async)
_createCard @ decluttering-card.js:743
setConfig @ decluttering-card.js:739
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-stack-card.ts:89
(anonymous) @ hui-stack-card.ts:54
value @ hui-stack-card.ts:53
s @ create-element-base.ts:61
l @ create-element-base.ts:158
Tt @ create-card-element.ts:53
_createCard @ stack-in-card.js:784
async function (async)
_createCard @ stack-in-card.js:784
setConfig @ stack-in-card.js:758
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
_createCard @ decluttering-card.js:743
async function (async)
_createCard @ decluttering-card.js:743
setConfig @ decluttering-card.js:739
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-stack-card.ts:89
(anonymous) @ hui-stack-card.ts:54
value @ hui-stack-card.ts:53
s @ create-element-base.ts:61
l @ create-element-base.ts:158
Tt @ create-card-element.ts:53
_createCard @ stack-in-card.js:784
async function (async)
_createCard @ stack-in-card.js:784
setConfig @ stack-in-card.js:758
(anonymous) @ layout-card.js:1
u @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_card @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_cards @ layout-card.js:1
updated @ layout-card.js:1
performUpdate @ updating-element.ts:720
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
initialize @ lit-element.ts:146
A @ updating-element.ts:438
V @ lit-element.ts:61
p @ layout-card.js:1
s @ create-element-base.ts:56
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-panel-view.ts:55
value @ hui-panel-view.ts:34
performUpdate @ updating-element.ts:704
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
A @ updating-element.ts:438
i @ hui-panel-view.ts:16
value @ hui-root.ts:709
(anonymous) @ hui-root.ts:79
(anonymous) @ debounce.ts:22
setTimeout (async)
(anonymous) @ debounce.ts:27
handleEvent @ parts.ts:505
__boundHandleEvent @ parts.ts:463
r @ card-mod.js:1
(anonymous) @ card-mod.js:1
Promise.then (async)
(anonymous) @ card-mod.js:1
n @ card-mod.js:1
(anonymous) @ card-mod.js:1
(anonymous) @ card-mod.js:1
3hui-stack-card.ts:109 Uncaught TypeError: Cannot read property 'replaceChild' of null
    at HTMLElement.value (hui-stack-card.ts:109)
    at HTMLElement.t.addEventListener.once (hui-stack-card.ts:97)
    at r (fire_event.ts:76)
    at create-element-base.ts:94
value @ hui-stack-card.ts:109
t.addEventListener.once @ hui-stack-card.ts:97
r @ fire_event.ts:76
(anonymous) @ create-element-base.ts:94
Promise.then (async)
a @ create-element-base.ts:92
l @ create-element-base.ts:154
Tt @ create-card-element.ts:53
value @ hui-stack-card.ts:89
(anonymous) @ hui-stack-card.ts:54
value @ hui-stack-card.ts:53
s @ create-element-base.ts:61
l @ create-element-base.ts:158
Tt @ create-card-element.ts:53
_createCard @ stack-in-card.js:784
async function (async)
_createCard @ stack-in-card.js:784
setConfig @ stack-in-card.js:758
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
_createCard @ decluttering-card.js:743
async function (async)
_createCard @ decluttering-card.js:743
setConfig @ decluttering-card.js:739
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-stack-card.ts:89
(anonymous) @ hui-stack-card.ts:54
value @ hui-stack-card.ts:53
s @ create-element-base.ts:61
l @ create-element-base.ts:158
Tt @ create-card-element.ts:53
_createCard @ stack-in-card.js:784
async function (async)
_createCard @ stack-in-card.js:784
setConfig @ stack-in-card.js:758
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
(anonymous) @ layout-card.js:1
build_card @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_cards @ layout-card.js:1
updated @ layout-card.js:1
performUpdate @ updating-element.ts:720
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
initialize @ lit-element.ts:146
A @ updating-element.ts:438
V @ lit-element.ts:61
p @ layout-card.js:1
s @ create-element-base.ts:56
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-panel-view.ts:55
value @ hui-panel-view.ts:34
performUpdate @ updating-element.ts:704
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
A @ updating-element.ts:438
i @ hui-panel-view.ts:16
value @ hui-root.ts:709
(anonymous) @ hui-root.ts:79
(anonymous) @ debounce.ts:22
setTimeout (async)
(anonymous) @ debounce.ts:27
handleEvent @ parts.ts:505
__boundHandleEvent @ parts.ts:463
a @ layout-card.js:1
(anonymous) @ layout-card.js:1
Promise.then (async)
(anonymous) @ layout-card.js:1
u @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_card @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_cards @ layout-card.js:1
updated @ layout-card.js:1
performUpdate @ updating-element.ts:720
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
requestUpdate @ updating-element.ts:623
updateSize @ layout-card.js:1
firstUpdated @ layout-card.js:1
performUpdate @ updating-element.ts:718
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
initialize @ lit-element.ts:146
A @ updating-element.ts:438
V @ lit-element.ts:61
p @ layout-card.js:1
s @ create-element-base.ts:56
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-panel-view.ts:55
value @ hui-panel-view.ts:34
performUpdate @ updating-element.ts:704
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
A @ updating-element.ts:438
i @ hui-panel-view.ts:16
value @ hui-root.ts:709
(anonymous) @ hui-root.ts:79
(anonymous) @ debounce.ts:22
setTimeout (async)
(anonymous) @ debounce.ts:27
handleEvent @ parts.ts:505
__boundHandleEvent @ parts.ts:463
r @ card-mod.js:1
(anonymous) @ card-mod.js:1
Promise.then (async)
(anonymous) @ card-mod.js:1
n @ card-mod.js:1
(anonymous) @ card-mod.js:1
(anonymous) @ card-mod.js:1
3hui-stack-card.ts:109 Uncaught TypeError: Cannot read property 'replaceChild' of null
    at HTMLElement.value (hui-stack-card.ts:109)
    at HTMLElement.t.addEventListener.once (hui-stack-card.ts:97)
    at r (fire_event.ts:76)
    at create-element-base.ts:94
value @ hui-stack-card.ts:109
t.addEventListener.once @ hui-stack-card.ts:97
r @ fire_event.ts:76
(anonymous) @ create-element-base.ts:94
Promise.then (async)
a @ create-element-base.ts:92
l @ create-element-base.ts:154
Tt @ create-card-element.ts:53
value @ hui-stack-card.ts:89
(anonymous) @ hui-stack-card.ts:54
value @ hui-stack-card.ts:53
s @ create-element-base.ts:61
l @ create-element-base.ts:158
Tt @ create-card-element.ts:53
_createCard @ stack-in-card.js:784
async function (async)
_createCard @ stack-in-card.js:784
setConfig @ stack-in-card.js:758
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
_createCard @ decluttering-card.js:743
async function (async)
_createCard @ decluttering-card.js:743
setConfig @ decluttering-card.js:739
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-stack-card.ts:89
(anonymous) @ hui-stack-card.ts:54
value @ hui-stack-card.ts:53
s @ create-element-base.ts:61
l @ create-element-base.ts:158
Tt @ create-card-element.ts:53
_createCard @ stack-in-card.js:784
async function (async)
_createCard @ stack-in-card.js:784
setConfig @ stack-in-card.js:758
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
_createCard @ decluttering-card.js:743
async function (async)
_createCard @ decluttering-card.js:743
setConfig @ decluttering-card.js:739
s @ create-element-base.ts:61
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
(anonymous) @ layout-card.js:1
build_card @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_cards @ layout-card.js:1
updated @ layout-card.js:1
performUpdate @ updating-element.ts:720
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
initialize @ lit-element.ts:146
A @ updating-element.ts:438
V @ lit-element.ts:61
p @ layout-card.js:1
s @ create-element-base.ts:56
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-panel-view.ts:55
value @ hui-panel-view.ts:34
performUpdate @ updating-element.ts:704
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
A @ updating-element.ts:438
i @ hui-panel-view.ts:16
value @ hui-root.ts:709
(anonymous) @ hui-root.ts:79
(anonymous) @ debounce.ts:22
setTimeout (async)
(anonymous) @ debounce.ts:27
handleEvent @ parts.ts:505
__boundHandleEvent @ parts.ts:463
a @ layout-card.js:1
(anonymous) @ layout-card.js:1
Promise.then (async)
(anonymous) @ layout-card.js:1
u @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_card @ layout-card.js:1
(anonymous) @ layout-card.js:1
build_cards @ layout-card.js:1
updated @ layout-card.js:1
performUpdate @ updating-element.ts:720
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
requestUpdate @ updating-element.ts:623
updateSize @ layout-card.js:1
firstUpdated @ layout-card.js:1
performUpdate @ updating-element.ts:718
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
initialize @ lit-element.ts:146
A @ updating-element.ts:438
V @ lit-element.ts:61
p @ layout-card.js:1
s @ create-element-base.ts:56
a @ create-element-base.ts:80
l @ create-element-base.ts:131
Tt @ create-card-element.ts:53
value @ hui-panel-view.ts:55
value @ hui-panel-view.ts:34
performUpdate @ updating-element.ts:704
_enqueueUpdate @ updating-element.ts:653
async function (async)
_enqueueUpdate @ updating-element.ts:640
_requestUpdate @ updating-element.ts:605
initialize @ updating-element.ts:449
A @ updating-element.ts:438
i @ hui-panel-view.ts:16
value @ hui-root.ts:709
(anonymous) @ hui-root.ts:79
(anonymous) @ debounce.ts:22
setTimeout (async)
(anonymous) @ debounce.ts:27
handleEvent @ parts.ts:505
__boundHandleEvent @ parts.ts:463
r @ card-mod.js:1
(anonymous) @ card-mod.js:1
Promise.then (async)
(anonymous) @ card-mod.js:1
n @ card-mod.js:1
(anonymous) @ card-mod.js:1
(anonymous) @ card-mod.js:1
hui-stack-card.ts:109 Uncaught TypeError: Cannot read property 'replaceChild' of null
    at HTMLElement.value (hui-stack-card.ts:109)
    at HTMLElement.t.addEventListener.once (hui-stack-card.ts:97)
    at r (fire_event.ts:76)
    at create-element-base.ts:94

Thanks for your work :slight_smile:

hui-stack-card is not me, it’s a core element (vertical-stack or horizontal-stack). Not much I can do about it.

I’ll add the divider support though, thanks for letting me know :slight_smile:

Edit: type: divider is not valid in a standard vertical-stack (and this is what I use to wrap all your cards).

Thanks :slight_smile:

That’s odd, that error started now that I’ve added your stack-in-card, I thought it was related

Not sure to understand what fails. Could detail the problem please?

You’ll have to replace your divider with:

          - type: entities
            entities:
              - type: divider

But there another problem by doing that which I’m trying to fix right now :slight_smile:

Oh I see, thanks for the time you’re taking to help me with this

Sorry for being vague… the styling is only applied on the first part (glance-card) of the card. The entities part should be in the same background-colour as the glance card

Right, I’ve seen the same thing on my side. entities card do not keep the style. I’m looking into this :slight_smile:

1 Like

how come type: divider isn’t valid? Or, maybe the better question would be: how come the the vertical-stack-in-card didn’t throw an error on this? much friendlier to use the shorthand notation…

maybe you are willing to think of that finding a solution to the problem your trying to fix now :wink:

btw, your current solution doesn’t throw an error in the inspector at all, and is working just perfectly as far as we can see here.

trying to keep the background of the markdown card in the stack wont work, am I misunderstanding the --keep-background styles setting?

  - type: custom:stack-in-card
    cards:
      - type: markdown
        styles:
          card:
            - --keep-background: 'true'
        style: |
          ha-card {
            background: url('/local/images/alarm_bell.png');
            background-size: cover;
          }
        content: >
          # <font color={{'green' if states('sensor.next_alarm') == 'Not set' else 'crimson'}}>Next alarm</font>

          <font color={{'green' if states('sensor.next_alarm') == 'Not set' else 'crimson'}}>{{states('sensor.next_alarm_text')}}</font>

      - type: entities
#        title: Next alarm
        show_header_toggle: false
        entities:
          - sensor.next_alarm_text
          - sensor.time_until_next_alarm
          - sensor.number_of_days_next_alarm
          - sensor.next_alarm_day
          - sensor.next_alarm
          - entity: script.say_next_alarm
            action_name: Say
          - entity: script.say_next_alarm_greet
            action_name: Greet

I won’t find a solution, as instead of redefining a complete new element I leverage HA’s core elements (vertical-stack and horizontal-stack) and they natively don’t support divider as a child element.

        style: |
          ha-card {
            background: url('/local/images/alarm_bell.png');
            background-size: cover;
            --keep-background: 'true';
          }

Of course I tried that before asking, sorry, should have mentioned. It doesn’t work however…It show what I posted above, and no error in the inspector

for reference, this is what vertical-stack-in-card makes of it:

funny enough, it doesnt complain about the --keep-background which I forgot to take out

@RomRider Great that you took over the card. I’ve read your comments over at GitHub. :slight_smile: Thanks!

When you’re done with all the “moving”, could you please make an example for an update from vertical-stack-in-card to stack-in-card. Oh, and it would be great, if you could rename and clean the forked repository. :slight_smile:

Thanks for taking action! :slight_smile: