🔹 Card-mod - Super-charge your themes!

Hi everyone, can someone tell me how I can change the color and background color of the Icons and the buttons? It’s thermostat-card, HVAC modes.
Thank you very much in advance

Frank

Ask in the main card-mod thread, this one is for themes

Hi,

I am quite satisfied with my theme now. I have background colors change based on entity states and it works flawlessly.

BUT,

How can i keep my theme from conflicting with other themes? No matter which theme i select, my background mods still appear.

clear:
    modes:
        dark: {}
    ha-card-border-width: '0px'
    ha-card-border-radius: '33px'
    ha-card-background: 'rgba(150, 150, 165, 0.1)'
    mush-chip-height: '34px'
    mush-title-padding: '24px 16px 16px'
    mush-title-font-size: '20px'
    bar-card-border-radius: '8px'
    primary-color: 'rgba(189, 237, 142, 1.0)'
    mdc-theme-surface: 'rgba(43, 53, 60, 1.0)'
    ha-card-box-shadow: 'none'
    mush-card-secondary-color: 'rgba(150, 150, 150, 1.0)'
    mush-card-secondary-font-weight: '600'
    
  # Fonts
    primary-font-family: 'Roboto'
    paper-font-common-base_-_font-family: "var(--primary-font-family)"
    paper-font-common-code_-_font-family: "var(--primary-font-family)"
    paper-font-body1_-_font-family: "var(--primary-font-family)"
    paper-font-subhead_-_font-family: "var(--primary-font-family)"
    paper-font-headline_-_font-family: "var(--primary-font-family)"
    paper-font-caption_-_font-family: "var(--primary-font-family)"
    paper-font-title_-_font-family: "var(--primary-font-family)"
    ha-card-header-font-family: "var(--primary-font-family)"


  # Colors
  
  # Bubble Card variables test
    bubble-border-radius: "8px"
    bubble-main-background-color: "rgb(150,150,150)"
    bubble-secondary-background-color: "rgb(1,1,1)"
    bubble-pop-up-main-background-color: "rgba(1,1,1,0.5)"
    bubble-accent-color: "rgb(1,1,1)"
    bubble-icon-background-color: "rgb(5,8,1)"
    bubble-select-list-width: "200px"
    bubble-select-list-background-color: "rgb(1,1,1)"

      
  
    # Background image
    lovelace-background: 'center / cover no-repeat url("/local/media/misc/bg.jpg") fixed'

    card-mod-theme: clear

    card-mod-card: |
      
      ha-card {
        background: {% if is_state(config.entity, 'on') %} rgba(225, 225, 225, 1);
        --card-primary-color: black;
        --bsc-background: rgba(225, 225, 225, 1);
        --bsc-color: black;
        --bsc-slider-color: #f9d2b0; /* Customize the slider color */
        --bsc-primary-text-color: black;
        --bsc-icon-color: black;
        {% elif is_state(config.entity, 'home') %} rgba(225, 225, 225, 1);
        --card-primary-color: black;
        --bsc-background: rgba(225, 225, 225, 1);
        --bsc-color: black;
        {% elif is_state(config.entity, 'open') %} rgba(225, 225, 225, 1);
        --card-primary-color: black;
        --bsc-background: rgba(225, 225, 225, 1);
        --bsc-color: black;
        {% elif is_state(config.entity, 'heating') %} rgba(225, 225, 225, 1);
        --card-primary-color: black;
        --bsc-background: rgba(225, 225, 225, 1);
        --bsc-color: black;
        {% elif is_state(config.entity, 'playing') %} rgba(225, 225, 225, 1);
        --card-primary-color: black;
        --bsc-background: rgba(225, 225, 225, 1);
        --bsc-color: black;
        {% endif %}
      }
    
      ha-card {
        background: {% if config.entity.startswith('person.') and is_state(config.entity, 'home') %} rgba(150, 150, 165, 0.1);
        --card-primary-color: white;
        --bsc-background: rgba(150, 150, 165, 0.1);
        --bsc-color: white;
        {% endif %}
      }
    
      ha-card {
        background: {% if config.entity.startswith('climate.') and is_state_attr(config.entity, 'hvac_action', 'heating') %} rgba(225, 225, 225, 1);
        --card-primary-color: black;
        --bsc-background: rgba(225, 225, 225, 1);
        --bsc-color: black;
        {% endif %}
      }
      
      stack-in-card {
        --stack-in-card-background: rgba(255, 255, 255, 0.9); /* Adjust the background for the stack */
        --stack-in-card-border-radius: 12px; /* Optional border radius */
        --stack-in-card-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Optional shadow */
        --bsc-background: inherit; /* Inherit from the stack if needed */
        --bsc-color: inherit;
        --bsc-slider-color: inherit;
      }

Hi! So far while using card-mod “inline” I was able to figure out most of the things using Ildar’s magnificent post!
Now I started to my stuff to themes (mainly for deduplication purposes) and seems like I still missing a couple of things…

Question #1: I have a bunch of cards that shows batteries info:

type: entities
state_color: true
entities:
  - entity: sensor....
    name: ...
  - entity: sensor....
    name: ...
...
grid_options:
  columns: 12
  rows: auto
title: Phones/Tablets
card_mod:
  style:
    hui-sensor-entity-row:
      $:
        hui-generic-entity-row:
          $: |
            state-badge {
                transform: rotate(90deg);
                height: 32px;
                line-height: 32px;
            }   
    .: |
      ha-card .card-header .name {        
        font-size: 20px;
        line-height: 24px;
      }  

Using card-mod here to rotate icon and reduce margins. To move this to themes, I assigned class: my-batteries to the cards.

Reducing margins is easy in this case. In themes.yaml, I added:

card-mod-card-yaml: |
  .: |
...
      ha-card.my-batteries .card-header .name {        
        font-size: 20px;
        line-height: 24px;
      }  
...

and it works!

But how to specify that icon rotation should only be applied to .my-batteries cards…
In themes.yaml, tried:

card-mod-card-yaml: |
...
  .my-batteries: |
    hui-sensor-entity-row: |
      $: |
        hui-generic-entity-row: |
          $: |
            state-badge {
                transform: rotate(90deg);
                height: 32px;
                line-height: 32px;
            }   
...

but no luck.

Any Idea what do I do wrong?

Update on question #1: seems I found how to style non-shadow-root parts of whole card and its entities…
Giving whole card has class .my-batteries and one of the entities - .my-battery,
I can write following:

      style: 
        .: |
          :host(.my-battery) {
            color: blue;
          }  
...
  style:
    .: |
      :host .my-batteries {
        color: red;
      }

To make whole card’s text red and my-battery’s entity - blue. But how can I get inside shadow-roots???
Tried this:

    ":host .my-batteries": 
      hui-sensor-entity-row:
        $:
          hui-generic-entity-row:
            $: |
              state-badge {
                  transform: rotate(90deg);
                  height: 32px;
                  line-height: 32px;
              } 

No luck :frowning:

Question #2: I also have several tile cards I want to change icon color based on the value. I use template sensor to calculate and store color in the attribute so the card looks like this:

type: tile
entity: sensor.co2_max
grid_options:
  columns: 3
  rows: 2
vertical: true
hide_state: false
show_entity_picture: false
name: " "
card_mod:
  style: |
    ha-tile-icon {
      --tile-color: {{ state_attr(config.entity, "zone_color") }};
    }

Again as part of moving to theme, added a class to card and removed inline CSS:

type: tile
entity: sensor.co2_max
grid_options:
  columns: 3
  rows: 2
vertical: true
hide_state: false
show_entity_picture: false
name: " "
card_mod:
  class: my-zone-color

adding them to theme.yaml:

card-mod-card-yaml: |
...
    .my-zone-color ha-tile-icon {
      --tile-color: {{ state_attr(config.entity, "zone_color") }};
    }
...

Well, coloring works as expected, but started to get 100s of messages in the logs:

2025-02-26 16:16:03.673 ERROR (MainThread) [homeassistant.helpers.template] Template variable error: 'dict object' has no attribute 'entity' when rendering 'ha-card.my-heading .container .content ha-icon-next {
   display: none;
}
.my-zone-color ha-tile-icon {
  --tile-color: {{ state_attr(config.entity, "zone_color") }};
}'

So it does seem card-mod tries to apply jinja template to each and every ha-card I have on the page, including static headers that does not have config.entity set at all!
Any ways to limit jinja template calculations only for class:my-zone-color cards?

as a heads-up: HA 2025.3 (now in beta) seems to have borked this option to scroll.
havent completely explored all options yet, but is seems there was a change card-mod does not yet support

Ive opened a discussion FR: add option to swipe/scroll view badges horizontally · home-assistant/frontend · Discussion #24415 · GitHub to get the swipe option in Core…

havent been able to touch the hui-view-header yet, all other badges mods stil work though

1 Like

Hi,

I have a simple mushroom card for the control of a garage.
I want the card to blink when state is opening or closing but I don’t get it to work.

I tried with adding but this does not change anything.

  --animation: blink 1s linear infinite; 
@keyframes blink { 50% {opacity: 0;} }

Is anyone able to support? Addionally I want to have the german labels instead of the state names on the card. So in case of state open I want to show “offen”.


type: custom:mushroom-entity-card
entity: sensor.garage_tr_left_status
secondary_info: state
tap_action:
  action: call-service
  confirmation:
    text: Wirklich ausfĂźhren?
  service: switch.turn_on
  service_data:
    entity_id: switch.shellyplus2pm_10061cc9f634_switch_0
name: Garage links
primary_info: name
icon_color: white
card_mod:
  style: |
    ha-card {
          text-align: left;
          height: 70px !important;
        {% set state=states('sensor.garage_tr_left_status') %}
           {% if state == 'open' %}
            --ha-card-background: red;
            offen
           {% elif state == 'opending' %}
            --ha-card-background: orange;
           {% elif state == 'closing' %}
            --ha-card-background: orange;
            --animation: blink 1s linear infinite; 
            @keyframes blink { 50% {opacity: 0;} }
           {% elif state == 'closed' %}
            --ha-card-background: green;
            geschlossen
           {% endif %}
        }

Let’s say one wants to make all text of all cards red via card-mod theme. Easy:

...
  card-mod-card-yaml: |
    ".": |
      ha-card {
        color: red;
      }
...

Now, limit this only to cards with class “my-class”. No problem:

In card(s):

...
  card-mod:
    class: my-class
...

In theme:

  card-mod-card-yaml: |
...
    ".": |
      ha-card.my-class {
        color: red;
      }
...

Next level: make only names green. Requires interaction with shadow-roots, but still possible - for all cards it can be done by adding following in theme.yaml:

  card-mod-card-yaml: |
...
      hui-sensor-entity-row:
        $:
          hui-generic-entity-row:
            $: |
              .info.pointer {
                color: green;
              } 
...

Now - real challenge: try to restrict that only to “my-class” cards.

Following:

  card-mod-card-yaml: |
...
    ".my-class":
      hui-sensor-entity-row:
        $:
          hui-generic-entity-row:
            $: |
              .info.pointer {
                color: green;
              } 
...

doesn’t work, probably because it tries to look for element having “my-class” among ha-card element’s children and fails to find any, because it is ha-card itself has this class.

Also tried following selectors, but w/o any success:

".:is(.my-class)":
"..my-class": (note two dots here - try to say "'this' with class 'my-class'")

So, is there any way to achieve this?

To get a shadow root inside a particular class: described here:
main card-mid thread - 1st post - link at the bottom - themes - shadow root in a class

Somehow missed that… Shame on me!

In fact, also started to consider usage of variables, but it looks so cumbersome - was hoping it should be a better way.

Wonder is there any architectural limitation that prevents adding “root{something}”-like selector (e.g. “root.my-class”) to the “…-yaml” entries?

BTW, here is another thing I did while tried to make it work. if both is true:

  • you want to apply same change to all entities in entity card (cannot use config.entity here unfortunately)
  • you are ok to apply your class to each entity

you can do this:

Card:

type: entities
entities:
  - entity: ...
    card_mod:
      class: my-class
  - entity: ...
    card_mod:
      class: my-class
  - entity: ...
    card_mod:
      class: my-class

Theme:

  card-mod-card-yaml: |
...
    hui-sensor-entity-row.my-class:
      $:
        hui-generic-entity-row:
          $: |
            .info.pointer {
              color: green;
            }  
...

But you can use config.entities[i].entity.

Wait, do you mean “i” as a constant? Like: config.entities[0].entity?
Or is there still a way to have it updated for each entity?

Use it on “for”.

Can hardly imaging how to apply this in themes. Any snippet?

No PC, no snippet. You said that you want to use some card-level style but cannot use “config.entity”. Means - you need to analyze some entity: if true - then some style. So enumerate all entities inside a card (ie in config.entities) in “for” and style.

Looking at card-mod.ts sources:

...
  private async _connect() {
    const styles = this._fixed_styles ?? {};

    const styleChildren = {};
    let thisStyle = "";
    let hasChildren = false;
    const parent = this.parentElement || this.parentNode;

    this._debug("(Re)connecting", this);

    // Go through each path in the styles
    for (const [key, value] of Object.entries(styles)) {
      if (key === ".") {
        if (typeof value === "string") thisStyle = value;
        else this._debug("Style of '.' must be a string: ", value);
      } else {
        hasChildren = true;
        styleChildren[key] = this._style_child(key, value).catch((e) => {
          if (e.message == "NoElements") {
            if (this.debug) {
              console.groupCollapsed("card-mod found no elements");
              console.info(`Looked for ${key}`);
              console.info(this);
              console.groupEnd();
            }
            return;
          }
          throw e;
        });
      }
    }
...

think it should be possible to add another condition in between.

Like this naive implementation for single class support:

      // adding root element's class handling ("..{className}" notation)
      else if (key.startsWith('..')) {
          let className = key.substr(2, key.length-2);
          const parent = this.parentElement || this.parentNode;
          if (parent.classList.contains(className)) {
              // root element contains class -> continue recursive magic for value
          } else {
              // root element does not contain class -> ignore value;
              continue;
          }
      // end classes handling 

Not quite yet understand how to trigger recursive magic continuation :slight_smile: . Any help?

Seems like you may already have a solution, but you can also play with some advanced CSS features too. This doesn’t work in every browser (yet - check compatibility of :host-context()). The code below isn’t specific to your situation, but shows the use of :host-context(). It took me a few minutes to figure out how it worked, but basically it’s similar to :has() where you can specify a selector that will select a shadow root where the selector matches.

So, in this example, I can style ha-icon where, as a parent before the shadow root for ha-svg-icon (the thing I need to change), it matches paper-tab.iron-selected (the selected header tab on a dashboard), and then I can style the ha-svg-icon that’s inside the shadow root.

  card-mod-root-yaml: |
    "ha-icon$": |
      :host-context(paper-tab.iron-selected) ha-svg-icon{
        fill:var(--theme-icon-header-color);
      }

FYI, this lets me customize the fill colour for the icon in the selected header tab and ensures that when you change tabs, it changes the colour back to normal (if you didn’t do this, it would permanently style the first selected tab and it would remain this colour even after changing tabs).

Nice! I like it much more than CSS variables hack.
Though being able to use classes directly is much-much more cleaner.

And it seems I nailed it after all :slight_smile:!

Took slightly different approach than initially planned - now I’m looping through styles dictionary separately to find any starting with “…”.
For any key/value found:

  • Delete them from styles variable
  • If parent (or host) contains all styles from key - just merge value into styles, otherwise - discard value

So, basically, if I have this entry in themes.yaml

  card-mod-row-yaml:
  ...
    ..my-class.my-class2:
      hui-generic-entity-row:
        $: |
          .info.pointer {    
            font-size: 120%; 
          }   
  ....

for entities with “class: my-class my-class2” it becomes:

        hui-generic-entity-row:
          $: |
            .info.pointer {    
              font-size: 120%; 
            }   

for all other entities - it completely discarded.

Here is my code (put it after this._debug(“(Re)connecting”, this); line):

    // adding root element's class handling ("..{className1}.{className2}.{classNameN}" notation)
    let parentEl = this.parentElement || (this.parentNode ? this.parentNode['host'] : null);
    for (const [key, value] of Object.entries(styles)) {
      if (key.startsWith('..')) {
        delete styles[key];
        if (parentEl) {
          let classNames = key.substring(2, key.length).split('.');
          if (classNames.every(cl => parentEl.classList.contains(cl))) {
            // root element contains all classes -> merge value into styles
            if (typeof value === "string")
              this._debug("If style of '..' is a string - you can safely put it under '.': ", value);
              // or we can do it ourselves:
              // merge_deep(styles, {'.' : value});
            else {
              merge_deep(styles, value);
            }
          } else {
              // root element does not contain all classes -> ignore value;
              continue;
          }
        } else {
          console.warn("Cannot find parent element!");
        }
      }  
    }  

However, is this not parsing styles only once, so if the classes change dynamically, those styles aren’t re-evaluated at all. Might not be an issue all the time, but if you do have something that toggles classes, the styles wouldn’t change because the class piece isn’t part of the rendered CSS rule itself.

True. Consider the main purpose of this change is to allow to move “static” part of the CSSes from the cards to the theme to removing duplication in the first place.

Obviously, it is not intended to cover all the use-cases, especially such complicated as dynamic class chang. For those - different solution still might be needed.

Have you had any progress with this?