Mushroom Cards - Build a beautiful dashboard easily šŸ„ (Part 1)

Post what you already have in code please.

Thanks. I understand. But where i have to put the ā€œif statesā€ code?

Where i showed. just around the animation and transform-origin statements.

to put it in to full perspective.

card_mod:
  style: |
    ha-state-icon {
      {% if states('input_boolean.yourentityid') == 'on' %}
        animation: shake 400ms ease-in-out infinite, drum 2s ease infinite;
        transform-origin: 50% 110%;
      {% else %}

      {% endif %}
    }
    @keyframes spin {
      0% {transform: rotate(360deg);}
      100% {transform: rotate(0deg);}
    }

Hi Dimitri,

It is a bit difficult as I have most of my code seperated in decluttering-cards but I will tryā€¦

The following code is how it looks at the moment. I have a mushroom template card and chip cards stack together.
The result looks like that:
Card 2

#Code 1
        - type: custom:stack-in-card
          cards:
            # Header Card
            - type: custom:decluttering-card
              template: my_room_template_card
              variables:
                - primary: '[[primary]]'
                - temperature: '[[temperature]]'
                - humidity: '[[humidity]]'
                - luminance: '[[luminance]]'
                - icon: '[[icon]]'
                - entity: '[[entity]]'
                - tab_action: '[[tab_action]]'
                - navigation_path: '[[navigation_path]]'
                - icon_color: '[[icon_color]]'
                - motion_sensor: '[[motion_sensor]]'

            # Sensor cards
            - type: custom:mushroom-chips-card
              chips:

            # IoT's

              # Dishwasher
                - type: conditional
                  conditions:
                    - entity: '[[dishwasher]]'
                      state: 'on'
                  chip:
                    type: template
                    icon: |
                      {{ state_attr('[[dishwasher]]', 'icon') }}
                    icon_color: light-blue
                    content: |
                      {{ state_attr('[[dishwasher]]', 'remaining_time_for_ui') }}
                    tap_action:
                      action: navigate
                      navigation_path: /lovelace-kitchen/0
                    hold_action:
                      action: none
                    double_tap_action:
                      action: none

              # Oven
                - type: conditional
                  conditions:
                    - entity: '[[oven]]'
                      state: 'on'
                  chip:
                    type: template
                    icon: |
                      {{ state_attr('[[oven]]', 'icon') }}
                    icon_color: |
                      {{ state_attr('[[oven]]', 'icon_color') }}
                    content: |
                      {{ state_attr('[[oven]]', 'remaining_time_for_ui') }}
                    tap_action:
                      action: navigate
                      navigation_path: /lovelace-kitchen/0
                    hold_action:
                      action: none
                    double_tap_action:
                      action: none

                    
              alignment: end
              style: |
                ha-card {
                    --chip-box-shadow: none;
                    --chip-background: none;
                    --chip-spacing: 0;
                      }  
          style: |
              ha-card {
                height: 102px;
                {% if is_state('[[entity]]', 'on') %}
                  background: rgba(255, 152, 0, 0.1);
                {% endif %}
                  } 

For the sake of good order I also post the code for the template card which is behind the decluttering-card of code 1.

# Code 2:
type: custom:mushroom-template-card
      primary: '[[primary]]'
      secondary: >-
          {{ states('[[temperature]]', with_unit=True) if has_value('[[temperature]]') }}
          {{ " | " if (has_value('[[temperature]]') and has_value('[[humidity]]')) }}
          {{ (states('[[humidity]]', with_unit=True)) if has_value('[[humidity]]') }} 
          {{ " | " if (has_value('[[humidity]]') and has_value('[[luminance]]')) }}
          {{ (states('[[luminance]]', with_unit=True)) if has_value('[[luminance]]') }} 
      icon: '[[icon]]'
      badge_icon: |-
        {{ 'mdi:motion-sensor' if has_value('[[motion_sensor]]') and 
              is_state('[[motion_sensor]]', 'on')}}
      badge_color: light-blue
      entity: '[[entity]]'
      tap_action:
        action: '[[tab_action]]'
        navigation_path: '[[navigation_path]]'
      hold_action:
        action: toggle
      icon_color: '[[icon_color]]'
      fill_container: true
      layout: horizontal
      multiline_secondary: false
      style: |
          :host([dark-mode]) {
            background: rgba(var(--rgb-primary-background-color), 0.025);
          } 
          :host {
            background: rgba(var(--rgb-primary-text-color), 0.025);
          }

Now follows the code 3 that stacks a chip card with a markdown card. Is results into the card I am looing for.

Card 3

I hope it does not become to confusing.

# Code 3:
      type: conditional
      conditions:
        - entity: '[[entity]]'
          state: '[[state]]'
      card:
        type: vertical-stack
        cards:
          - type: markdown
            content: '[[header]]'
            style: |
              ha-card {
                  text-align: center;            
                  box-shadow: none;
                  background: none;
                  box-shadow: 'rgba(0,0,0,0)';
                  font-size: 8px;
                --chip-box-shadow: none;
                --chip-background: none;
                --chip-spacing: 0;
                } 
          - type: custom:mushroom-chips-card
            chips:
              - type: template
                icon: '[[icon]]'
                icon_color: '[[icon_color]]'
                content: '[[content]]'
                tap_action:
                  action: none
                hold_action:
                  action: none
                double_tap_action:
                  action: none
            alignment: center
            style: |
              ha-card {
                  --chip-box-shadow: none;
                  --chip-background: none;
                  --chip-spacing: 0;
                  margin-top: -30px;
                    }

However I donā€™t know how to implement several of those cards into code 1 to make them being alined on the right side/end. It does not work as a mushroom-chips-card but when I use horizontal stack they get equaly seperated through out the row. But I want them alined like shown in below picture.

Card

Instead of using a markdown card maybe just use a CSS pseudo element.

image

              card_mod:
                style: |
                  ha-card::after {
                    content: 'Dryer';
                    position: absolute;
                    font-size: 9px;
                    top: -8px;
                    right: 20px
                  }

and full code:

type: custom:stack-in-card
cards:
  - type: custom:mushroom-template-card
    primary: Ground Floor
    secondary: >-
      {{ states('sensor.lounge_downstairs_temperature', with_unit=True) if
      has_value('sensor.lounge_downstairs_temperature') }} {{ " | " if
      (has_value('sensor.lounge_downstairs_temperature') and
      has_value('sensor.lounge_downstairs_humidity')) }} {{
      (states('sensor.lounge_downstairs_humidity', with_unit=True)) if
      has_value('sensor.lounge_downstairs_humidity') }}  {{ " | " if
      (has_value('[[humidity]]') and has_value('[[luminance]]')) }} {{
      (states('[[luminance]]', with_unit=True)) if has_value('[[luminance]]')
      }} 
    icon: mdi:home-thermometer
    badge_icon: >-
      {{ 'mdi:motion-sensor' if
      has_value('binary_sensor.hallway_motion_sensor_occupancy') and 
            is_state('binary_sensor.hallway_motion_sensor_occupancy', 'on')}}
    badge_color: light-blue
    entity: light.lounge_lights
    tap_action:
      action: navigate
      navigation_path: '[[navigation_path]]'
    hold_action:
      action: toggle
    icon_color: disabled
    fill_container: true
    layout: horizontal
    multiline_secondary: false
    card_mod:
      style: |
        :host([dark-mode]) {
          background: rgba(var(--rgb-primary-background-color), 0.025);
        } 
        :host {
          background: rgba(var(--rgb-primary-text-color), 0.025);
        }
  - type: conditional
    conditions:
      - entity: input_boolean.batteries_low
        state: 'on'
    card:
      type: vertical-stack
      cards:
        - type: custom:mushroom-chips-card
          chips:
            - type: template
              icon: mdi:tumble-dryer
              icon_color: blue
              content: 899W
              tap_action:
                action: none
              hold_action:
                action: none
              double_tap_action:
                action: none
              card_mod:
                style: |
                  ha-card::after {
                    content: 'Dryer';
                    position: absolute;
                    font-size: 9px;
                    top: -8px;
                    right: 20px
                  }
          alignment: end
          card_mod:
            style: |
              ha-card {
                --chip-box-shadow: none;
                --chip-background: none;
                --chip-spacing: 0;
                margin-top: -30px;
              }

Dimitri, you are my men! I knew that there must be a much easier way to achive it.
You have fixed positioned the header by using (right: 20px).
Is there a way to position it by using center? The sensor chip cards all have a different width and if they only have an icon without any text then the header way off.

Unfortunately I still have now idea from CSS

Try and use right: calc(50% - 10px); should center better along different sized chips :slight_smile:

We cant center directly because we are using position: absolute; which we have to use to position it nicely in the first place. If we use position: relative; instead then the text floats to the right of the chip and expands the chip size insteadā€¦ which is not idealā€¦

It works only a little better. When there is no text behind the icon basicially just the icon then the header is still way off.

Can I somehow get the width of the main element and take 50% of this.
Something like

-{50% of the element's width};

I wouldnt say that it is way off?
image

              card_mod:
                style: |
                  ha-card::after {
                    content: 'Dryer';
                    position: absolute;
                    font-size: 9px;
                    right: calc(50% - 10px);
                    top: -8px;
                  }

its a bit closer with right: calc(50% - 11px); instead.

but no is the simple answer you cant know the width of the main element. as the chips width is set to auto. not a specific width, you cant know it.

why is it always so difficultā€¦ :rofl:
Youā€™re right of course, itā€™s an exaggeration to say that it is way off.

I found out that the following code centers it better if you have a text behind the icon.

# Code 1
                    card_mod:
                      style: |
                        ha-card::after {
                          content: 'Server';
                          position: absolute;
                          font-size: 9px;
                          top: -8px;
                          inset: 0 auto 50% 50%;
                          transform: translate(-50%, -50%);
                        }

but your code is better if there is just the icon.

# Code 2:
                    card_mod:
                      style: |
                        ha-card::after {
                          content: 'Server';
                          position: absolute;
                          font-size: 9px;
                          top: -8px;
                          right: calc(50% - 10px);
                        }

Example code 1:
Shutter2 Code2

Example code 2:
Shutter1 Code 1

How to get the best out of the two worlds? :grin:

EDIT,
Nevermind got it with the below:

              card_mod:
                style: |
                  ha-card::after {
                    content: 'Dishwasher Long Test Name';
                    position: absolute;
                    font-size: 9px;     
                    width: max-content;
                    left: 50%;     
                    transform: translate(-50%, -80%);
                  }

I was just looking in this as my Washer finally wanted to connect to my network, so I did steal your code and made some animations in it and some other minor changes:

type: custom:mushroom-template-card
primary: Wasmachine
secondary: |-
  {% if states('sensor.washer_job_state') == 'none' %}
    Klaar
  {% else %}
    Klaar over: {{((states('sensor.washer_completion_time') | as_datetime) - now()).total_seconds() | timestamp_custom('%-Hh %-Mm', false) }}
  {% endif %}
icon: |-
  {% if states('sensor.washer_job_state') == 'drying' %}
    mdi:tumble-dryer
  {% elif states('sensor.washer_job_state') == 'none' %}
   mdi:washing-machine-off
  {% else %}
    mdi:washing-machine
  {% endif %}
icon_color: |-
  {% if states('sensor.washer_job_state') == 'none' %} 
    #6f6f6f
  {% elif states('sensor.washer_job_state') == 'wash' %}
    light-blue
  {% elif states('sensor.washer_job_state') == 'drying' %}
    #fc7b03
  {% endif %}
entity: sensor.washer_job_state
tap_action:
  action: more-info
hold_action:
  action: none
double_tap_action:
  action: none
multiline_secondary: false
card_mod:
  style: |
    ha-state-icon {
      {% if is_state(config.entity, 'wash' ) %}
        animation: shake 400ms ease-in-out infinite, drum 1s infinite;
      {% endif %}
      transform-origin: 50% 65%;
    }
    @keyframes shake {
      0%, 100% { transform: rotate(4deg); }
      50%  { transform: rotate(-4deg); }
    }
    @keyframes drum {
      50%  { clip-path: polygon(0 0, 0 100%, 35% 100%, 36% 74%, 31% 43%, 61% 40%, 71% 69%, 62% 78%, 36% 73%, 35% 100%, 100% 100%, 100% 0); }
    }

    ha-state-icon {
      {% if is_state(config.entity, 'drying' ) %}
        animation: shake 400ms ease-in-out infinite, drum 1s infinite;
      {% endif %}
      transform-origin: 50% 65%;
    }
    @keyframes shake {
      0%, 100% { transform: rotate(4deg); }
      50%  { transform: rotate(-4deg); }
    }
    @keyframes drum {
      50%  { clip-path: polygon(0 0, 0 100%, 35% 100%, 36% 74%, 31% 43%, 61% 40%, 71% 69%, 62% 78%, 36% 73%, 35% 100%, 100% 100%, 100% 0); }
    }

        ha-state-icon {
      {% if is_state(config.entity, 'rinse' ) %}
        animation: shake 400ms ease-in-out infinite, drum 1s infinite;
      {% endif %}
      transform-origin: 50% 65%;
    }
    @keyframes shake {
      0%, 100% { transform: rotate(4deg); }
      50%  { transform: rotate(-4deg); }
    }
    @keyframes drum {
      50%  { clip-path: polygon(0 0, 0 100%, 35% 100%, 36% 74%, 31% 43%, 61% 40%, 71% 69%, 62% 78%, 36% 73%, 35% 100%, 100% 100%, 100% 0); }
    }

    ha-state-icon {
      {% if is_state(config.entity, 'spin' ) %}
        animation: shake 400ms ease-in-out infinite, drum 1s infinite;
      {% endif %}
      transform-origin: 50% 65%;
    }
    @keyframes shake {
      0%, 100% { transform: rotate(4deg); }
      50%  { transform: rotate(-4deg); }
    }
    @keyframes drum {
      50%  { clip-path: polygon(0 0, 0 100%, 35% 100%, 36% 74%, 31% 43%, 61% 40%, 71% 69%, 62% 78%, 36% 73%, 35% 100%, 100% 100%, 100% 0); }
    }

2 Likes

You might be interested in my way more overkill version then :wink:

it displays the time better by only showing hours when hours is applicable, so once 59m left. it wont say 0h 59m, it will just say 59m :slight_smile:

plus a color difference for every state that my washing machine gives :slight_smile:

              type: custom:mushroom-template-card
              primary: Washing Machine
              secondary: |-
                {% if states('sensor.washing_machine_washer_job_state') == 'none' %}
                  Finished 
                {% else %} 
                  {% set ct = states('sensor.washing_machine_washer_completion_time') | as_datetime %}
                  {% set final = (ct - now()).total_seconds() | timestamp_custom('%-Hh %-Mm', false) %}
                  {% set test = final.split('h')[0] | int %}
                  {% if test == 0 %}
                    Est. Completion {{ final.split('h')[1] }}
                  {% else %}
                    Est. Completion {{final}}
                  {% endif %}
                {% endif %}
              icon: |-
                {% if states('sensor.washing_machine_washer_job_state') == 'drying' %}
                  mdi:tumble-dryer
                {% else %}
                  mdi:washing-machine
                {% endif %}
              badge_icon: |-
                {% if states('sensor.washing_machine_washer_job_state') == 'none' %} 
                  mdi:flag-checkered
                {% elif states('sensor.washing_machine_washer_job_state') == 'wash' %}
                  mdi:waves
                {% elif states('sensor.washing_machine_washer_job_state') == 'rinse' %}
                  mdi:tilde
                {% elif states('sensor.washing_machine_washer_job_state') == 'spin' %}
                  mdi:rotate-left
                {% elif states('sensor.washing_machine_washer_job_state') == 'drying' %}
                  mdi:weather-sunny
                {% elif states('sensor.washing_machine_washer_job_state') == 'weightSensing'%}
                  mdi:scale
                {% elif states('sensor.washing_machine_washer_job_state') == 'finish'%}
                  mdi:flag-checkered
                {% endif %}
              badge_color: |-
                {% if states('sensor.washing_machine_washer_job_state') == 'none' %} 
                  #5bc779 
                {% elif states('sensor.washing_machine_washer_job_state') == 'wash' %}
                  #697fff 
                {% elif states('sensor.washing_machine_washer_job_state') == 'rinse' %}
                  #5eccff 
                {% elif states('sensor.washing_machine_washer_job_state') == 'spin' %}
                  #999c9e 
                {% elif states('sensor.washing_machine_washer_job_state') == 'drying' %}
                  #ff7b2e 
                {% elif states('sensor.washing_machine_washer_job_state') == 'weightSensing'%} 
                  #be9cff 
                {% elif states('sensor.washing_machine_washer_job_state') == 'finish'%}
                  #5bc779
                {% endif %}
              icon_color: |-
                {% if states('sensor.washing_machine_washer_job_state') == 'none' %} 
                  #5bc779 
                {% elif states('sensor.washing_machine_washer_job_state') == 'wash' %}
                  #697fff 
                {% elif states('sensor.washing_machine_washer_job_state') == 'rinse' %}
                  #5eccff 
                {% elif states('sensor.washing_machine_washer_job_state') == 'spin' %}
                  #999c9e 
                {% elif states('sensor.washing_machine_washer_job_state') == 'drying' %}
                  #ff7b2e 
                {% elif states('sensor.washing_machine_washer_job_state') == 'weightSensing'%} 
                  #be9cff 
                {% elif states('sensor.washing_machine_washer_job_state') == 'finish'%}
                  #5bc779
                {% endif %}
              entity: sensor.washing_machine_washer_job_state
              tap_action:
                action: more-info
              hold_action:
                action: none
              double_tap_action:
                action: none
              multiline_secondary: false
              card_mod:
                style: |
                  ha-state-icon {
                    {% if states('sensor.washing_machine_washer_job_state') == 'none' %}
                      
                    {% else %}
                      animation: shake 400ms ease-in-out infinite, drum 2s ease infinite; transform-origin: 50% 110%;
                    {% endif %}
                  }
                  @keyframes shake {
                    0%, 100% { transform: translate(0, 0) rotate(0); }
                    20%  { transform: translate(0.4px, -0.4px) rotate(-4deg); }
                    40%  { transform: translate(-0.4px, 0.4px) rotate(4deg); }
                    60%  { transform: translate(0.4px, 0.4px) rotate(-4deg); }
                    80%  { transform: translate(-0.4px, -0.4px) rotate(4deg); }
                  }
                  @keyframes drum {
                    50%  { clip-path: polygon(0 0, 0 100%, 35% 100%, 32% 45%, 60% 34%, 71% 56%, 65% 74%, 47% 79%, 32% 69%, 35% 100%, 100% 100%, 100% 0); }
                  }
4 Likes

thanks going to play with this one :slight_smile:

Thank you once again for your help!!

1 Like

I have seen that you can use code within the style.

is it possible to do something likeā€¦

                    card_mod:
                      style: |
                        ha-card::after {
                          content: 'Dishwasher';
                          position: absolute;
                          font-size: 9px;
                          top: -8px;
                          {% if content == '' %}
                          inset: 0 auto 50% 40%;
                          {% else %}
                          inset: 0 auto 50% 50%;
                          {% endif%}
                          transform: translate(-50%, -50%);
                        }

The 40% for chips without content (just icon) and 50% with both works perfect for me.
Sofar it does not work but I am not sure if I address the content of the card the right way.

You can do this:

                    card_mod:
                      style: |
                        ha-card::after {
                          content: 'Dishwasher';
                          position: absolute;
                          font-size: 9px;
                          top: -8px;
                          transform: translate(-50%, -50%);
                          {% if config.content == '' %}
                            inset: 0 auto 50% 40%;
                          {% else %}
                            inset: 0 auto 50% 50%;
                          {% endif %}
                        }

Did you try the other solution i gave?

              card_mod:
                style: |
                  ha-card::after {
                    content: 'Dishwasher Long Test Name';
                    position: absolute;
                    font-size: 9px;     
                    width: max-content;
                    left: 50%;     
                    transform: translate(-50%, -80%);
                  }

Seems to work perfectly for me whether there is an icon or not, or whatever the text length is.

Pulsing animation on a Mushroom Chip card when the input_boolean is on/true.

type: custom:mushroom-chips-card
chips:
  - type: template
    entity: input_boolean.away_lighting
    icon: mdi:lightbulb-auto
    content: Away
    tap_action:
      action: toggle
    icon_color: |-
      {% if is_state('input_boolean.away_lighting', 'on') %}
        amber
      {% else %}
        grey
      {% endif %}  
    card_mod:
      style: |
        .content {
          {{ 'animation: pulse 3s linear infinite;' if is_state('input_boolean.away_lighting', 'on') }}
        } 

@andy.schmid Mine is also showing like thatā€¦ did you solve the issue?
@rhysb Can you help please?

Yes I have seen your code.
It works but the other code works better on my computer/mobile.

Your code setup:
Code1

my code setup:
Code2