A different take on designing a Lovelace UI

Thanks for trying to help. Much appreciated.
Yes, I already caught that. It didn’t do the trick. I am also having problems with variables. I just figured out that variables need to be created in the tesla template (eq: light template), where I created circle_input and circle_input_unit. So now I created a charge_power variable too which is recalled in tesla_circle template. But it’s still not working.
Now my tesla (light) template is:

tesla:
  template:
    - base
    - tesla_circle
  double_tap_action:
    action: fire-dom-event
    browser_mod:
      service: browser_mod.popup
      data:
        title: >
          [[[
            return !entity || entity.attributes.friendly_name;
          ]]]
        card_mod:
          style:
            #popup header
            .:
        content:
          type: entities
          card_mod:
            style: |
              #states {
                padding-top: 0.5em;
              }
  variables:
    circle_input: >
      [[[
        if (entity) {
          let battery = entity.attributes.battery_level
            ? Math.round(entity.attributes.battery_level * 100)
            : null;
          if (battery !== null) {
            return battery === 0 && entity.state !== 'off'
              ? 1
              : battery;
          }
        }
      ]]]
    charge_power: >
      [[[
        if (entity && entity.attributes.charge_power) {
          return Math.round(entity.attributes.battery_level * 100);
        } else {
          return null;
        }
      ]]]

    circle_input_unit: '%'

and the tesla_circle (circle) template:

tesla_circle:
  styles:
    card:
      - --c-stroke-color-on: '#b0b0b0'
      - --c-stroke-color-off: none
      - --c-fill-color-on: '68f213'
      - --c-fill-color-off: rgba(255,255,255,0.04)
      - --c-stroke-width: 2.3
      - --c-stroke-width-dragging: 4
      - --c-font-color: '#97989c'
      - --c-font-size: 14px
      - --c-unit-font-size: 10.5px
      - --c-font-weight: 700
      - --c-letter-spacing: -0.02rem
    custom_fields:
      circle:
        - display: initial
        - width: 88%
        - margin: -3% 2% 0 0
        - justify-self: end
        - opacity: 1
  custom_fields:
    circle: >
      [[[
            let r = 22.1,
                c = r * 2 * Math.PI,
                tspan = '<tspan dx=".2" dy="-.4">',
                charge = variable.charge_power,
                input = variables.circle_input || ' ',
                unit = variables.circle_input_unit || ' ';


                return `
                  <svg viewBox="0 0 50 50">
                    <style>
                      circle {
                        transform: rotate(-90deg);
                        transform-origin: 50% 50%;
                        stroke-dasharray: ${c};
                        stroke-dashoffset: ${typeof input === 'number' && c - input / 100 * c};
                        stroke-width: var(--c-stroke-width);
                        stroke: ${charge > 0 ? 'var(--c-fill-color-on)' : 'var(--c-fill-color-off)'};
                        fill: ${charge > 0 ? 'var(--c-fill-color-on)' : 'var(--c-fill-color-off)'};

                      }
                      text {
                        font-size: var(--c-font-size);
                        font-weight: var(--c-font-weight);
                        letter-spacing: var(--c-letter-spacing);
                        fill: ${charge > 0 ? 'var(--c-fill-color-on)' : 'var(--c-fill-color-off)'};
                      }
                      tspan {
                        font-size: var(--c-unit-font-size);
                      }
                      #circle_value, tspan {
                        text-anchor: middle;
                        dominant-baseline: central;
                      }
                    </style>
                    <circle id="circle_stroke" cx="25" cy="25" r="${r}"/>
                    <text id="circle_value" x="50%" y="52%">${input}${tspan}${unit}</tspan></text>
                  </svg>
                `;
      ]]]

But that still doesn’t work… Argh!!!

Edit: And I just changed this line:

charge = variables.charge_power || ' ',

It was missing an s to variable and the || ’ ',

You might be able to get some insight from the way that I set up my washer & dryer cards.

In that post, I’m also using a template sensor and I have an added variable being used in the circle template.

I’ll check it out thanks. I had seen them but didn’t think it was related. Nice buttons btw.
Hoping Mason can figure it out. :sweat_smile:

Thanks for this @Laffer . I’ve just installed it. Every entity state seems to work as it should.

Right now Plex is online and I see sensor.recently_added_offline as Passive.

I see the Posters for Plex movies on my dashboard.

But when I play my spotify or Apple TV, I get an error:

Screenshot 2023-03-09 at 10.49.30 PM

sensor.recently_added_offline is still Passive, conditional media is set to Spotify or Apple TV depending on what I play.

When I stop playing Spotify, Plex recently added shows up again as it should.

I triple checked all the code.
It seems like it can’t find the spotify album picture? I do see it in the spotify state though.

I just wanted to know if you’ve changed anything since you posted this?

Thanks

I’m glad you appreciate it.

I’m thinking this might be because your conditional_media ‘select’ template sensor is not updated with the entity that is playing.
Please confirm if this is correct or not.
In any case, it might help if you show some of your code.
I’m curious as to what 'states[variables.entity_picture].attributes’ is doing for you here. My JavaScript logic uses ‘states[variables.entity_picture].attributes.entity_picture’.

I’ve updated my repo on github with some small changes I made recently, but I’m not confident that it will help with this.
(see ‘button_card_templates.yaml’)

conditional_media is def updated right away. Just tried it again by playing Spotify.
Hmmm right now it’s set to Recently Added (Offline) and recently_added_offline is Active but recently_added_mix is also online. I thought recently_added_offline was Active only when recently_added_mix was offline. Maybe I didn’t get that quite right. Hmmm.

Anyway. I also grabbed your code from your repo, so it’s the latest. That only thing I didn’t copy over was the color changes that you added at the end.

Just to make things harder, I’m not using the same names as you. :sweat_smile:
sensor.recently_added_mix => sensor.plex_recently_added
sensor.recently_added_offline => sensor.plex_movies_offline

Here’s my code. conditional_media template

conditional_media:
  aspect_ratio: 1000/996
  template:
    - base
    - base_media
    - icon_play_pause
  variables:
    i: >
      [[[
        if (entity) {
            let data = entity.attributes.data;
            return data === undefined || Math.floor(Math.random() * (data.length - 1)) + 1;
        }
      ]]]
  state_display: >
    [[[
        let horizontalStack = this.getRootNode().host,
            swipeCard = horizontalStack.getRootNode().host,
            gridTitle = swipeCard.getRootNode().querySelector("h1");
        swipeCard.swiper.on("slideChange", () => {
            if (swipeCard.swiper.realIndex === 0) {
                gridTitle.textContent = "Media";
            }
            else if (swipeCard.swiper.realIndex === 1) {
                gridTitle.textContent = "Players";
            }
        });
        if (entity) {
          let elt = this.shadowRoot,
              await = setTimeout(marquee, 100),
              data = entity.attributes.data,
              title = entity.attributes.title,
              poster = entity.attributes.poster,
              fanart = entity.attributes.fanart,
              s_e = entity.attributes.number,
              media_artist = entity.attributes.media_artist,
              media_title = entity.attributes.media_title;
            if (entity.state === "Active") {
              var number = s_e === undefined && title !== undefined
                  ? ' '
                  : '· ' + s_e,
              output = `${title} ${number}`;
            }
            else if (data !== undefined) {
              var number = data[variables.i].number === undefined && data[variables.i].aired !== undefined
                  ? `(${data[variables.i].aired.split("-")[0]})`
                  : data[variables.i].number === undefined && data[variables.i].aired === undefined
                      ? ' '
                      : '· ' + data[variables.i].number,
              output = `${data[variables.i].title} ${number}`;
            } else {
              var output = media_artist === undefined && media_title !== undefined
                  ? media_title
                  : media_title === undefined && media_artist !== undefined
                      ? media_artist
                      : media_title !== undefined && media_artist !== undefined
                          ? `${media_artist} - ${media_title}`
                          : variables.translate_idle;
            }
            function marquee() {
                let state = elt.getElementById("state"),
                    container = elt.getElementById("container");
                if (state && container) {
                    state.innerHTML = output;
                    let ro = new ResizeObserver(entries => {
                        let spacer = "&nbsp;".repeat(3),
                            s = entries[0],
                            c = entries[1],
                            r = s && s.contentRect &&
                                c && c.contentRect &&
                                s.contentRect.width !== 0 &&
                                c.contentRect.width !== 0;
                        if (r && s.contentRect.width < c.contentRect.width) {
                            state.classList.remove("marquee");
                        }
                        else if (r && s.contentRect.width >= c.contentRect.width) {
                            state.innerHTML = `${output} ${spacer} ${output} ${spacer}&nbsp;`;
                            state.classList.add("marquee");
                        }
                    });
                    ro.observe(state);
                    ro.observe(container);
                }
            }
            return output;
        }
        return variables.translate_unknown;
      ]]]
  tap_action:
    action: call-service
    service: media_player.media_play_pause
    service_data:
      entity_id: >
        [[[ return variables.entity_id; ]]]
  styles:
    grid:
      - gap: 0.65%
    name:
      - padding: 0.2vw
      - margin: -0.2vw
    state:
      - padding-bottom: 5.25%
      - max-width: unset
      - overflow: visible
    card:
      - padding: 5.75% 5.25% 0 5.75%
      - border-radius: calc(var(--button-card-border-radius) / 2)
      - background: rgba(115, 115, 115, 0.2) center center/cover no-repeat
      - background-image: &media_background_image >
          [[[
              if (entity) {
                if (entity.state === "Active") {
                  return entity.attributes.data !== undefined
                    ? `url("${entity.attributes.fanart}"), url("${entity.attributes.poster}")`
                    : `url("${states[variables.entity_picture].attributes.entity_picture}")`;
                }
                else {
                  return entity.attributes.data !== undefined
                    ? `url("${entity.attributes.data[variables.i].fanart}"), url("${entity.attributes.data[variables.i].poster}")`
                    : `url("${states[variables.entity_picture].attributes.entity_picture}")`;
                }
              }
            ]]]
      - color: >
          [[[
            return entity === undefined
                ? '#97989c'
                : '#efefef';
          ]]]
      - text-shadow: >
          [[[
            return entity === undefined
                ? 'none'
                : '1px 1px 5px rgba(18, 22, 23, 0.9)';
          ]]]
    custom_fields:
      icon:
        - width: 30%
        - fill: >
            [[[
              return entity && variables.media_on
                  ? 'rgba(255, 255, 255, 0.8)'
                  : '#9da0a2';
            ]]]
      blur_overlay:
        - display: block
        - position: absolute
        - width: 103.1%
        - height: 103.1%
        - filter: var(--blur-intensity)
        - clip-path: >
            inset(74.5% 1.45% 1.45% 1.45% round 0 0 calc(var(--button-card-border-radius) / 2) calc(var(--button-card-border-radius) / 2))
        - background: center center/cover no-repeat
        - background-image: *media_background_image
        - left: -1.5%
        - bottom: -1.6%
  custom_fields:
    blur_overlay: >
      [[[
        setTimeout(() => {
            let elt = this.shadowRoot,
                card = elt.getElementById('card'),
                container = elt.getElementById('container'),
                blur_overlay = elt.getElementById('blur_overlay');
            if (elt && card && container && blur_overlay) {
                card.insertBefore(blur_overlay, container);
            }
          }, 0);
        return ' ';
      ]]]

sensors:

template:
  select:
    - name: conditional_media
      state: >
        {% set recently_added = 'Plex Movies' %}
        {% set recently_added_backup = 'Plex Movies (Offline)' %}
        {% set paused_timeout_minutes = 15 %}
        {% set media_players = [
          states.media_player.control_room,
          states.media_player.fire_tv_192_168_1_83,
          states.media_player.spotify_cheznath,
          states.media_player.spotify_boutinmusic ] %}
        
        {% macro media(state) %}
        {% set state = media_players | selectattr('state','eq',state) | list %}
        {% set last_changed = recently_added if state | length == 0 else state | map(attribute='last_changed') | list | max %}
          {{ state | selectattr('last_changed','eq', last_changed) | map(attribute='name') | list | join }}
        {% endmacro %}

        {% set recently_added = recently_added_backup if is_state('sensor.plex_movies_offline','Active') else recently_added %}
        
        {% set playing = media_players | selectattr('state','eq','playing') | list %}
        {% set timeout_playing = False if playing | length == 0 else
          (as_timestamp(now()) - as_timestamp(playing | map(attribute='last_changed') | list | max)) < paused_timeout_minutes * 60 %}
        
        {% set paused = media_players | selectattr('state','eq','paused') | list %}
        {% set timeout_paused = False if paused | length == 0 else
          (as_timestamp(now()) - as_timestamp(paused | map(attribute='last_changed') | list | max)) < paused_timeout_minutes * 60 %}
        
        {% if playing %}
          {{ media('playing') if timeout_playing else media('paused') if timeout_paused else media('playing') }}
        {% elif paused %}
          {{ media('paused') if timeout_paused else recently_added }}
        {% else %}
          {{ recently_added }}
        {% endif %}
      options: >
        {% set recently_added = ['Plex Movies'] %}
        {% set recently_added_backup = ['Plex Movies (Offline)'] %}
        {% set media_players = [
          states.media_player.control_room,
          states.media_player.fire_tv_192_168_1_83,
          states.media_player.spotify_cheznath,
          states.media_player.spotify_boutinmusic ] %}
        {{ recently_added + recently_added_backup + media_players | map(attribute='name') | list }}
      select_option:
        service: select.select_option
        target:
          entity_id: select.conditional_media
        data:
          option: >
            {{ option }}


  sensor:
  
    # SENSOR - PLEX RECENTLY ADDED (BACKUP)
    - unique_id: recently_added_backup
      name: 'Plex Movies (Offline)'
      state: >
        {% if not is_state('sensor.plex_recently_added', 'Online') %}
          Active
        {% else %}
          Passive
        {# Waiting for 'Plex Movies' to fail #}
        {% endif %}
      attributes:
        data: >
          {% if this.state not in ['unavailable','undefined','unknown','none','null','0'] %}
            {% set data = states('input_text.backup_plex_recently_added') %}
            {% set return = namespace(state=[]) %}
            {% set items = data.split("|") %}
            {% for item in items %}
              {% set object = item.split(":") %}
              {% set return.state = return.state + [{object[0]:object[1]}] %}
            {% endfor %}
            {{ return.state }}
          {% endif %}
        title: >
          {% if this.state not in ['unavailable','undefined','unknown','none','null','0'] %}
            {% set data = this.attributes.data %}
            {% set year = data[0].aired.split('-')[0] %}
            {% if data | count == 4 %}
              {% set title = data[1].title + ' (' + year + ')' %}
            {% else %}
              {% set title = data[1].title + ' · ' + data[2].number %}
            {% endif %}
            {{ title }}
          {% endif %}
        poster: >
          {% if this.state not in ['unavailable','undefined','unknown','none','null','0'] %}
            {% set data = this.attributes.data %}
            {% if data | count == 4 %}
              {% set poster = data[3].poster %}
            {% else %}
              {% set poster = data[4].poster %}
            {% endif %}
            {{ poster }}
          {% endif %}
        fanart: >
          {% if this.state not in ['unavailable','undefined','unknown','none','null','0'] %}
            {% set data = this.attributes.data %}
            {% if data | count == 4 %}
              {% set fanart = data[2].fanart %}
            {% else %}
              {% set fanart = data[3].fanart %}
            {% endif %}
            {{ fanart }}
          {% endif %}

ui-lovelace.yaml

cards:
              - type: horizontal-stack
                cards:

                  - type: conditional
                    conditions:
                      - entity: select.conditional_media
                        state_not: Plex Movies (Offline)
                        
                      - entity: select.conditional_media
                        state_not: Living Room Apple TV

                      - entity: select.conditional_media
                        state_not: Spotify Nath

                      - entity: select.conditional_media
                        state_not: Spotify Paul

                      - entity: select.conditional_media
                        state_not: Fire TV
                    card:
                      type: custom:button-card
                      entity: sensor.plex_recently_added
                      name: Plex Movies
                      double_tap_action:
                        !include popup/plex.yaml
                      template:
                        - conditional_media
                        - icon_plex

                  - type: conditional
                    conditions:
                      - entity: select.conditional_media
                        state: Plex Movies (Offline)
                    card:
                      type: custom:button-card
                      entity: sensor.plex_movies_offline
                      name: Plex Movies (Offline)
                      double_tap_action:
                        !include popup/plex.yaml
                      template:
                        - conditional_media
                        - icon_plex
                    
                  - type: conditional
                    conditions:
                      - entity: select.conditional_media
                        state: Living Room Apple TV
                    card:
                      type: custom:button-card
                      entity: media_player.control_room
                      triggers_update: sensor.youtube_watching
                      template:
                        - conditional_media
                        - icon_apple_tv
                        - progress_bar

                  - type: conditional
                    conditions:
                      - entity: select.conditional_media
                        state: Spotify Nath
                    card:
                      type: custom:button-card
                      entity: media_player.spotify_cheznath
                      template:
                        - conditional_media
                        - icon_spotify

Automation

- id: '1675415708652'
  alias: System - Plex Recently Added
  description: Sensor attribute backup
  trigger:
  - platform: state
    entity_id:
    - sensor.plex_recently_added
    to: Online
  condition:
  - condition: template
    value_template: '{{ trigger.to_state.state not in [trigger.from_state.state, ''cannot
      be reached'', ''unavailable'', ''undefined'',''unknown'',''none'',''null'']
      }}'
  action:
  - service: input_text.set_value
    data:
      value: "{% if not states('sensor.plex_recently_added') in ['unavailable','undefined','unknown','none','null','0']
        %}\n  {% set state = namespace(return='') %}\n  {% set data = state_attr('sensor.plex_recently_added','data')
        %}\n  {%- for value in data %}\n    {%- if not loop.first and value is defined
        and state.return == '' %}\n      {%- if not value.number is defined %}\n        {%
        set state.return = \n          \"aired:\" + value.aired + \"|\" +\n          \"title:\"
        + value.title  + \"|\" +\n          \"fanart:\" + value.fanart + \"|\" +\n
        \         \"poster:\" + value.poster\n        %}\n      {%- else %}\n        {%
        set state.return = \n          \"aired:\" + value.aired + \"|\" +\n          \"title:\"
        + value.title  + \"|\" +\n          \"number:\" + value.number  + \"|\" +\n
        \         \"fanart:\" + value.fanart + \"|\" +\n          \"poster:\" + value.poster\n
        \       %}\n      {%- endif %}\n    {%- endif %}\n  {%- endfor %}\n  {{ state.return
        }}\n{% endif %}"
    target:
      entity_id: input_text.backup_plex_recently_added
  mode: single

I think you’re using a variable that is not declared:

‘entity_picture’

  conditional_media:
    aspect_ratio: 1000/996
    template:
      - base
      - base_media
      - icon_play_pause
    variables:
      entity_picture: '[[[ return entity.entity_id; ]]]'
      i: >
        [[[
          if (entity) {
            let data = entity.attributes.data;
            return data === undefined || Math.floor(Math.random() * (data.length - 1)) + 1;
          }
        ]]]

Take a look at that first… I’ll have a closer look if that doesn’t solve anything.

1 Like

I will look at this soon and let you know what I find.

I don’t have Illustrator. Would you mind sharing the SVG?

That did the trick. Thanks you so much. I wonder why I needed that when I just copy pasted all of Matttia’s code. Thank you so much for your help. Much appreciated. :pray:t2:

1 Like

Thanks Mason. No worries… I got all the time in the world. :wink: :+1:t2:

Hi everyone, does anyone know how to enable the circle in off state, just like with the climate?
Wanted to keep it on for a sensor and display its Math Round.

Thanks in advance!

ok still not 100% on what you wanted the card to do but this is what i go

this is the inputs

this is what the card looks like, (you didn’t provide the icon code so i just used a random icon)
image

the value in the circle is the battery_level, the stroke-color of the circle changes from red when less than 10 to yellow when less than 60 to green when more than 60, or gray when the on_state is false

the on_state is true when the car_state (state of charging) is (charging, asleep or online)

the state displayed is the car_state

usage (change the icon template to yours)

- type: custom:button-card
  entity: sensor.template_tesla_combo_1
  name: Blanche
  template:
    - tesla
    - icon_cctv

tesla template code, located in button_card_templates/tesla.yaml

tesla:
  template:
    - base
  variables:
    state: >
      [[[
        if (entity) {
          return entity.attributes.car_state 
        }
      ]]]
    state_on: >
      [[[ 
        return ['charging','asleep', 'online'].indexOf(!entity || entity.attributes.car_state) !== -1; 
      ]]]
    circle_input:  >
      [[[
        return entity === undefined || entity.attributes.battery_level;
      ]]]
  state_display: >
    [[[
      if (entity) {
        return entity.attributes.car_state 
      }else{
        return variables.translate_unknown
      }
    ]]]
  custom_fields:
    circle: >
      [[[

          let input = variables.circle_input,
            radius = 20.5,
            circumference = radius * 2 * Math.PI,
            circle_stroke = variables.state_on ? entity.attributes.battery_level < 10 ? "red" : entity.attributes.battery_level < 60 ? "yellow" : "green" : "#b2b2b2",
            inner_text = entity.attributes.battery_level;
          return `
            <svg viewBox="0 0 50 50">
              <style>
                circle {
                  transform: rotate(-90deg);
                  transform-origin: 50% 50%;
                  stroke-dasharray: ${circumference};
                  stroke-dashoffset: ${circumference - input / 100 * circumference};
                }
                tspan {
                  font-size: 10px;
                }
              </style>
              <circle cx="25" cy="25" r="${radius}" stroke="${circle_stroke}" stroke-width="1.5" fill="none" />
              <text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${inner_text}%</text>
            </svg>
          `;
      ]]]
  styles:
    card:
      - --c-stroke-color-on: '#b0b0b0'
      - --c-stroke-color-off: none
      - --c-fill-color-on: none
      - --c-fill-color-off: rgba(255,255,255,0.04)
      - --c-stroke-width: 2.3
      - --c-stroke-width-dragging: 4
      - --c-font-color: '#97989c'
      - --c-font-size: 14px
      - --c-unit-font-size: 10.5px
      - --c-font-weight: 700
      - --c-letter-spacing: -0.02rem
    custom_fields:
      circle:
        - display: initial
        - width: 88%
        - margin: -3% 2% 0 0
        - justify-self: end
        - opacity: 1```
2 Likes

This code looks amazing as so well thought out. Very cool man, thank you so much to take the time to recreate it. Much appreciated.
But it still doesn’t work and I’m thinking it’s maybe because of my base.yaml

base:
  template:
    - settings
    - tilt
    - extra_styles
  variables:
    variables:
    state_on: >
      [[[ return ['on', 'home', 'cool', 'heat', 'playing', 'charging', 'online', 'unlocked'].indexOf(!entity || entity.state) !== -1; ]]]
    state: >
      [[[ return !entity || entity.state; ]]]
    entity_id: >
      [[[ return !entity || entity.entity_id; ]]]
    entity_picture: >
      [[[ return !entity || entity.attributes.entity_picture; ]]]
    timeout: >
      [[[ return !entity || Date.now() - Date.parse(entity.last_changed); ]]]
    is_youtube: >
      [[[
        let is_youtube = entity?.attributes?.app_id === 'com.google.ios.youtube',
            sensor = this?._config?.triggers_update,
            media_title = entity?.attributes?.media_title,
            watching_title = states[sensor]?.attributes?.title;
        if (is_youtube && media_title === watching_title) {
            return true;
        }
      ]]]
  aspect_ratio: 1/1
  show_state: true
  show_icon: false
  state_display: >
    [[[ if (variables.state === true) return variables.translate_unknown; ]]]
  tap_action:
    ui_sound_tablet: |
      [[[
        let screensaver = states[variables.entity_tablet] === undefined ||
            states[variables.entity_tablet].state;
        if (variables.state === 'off' && screensaver === 'off') {
            hass.callService('media_player', 'play_media', {
                entity_id: variables.entity_browser_mod,
                media_content_id: '/local/sound/on.m4a',
                media_content_type: 'music'
            });
        }
        if (variables.state_on && screensaver === 'off') {
            hass.callService('media_player', 'play_media', {
                entity_id: variables.entity_browser_mod,
                media_content_id: '/local/sound/off.m4a',
                media_content_type: 'music'
            });
        }
      ]]]
    card_bounce: |
      [[[
        // add animation
        if (this.getElementsByTagName("style").length === 0) {
            // phone condition
            let mq = window.matchMedia('(max-width: 800px)').matches;
            let style = document.createElement('style');
            style.innerHTML = `
                @keyframes card_bounce {
                    0%   { transform: scale(1); }
                    10%  { transform: scale(${ mq ? '0.92' : '0.94' }); }
                    25%  { transform: scale(1); }
                    30%  { transform: scale(${ mq ? '0.96' : '0.98' }); }
                    100% { transform: scale(1); }
                }
            `;
            this.appendChild(style);
        }
        // duration
        let duration = 800;
        // animate
        this.style.animation = `card_bounce ${duration}ms cubic-bezier(0.22, 1, 0.36, 1)`;
        // reset
        window.setTimeout(() => { this.style.animation = "none"; }, duration + 100)
      ]]]
    action: toggle
    haptic: medium
  double_tap_action:
    haptic: success
  hold_action:
    action: block
  styles:
    grid:
      - grid-template-areas: |
          "icon  circle"
          "n     n"
          "s     s"
      - grid-template-columns: repeat(2, 1fr)
      - grid-template-rows: auto repeat(2, min-content)
      - gap: 1.3%
      - align-items: start
      - will-change: transform
    name:
      - justify-self: start
      - line-height: 121%
    state:
      - justify-self: start
      - line-height: 115%
    card:
      - border-radius: var(--button-card-border-radius)
      - border-width: 0
      - -webkit-tap-highlight-color: rgba(0,0,0,0)
      - transition: none
      - --mdc-ripple-color: >
          [[[
            return variables.state_on
                ? 'rgb(0, 0, 0)'
                : '#97989c';
          ]]]
      - color: >
          [[[
            return variables.state_on
                ? '#4b5254'
                : '#97989c';
          ]]]
      - background-color: >
          [[[
            return variables.state_on
                ? 'rgba(255, 255, 255, 0.85)'
                : 'rgba(115, 115, 115, 0.25)';
          ]]]

I did comment out this line in your tesla template thinking it might help.

    state_on: >
      [[[ 
        return ['charging','asleep', 'online'].indexOf(!entity || entity.attributes.car_state) !== -1; 
      ]]]

Here is the error message:


Can we make the text and circle green when attribute charge_power > 0 ?

EDIT: I just noticed the red yellow green thing going on. That’s very cool too. Super cool. Man… so many ideas… how could I show that it’s charging but while also keeping that red yellow red thing going?
Maybe make the whole card go green instead of white?

Below the code of the sidebar. There are of course a lot of custom entities in it :smile:

@Brkie I have also shared the bottom card with the electricity overview.

Code from sidebar in template sensor
- sensor:
    - unique_id: sidebar_tablet3
      state: template
      attributes:
        time: >
          {% set hours = now().strftime('%H') %}
          {% set minutes = now().strftime('%M') %}
          <span class="time">
            {{ hours }}<span class="time-colon">:</span>{{ minutes }}
          </span>
        date: |
          <style="font-color:rgba(255, 255, 255, 0.6)">
          {% if strptime(states('sensor.date'), '%Y-%m-%d').day != null %}
          {% set days = ['Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'] %}
          {% set months = ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 
          'juli', 'augustus', 'september', 'oktober', 'november', 'december'] %}
            <b><p style="font-size:19px">{{- days[now().weekday()] }} {{ strptime(states('sensor.date'), '%Y-%m-%d').day }} {{ months[now().month-1] }}</p></b>
          {% endif %}
          </style>
        greet: |
          {{ states['sensor.greeting'].state + ' \U0001F44B' }}
        active: |
          <p style="font-size: 95%">
          <style="font-color: rgba(255, 255, 255, 0.6)">
          {% set lights = [
            states.light.hue_outdoor_wall,
            states.light.shelly_buitenlampen
          ] %}

          {% set switches = [] %}

          {% set lights_on = lights | selectattr('state','eq','on') | list %}
          {% set lights_name = lights | selectattr('state','eq','on') | map(attribute='name') | join(', ') %}

          {% set switches_on = switches | selectattr('state','eq','on') | list %} 
          {% set switches_name = switches | selectattr('state','eq','on') | map(attribute='name') | join(', ') %}

          {% if (lights_on | length == 1) %} 
            {% if is_state('light.hue_outdoor_wall', 'on') %}
              <b>Lamp voordeur aan</b>
            {% elif is_state('light.shelly_buitenlampen', 'on') %}
              <b>Buitenlampen aan</b>
            {% endif %}
          {% elif (lights_on | length == 2) %}
            <b>Alle buitenlampen aan</b>
          {% endif %}
          </style>
          </p>
        info: |
          <style>
            #grid, #grid4 {
                display: grid;
                grid-template-columns: 30px 220px; /* grid-template-columns: 15% 85%; */
                font-family: SF Display;
                font-size: var(--sidebar-font-size);
                line-height: var(--sidebar-line-height);
                font-weight: 300;
                letter-spacing: 0.06vw;
                color: rgba(255, 255, 255, 0.6);
                font-size: 95%;
                margin-right: -50px;
                margin-bottom: 6px;
            }
          </style>
          {% if is_state('sensor.count_all_lights_on', '0') %}
            <div id="grid">
              <div><ha-icon icon='mdi:lightbulb-off-outline' style='width: 20px; height: 20px;'></ha-icon></div> <div> Alle lampen uit <br></div>
            </div>
          {% else %}
            <div id="grid">
              <div><ha-icon icon='mdi:lightbulb-on-outline' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div><b> Lampen aan: {{ states['sensor.count_all_lights_on'].state }} </b></div>
            </div>
          {% endif %}
          {% if is_state('sensor.names_all_doors_open', 'Geen') %}
            <div id="grid">
              <div><ha-icon icon='mdi:door-closed' style='width: 20px; height: 20px;'></ha-icon></div> <div> Alle deuren dicht </div>
            </div>
          {% else %}
            <div id="grid">
              <div><ha-icon icon='mdi:door-open' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div><b> {{ states['sensor.names_all_doors_open'].state }} </b></div>
            </div>
          {% endif %}
          {% if is_state('sensor.names_all_motion_on', 'Geen') %}
            <div id="grid">
              <div><ha-icon icon='mdi:motion-sensor' style='width: 20px; height: 20px;'></ha-icon></div> <div>{{ states['sensor.last_motion_on'].state }} </div>
            </div>
          {% else %}
            <div id="grid">
              <div><ha-icon icon='mdi:motion-sensor' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div><b>{{ states['sensor.names_all_motion_on'].state }} </b></div>
            </div>
          {% endif %}
          {% if not is_state('sensor.trash_tomorrow', 'Geen') %}
            <div id="grid">
              <div><ha-icon icon='mdi:trash-can' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div><b> Morgen: {{ states['sensor.trash_tomorrow'].state }} </b></div>
            </div>
          {% endif %}
          {% if not is_state('sensor.trash_today', 'Geen') %}
            <div id="grid">
              <div><ha-icon icon='mdi:trash-can' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div><b> Vandaag: {{ states['input_select.trash_today'].state }} </b></div>
            </div>
          {% endif %}
          {% if is_state('binary_sensor.water_sensor_zolder', 'on') %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> Water geconstateerd bij de wasmachine </b></div>
            </div>
          {% endif %}
        weather: |
          {% set temperature_out = states('sensor.buiten_temperature_xiaomi') | float (default='?') %}
          {% set temperature_in = states('sensor.kamer_temperature_xiaomi') | float (default='?') %}
          {% set co2_in = states('sensor.kamer_co2_xiaomi') | int(default=0) | string %}
          {% if co2_in | int > 1200 %}
            {% set co2_in_color = 'red' %}
          {% elif co2_in | int > 800 %}
            {% set co2_in_color = 'orange' %}
          {% else %}
            {% set co2_in_color = 'green' %}
          {% endif %}
          {% if is_state('sun.sun', 'above_horizon') %}
              {% set sun_icon = 'up' %}
            {% else %}
              {% set sun_icon = 'down' %}
          {% endif %}
          {% set sun_elevation = state_attr('sun.sun', 'elevation') %}
          <style>
            #grid4 {
                grid-template-columns: 30px 70px 30px 120px;
            }
          </style>
          <div id="grid4">
            <div><ha-icon icon='mdi:sun-thermometer-outline' style='width: 20px; height: 20px;'></ha-icon></div> <div> {{ temperature_out }}°C </div>
            <div><ha-icon icon='mdi:home-thermometer-outline' style='width: 20px; height: 20px;'></ha-icon></div> <div> {{ temperature_in }}°C </div>
          </div>
          <div id="grid4">
            <div><ha-icon icon='mdi:weather-sunset-{{ sun_icon }}' style='width: 20px; height: 20px;'></ha-icon></div> <div> {{ sun_elevation }} </div>
            <div><ha-icon icon='mdi:molecule-co2' style='color:{{ co2_in_color }}; width: 20px; height: 20px;'></ha-icon></div> <div> {{ co2_in }} ppm </div>
          </div>
        vacuum: |
          {% set entity_id = 'vacuum.rockrobo' %}
          {% set tijd = states['sensor.vacuum_program_time'].state %}
          {% set programma = states['input_boolean.vacuum_program'].state %}
          {% if is_state(entity_id, 'cleaning') %}
            <div id="grid">
            <div><ha-icon icon='custom:roborock-vacuum' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div> <b>Aan het stofzuigen</b> </div>
          </div>
          {% elif is_state(entity_id, 'returning') %}
            <div id="grid">
            <div><ha-icon icon='custom:roborock-vacuum' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div> <b>Stofzuigen is klaar</b> </div>
          </div>
          {% elif programma == 'on' %}
            <div id="grid">
            <div><ha-icon icon='mdi:clock-check-outline' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div> <b>Stofzuigen om {{ tijd }}</b> </div>
          </div>
          {% endif %}
        status_info: |
          {% set entity_id = 'switch.shelly_doorbell_chime_status' %}
          {% if is_state(entity_id, 'off') %}
          <div id="grid">
            <div><ha-icon icon='mdi:bell-off-outline' style='color:#FFA500; width: 20px; height: 20px;'></ha-icon></div> <div> <b>Deurbel is uit</b> </div>
          </div>
          {% endif %}
          {% if is_state('binary_sensor.wireguard_running', 'off') or is_state('switch.fritz_box_7590_port_forward_wireguard', 'off') %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> Wireguard offline </b></div>
            </div>
          {% endif %}
          {% if is_state('switch.adguard_filtering', 'off') %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> AdGuard offline </b></div>
            </div>
          {% endif %}
          {% if is_state('binary_sensor.nas_gz_security_status', 'on') %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> NAS security </b></div>
            </div>
          {% endif %}
          {% if is_state('binary_sensor.ix3_m_sport_door_lock_state', 'on') %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> Auto niet op slot </b></div>
            </div>
          {% endif %}
          {% if is_state('binary_sensor.ix3_m_sport_lids', 'on') %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> Auto deuren open </b></div>
            </div>
          {% endif %}
          {% if is_state('binary_sensor.ix3_m_sport_windows', 'on') %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> Auto ramen open </b></div>
            </div>
          {% endif %}
          {% if is_state('input_boolean.inverter_online', 'off') and (now().timestamp() - states.input_boolean.inverter_online.last_changed.timestamp() > 300) %}
            <div id="grid">
              <div><ha-icon icon='mdi:alert' style='color:red; width: 20px; height: 20px;'></ha-icon></div> <div><b> Inverter offline </b></div>
            </div>
          {% endif %}
Code from bottom card with energy overview
  footer_energy:
    show_state: true
    show_name: false
    show_icon: false
    tap_action:
      action: none
    state_display: >
      [[[
        if (entity) {
          let spacer = '<div><span>|</span></div>';
          return `
            <div id="footer_container">
              <div><ha-icon icon="mdi:flash"></ha-icon> Verbruik: <b>${(entity.state)}</b> W</div>
              ${spacer}
              <div><ha-icon icon="mdi:transmission-tower"></ha-icon> Grid: <b>${states['sensor.grid_consumption_w'].state}</b> W</div>
              ${spacer}
              <div><ha-icon icon="mdi:solar-power"></ha-icon> Zon: <b>${states['sensor.solar_power_current'].state}</b> W</div>
              ${spacer}
              <div><ha-icon icon="mdi:flash-outline"></ha-icon> Productie: <b>${states['sensor.power_production_filtered_w'].state}</b> W</div>
              ${spacer}
              <div><ha-icon icon="mdi:home-lightning-bolt-outline"></ha-icon> Totaal verbruik: <b>${states['sensor.house_consumption_w'].state}</b> W</div>
              </b>
            </div>
          `;
        }
      ]]]
    styles:
      state:
        - place-self: start
        - font-family: SF Display, Roboto
        - font-weight: 400
        - font-size: 1.26vw
        - letter-spacing: 0.05vw
        - white-space: nowrap
        - color: rgba(255, 255, 255, 0.6);
        - opacity: 0.8
        - width: 100%
      card:
        - background: none
        - padding: 0
        - margin-top: -1.5vw
        - overflow: hidden
    extra_styles: |
      ha-icon {
        vertical-align: 5%;
        opacity: 0.6;
        width: 1.2vw;
      }
      #footer_container {
        color: rgba(255, 255, 255, 0.6);
        display: flex;
        justify-content: space-between;
      }
      span {
        color: rgba(255, 255, 255, 0.4);
      }
      .ellipsis {
        color: #6a7377;
      }
      /* phone */
      @media screen and (max-width: 800px) {
        #state {
          display: none;
        }
      }
1 Like

There should be no need to comment out that line, that is a standard way to confirm that the state of an entity is one of the values In The array

I can’t help with the error the code runs fine on my end. Try restarting the server

As for how to show charging and not charging,
Fine this line of code
circle_stroke = variables.state_on
And change it to this
circle_stroke = variables.state_on && entity.attributes. charge_power > 0
That should have the stroke of the circle normal when the car is not charging

NOTE: this was untested as I’m on my phone.

I found the culprit. It was in icon_tesla which was looking for a charing state attribute.

icon_tesla
icon_tesla:
    styles:
      custom_fields:
        icon:
          - width: 78%
          - margin-left: -10%
    custom_fields:
      icon: >
        [[[
          let state = states[variables.retain].attributes.charging_state;
          
          return `
            <svg viewBox="0 0 504 304.64" fill="var(--light-color)">
              <style>
                .Charging {
                  fill: url(#charging-fill);
                }
                .Stopped {
                  fill: red;
                }
                .Complete {
                  fill: #00CB00;
                }
                .resting {
                  transform: rotateZ(-90deg) translate(-1.5%, 0%);
                  transform-origin: 45% 41%;
                }
              </style>
              <linearGradient id="charging-fill" x1="0.5" y1="1" x2="0.5" y2="0">
                  <stop offset="100%" stop-opacity="1" stop-color="#00CB00">
                    <animate attributeName="offset" values="0;1" repeatCount="indefinite" dur="3s" begin="0s"/>
                  </stop>
                  <stop offset="100%" stop-opacity="0" stop-color="#00CB00">
                    <animate attributeName="offset" values="0;1" repeatCount="indefinite" dur="3s"  begin="0s"/>
                  </stop>
    
              </linearGradient>
              <g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="${state}" d="M490.56,90.72c-9-2.24-27.44-7.28-28.56,0s-3.35,20.16-3.35,20.16L443.52,112s-1.68-1.68-3.36-7.84-17.92-60.48-30.8-79S387.52,9,387.52,9v.56S337.13,0,251.44,0,115.36,9.52,115.36,9.52V9S107,6.72,93.52,25.2c-12.88,18.48-29.12,72.8-30.8,79S59.36,112,59.36,112L44.8,110.88S43.12,98,41.44,90.72c-1.12-7.28-19.6-2.24-28.56,0C4.48,92.4,0,106.4,0,109.2s2.24,5,6.16,6.72,28,3.36,31.92,3.36c4.48,0,3.92-1.12,3.92-1.12l14.56,1.12s-1.12,5-3.92,6.16-3.36,3.92-5,6.16-18.48,14.56-21.28,21.84c-6.16,14-1.12,41.44-1.12,62.16,0,20.16,3.92,52.64,5.6,66.08s12.88,23,12.88,23H95.2c7.28,0,21.28-2.24,28.56-2.24H380.24c7.28,0,21.28,2.24,28.56,2.24h51.52s11.76-9.52,12.88-23c1.68-13.45,5.6-45.93,5.6-66.08s5-47.6-1.12-62.16c-2.8-6.72-19.6-19-21.28-21.84-1.68-2.24-2.24-5-5-6.16s-3.92-6.16-3.92-6.16L462,118.16s-.56,1.12,3.92,1.12,28-1.12,31.92-3.36S504,112,504,109.2s-4.47-16.8-13.43-18.48ZM77.84,104.16C80.08,93.52,101.36,23.52,107.52,19c0,0,67.2-5,143.92-5s143.92,5,143.92,5c6.72,4.48,27.44,74.48,29.68,85.12s-1.12,10.64-1.12,10.64H79s-3.36,0-1.12-10.64ZM96.32,243c-3.92,0-47.6-.56-47.6-.56s9.52,18.48,11.2,21.84c1.68,3.92,1.68,10.64-3.92,9-5-1.68-10.64-12.88-12.32-19-1.68-6.72-3.92-23-3.92-23l66.07,5c0,.56-5.59,6.72-9.51,6.72ZM103,190.4c-16.24,0-37.52-6.16-43.68-7.84s-10.64-8.4-10.64-17.36,0-18.48,10.08-28.56c0,0,11.76,0,21.28,7.84,9,7.28,16.8,14,26.88,17.36,15.12,4.48,26.32,20.72,26.32,20.72s-14,7.84-30.24,7.84Zm267.68,93H133.28c-5.6,0-14-1.12-6.72-9,5.61-5.6,15.12-10.08,33.61-10.08H342.73c18.48,0,28,4.48,33.6,10.08,8.39,7.84,0,9-5.61,9Zm93.52-51.52s-2.24,16.24-3.92,23-7.28,17.36-12.32,19-5.6-5.6-3.92-9S455.28,243,455.28,243s-43.68.56-47.6.56-9-6.72-9-6.72Zm-9.52-66.64c0,9-3.92,15.68-10.64,17.36s-28,7.84-43.68,7.84c-16.24,0-30.24-7.84-30.24-7.84s10.64-16.24,26.32-20.72c10.08-2.8,17.36-10.08,26.88-17.36a38.11,38.11,0,0,1,21.28-7.84c10.08,10.08,10.08,19.6,10.08,28.56Z"/></g></g>
            </svg>
          `;
        ]]]

So I added in my ui-lovelace.yaml a little variable retainer.

ui-lovelace.yaml tesla part
 - type: custom:button-card
            entity: sensor.tesla_combo_1
            double_tap_action:
              !include popup/tesla1_climate.yaml
            name: Blanche
            variables:
              retain: binary_sensor.blanche_charger
            template:
              - tesla
              - icon_tesla

So I got it working. Thank you so much.

Thanks for the lovley design :slight_smile:
I have one question, i got this error recently.
How can work around this issue?

image

hi bjortte, welcome

Can you provide more details?

Was this working and is now broken?
What, if any changes have you made to the card?
Can you provide the yaml for the card that is not working?
did you install, and set up YouTube is watching?

i have 2 tiled buttons using this theme.

I have 2 enities that show me power

when the power is 0w its fine the button is off

How would i get the background to change colour when power is like 50w or something so i know its on

here is the code for the tile/button

          - type: custom:button-card
            entity: sensor.myenergi_eddi_21533936_internal_load_ct1
            name: <marquee behavior=scroll scrollamount="3">Eddi kWh Usage</marquee>
            custom_fields:
               icon: >
                 <ha-icon icon="mdi:power-plug"></ha-icon>
            template:
              - base