A different take on designing a Lovelace UI

That template is not included in the GitHub repo.

but it is built off the base template so it should work,
Did you add - border-width: 0 to the base template as described in this post?

try this if you have followed the steps in the above post.

sensor:
    state_display: >
      [[[ return ' '; ]]]
    template:
      - base
      - circle
    custom_fields:
      circle: >
        [[[
          if (entity) {
            return `
              <svg viewBox="0 0 50 50">
                <circle cx="25" cy="25" r="20.5" stroke="none" stroke-width="1.5" fill="rgba(255,255,255,0.04)" />
                <text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${parseInt(entity.state)}</text>
              </svg>
            `;
          }
        ]]]
      graph:
        card:
          type: sensor
          entity: >
            [[[ return entity.entity_id; ]]]
          graph: line
          hours_to_show: 2
          detail: 2
          card_mod:
            style: |
              .header, .value, .measurement {
                display: none !important;
              }
      push_graph: >
        [[[
          setTimeout(() => {
            let elt = this.shadowRoot,
              card = elt.getElementById('card'),
              container = elt.getElementById('container'),
              graph = elt.getElementById('graph');

            if (elt && card && container && graph) {
              card.insertBefore(graph, container);
            }
          }, 0);
          return null;
        ]]]
    styles:
      custom_fields:
        card:
         - border-width: 0
        graph:
          - position: absolute
          - width: 100%
          - height: 100%
          - clip-path: inset(0 round var(--custom-button-card-border-radius))
          - left: 0
          - bottom: 0

NOTE: I did not test this I currently don’t have access to my home assistant

1 Like

Yeah, i have the border-width: 0 on the base template.
On my screenshot have others cards that i have, they work perfectly, just this with a graphic inside get this.
And im pretty sure is because his second ha-card created inside the main ha-card:

I tried your example and didn’t work. :frowning:

Solved with a not a pretty code, but it works. I just put a style direct on the custom:button-card

          - type: custom:button-card
            entity: sensor.yidjcev0q3_output_power
            name: Energia
            template:
              - sensor
              - icon_energia
            tap_action: !include popup/energia.yaml
            hold_action: !include popup/energia.yaml
            card_mod:
              style: |
                :host{
                  --accent-color: #039be5;
                  --ha-card-border-width: 0px;
                }

The --ha-card-border-width: 0px solve the problem.

Can anyone tell me why I am constantly getting these little images when clicking on the buttons. They directly disappear again and it seems that any image is missing … but damn I can’t figure it out!!!
Screenshot 2022-11-04 171252

1 Like

Worked for all but the sidebar, where the borders are still visible.
I solved with Clatus’s solution by adding:

            card_mod:
              style: |
                :host{
                  --accent-color: #039be5;
                  --ha-card-border-width: 0px;
                }

well yeah, if you add a random card it’ll still have a border unless you use card_mod

1 Like

/www/loader.svg is there …

When using a Light entity with the template light, the brightness can be adapted by swiping across the brightness in the circle. Where can I find the code for this action? I would like to create a template for a cover/roller shutter and use swipe actions to open, or close the roller shutter.

Hi,

Using the mini-graph layout that came by earlier.

But I want to use a different entity displaying the temperature in the circle.

I use this code:

              - type: custom:button-card
                entity: sensor.woonkamer_humidity
                name: Vochtigheid
                template:
                  - base
                  - circle
                  - graph
                variables:
                  circle_input: >
                    [[[ return states['sensor.woonkamer_temperature'].state; ]]] 
                  circle_input_unit: "°C"
                  graph_entity: sensor.woonkamer_humidity

But no circle with the temperature is showing… Who can guide me?

Hi guys,
Great way to learn all in’s and out’s of home assistant. Been tweaking for weeks now but still stuck with the media card.

I would like to have a static image and/or recently added plex thumbnail but can’t get it work yet.
Bigger question when spotify is player the card should change to the spotify album cover and when it stops it should return back to the image/plex thumbail.

What am I missing?

      #################################################
      #                                               #
      #                     MEDIA                     #
      #                                               #
      #################################################

      - type: grid
        title: Media
        view_layout:
          grid-area: media
        columns: 1
        cards:

          - type: custom:swipe-card
            parameters:
              roundLengths: true
              effect: coverflow
              speed: 650
              spaceBetween: 20
              threshold: 7
              coverflowEffect:
                rotate: 80
                depth: 300
            cards:

              - type: horizontal-stack
                cards:

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state_not: Spotify_D
                        - entity: select.conditional_media
                          state_not: Spotify_L
                        - entity: select.conditional_media
                          state_not: PLEX

                      card:
                        type: custom:button-card
                        entity:  ### Cannot get this part to work // or can I add a random picture?
                        name: I dont get this part
                        tap_action:
                          action: none
                        template:
                          - conditional_media
                          - icon_plex

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state: Spotify_D
                      card:
                        type: custom:button-card
                        entity: media_player.spotify_dennis
                        template:
                          - conditional_media
                          - icon_spotify

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state: Spotify_L
                      card:
                        type: custom:button-card
                        entity: media_player.spotify_lillian
                        template:
                          - conditional_media
                          - icon_spotify

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state: PLEX
                      card:
                        type: custom:button-card
                        entity: media_player.plex_dennis_plex_for_lg_lg_oled48cx6lb
                        template:
                          - conditional_media
                          - icon_plex

            # SWIPE CARD - 2nd PAGE
              - type: grid

Does anyone have a guide on how to make animated SVG icons? I would like to make a couple including a garage door opening and closing, a ceiling fan/light combo lighting up/spinning, an XBOX logo turning green, a plug being powered on/off, and probably more down the road.

I would also like to move the people from cards to the sidebar. Maybe in color when they are detected home and in greyscale when they are not. I would still like to be able to tap on them to get more information. I have seen it done before, but I don’t think the configurations have been shared for it.

Can you link to the " mini-graph layout that came by earlier." I do not know what you are referring to.
the circle_input looks fine to me circle_input_unit should be ’ not " but I don’t think that is the issue

try adding a console.log and see what value is in states[‘sensor.woonkamer_temperature’].state.

circle_input: >
                    [[[ 
                       console.log(states['sensor.woonkamer_temperature'].state)
                       return states['sensor.woonkamer_temperature'].state;
                     ]]]

conditional_media in my opinion was the hardest thing to wrap my head around.

Based on the info you provided, you have not updated the conditional_media template.

This template has 2 templates inside out it one for the possible options and one for the selected option that should be the name of the entity you would like to display

This is mine, it might help for you to see what I have changed.

---
unique_id: conditional_media
name: conditional_media
state: >
  {% set recently_added = 'Last added' %}
  {% set paused_timeout_minutes = 15 %}
  {% set media_players = [
    states.media_player.plex_prologue_mason,
    states.media_player.spotify,
    states.media_player.living_room_tv,
    states.media_player.plex_plex_for_lg_lg_oled65cxpta ] %}

  {% 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 on = media_players | selectattr('state','eq','on')| list %}
  {% set timeout_playing = False if on | length == 0 else
    (as_timestamp(now()) - as_timestamp(on | map(attribute='last_changed') | list | max)) < paused_timeout_minutes * 60 %}

  {% 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 on %}
    {{ media('on')}}
  {% elif paused %}
    {{ media('paused') if timeout_paused else recently_added }}
  {% else %}
    {{ recently_added }}
  {% endif %}
options: >
  {% set recently_added = ['Last added'] %}
  {% set media_players = [
    states.media_player.living_room_tv,
    states.media_player.plex_prologue_mason,
    states.media_player.spotify,
    states.media_player.plex_plex_for_lg_lg_oled65cxpta ] %}
  {{ recently_added + media_players | map(attribute='name') | list }}
select_option:
  service: select.select_option
  target:
    entity_id: select.conditional_media
  data:
    option: >
      {{ option }}

I started to learn it by myself yesterday :wink: this video helped me a lot to get started. Make Awesome SVG Animations with CSS // 7 Useful Techniques - YouTube

Graph part came from post #3181

Nevertheless, this is the slightly modified template code I’m using

graph:
  custom_fields:
    graph:
      card:
        type: "custom:mini-graph-card"
        height: 200
        entities:
          - entity: "[[[ return variables.graph_entity ]]]"
        hours_to_show: 48
        points_per_hour: 1
        line_width: 8
        font_size: 75
        decimals: 0
        show:
          name: false
          icon: false
          state: false
          legend: false
          labels: false
          labels_secondary: false
          points: false
        color_thresholds:
          - value: 40
            color: "#276696"
          - value: 50
            color: "#228C22"
          - value: 55
            color: "#d35400"
          - value: 60
            color: "#c0392b"          
  styles:
    card:
      - border-width: 0  
    custom_fields:
      card:
        - border-width: 0
      graph: [bottom: 0%, left: 0%, width: 125%, position: absolute, margin: 0% 0% 0% 0% ] #-13% -14%]
  card_mod:
    style: |
      :host{
        --accent-color: #039be5;
        --ha-card-border-width: 0px;
      }

But it should not interfere with the circle.

the console.log gives the actual value

(function anonymous(states,entity,user,hass,variables,html
) {
'use strict';  
  console.log(states['sensor.woonkamer_temperature'].state)
  return states['sensor.woonkamer_temperature'].state; 

})

18.76

Hi , thank you for the reply. unfortunately I can’t solve the puzzle.
Static image until a player is activated would be enough.

#################################################
#                                               #
#                  BASE MEDIA                   #
#                                               #
#################################################

base_media:
  variables:
    media_on: >
      [[[ return !entity || ['playing', 'paused'].indexOf(entity.state) !== -1; ]]]
    media_off: >
      [[[ return !entity || ['off', 'idle', 'standby', 'unknown', 'unavailable'].indexOf(entity.state) !== -1; ]]]
  tap_action:
    action: >
      [[[
        return variables.media_on
            ? 'call-service'
            : 'none';
      ]]]
    service: media_player.media_play_pause
    service_data:
      entity_id: >
        [[[
          return variables.entity_id;
        ]]]
  double_tap_action:
    action: more-info
  styles:
    card:
        - color: >
            [[[
              let entity_picture = entity === undefined || entity.attributes.entity_picture;
              if (variables.state === 'off' || variables.state === 'idle' || variables.state === 'standby' ||
                variables.state === 'unknown' || variables.state === 'unavailable' || entity === undefined) {
                return 'rgba(255, 255, 255, 0.3)';
              }
              return (variables.state != 'off' && variables.state != 'idle' && variables.state != 'standby') && (entity_picture == null) ? 'rgba(0, 0, 0, 0.6)' : '#efefef';
            ]]]
        - text-shadow: >
            [[[
              let entity_picture = entity === undefined ? null : entity.attributes.entity_picture;
              if (variables.state === 'off' || variables.state === 'idle' || variables.state === 'standby' || variables.state === 'unknown' || variables.state === 'unavailable') {
                return entity_picture == null ? 'none' : '1px 1px 5px rgba(18, 22, 23, 0)';
              }
              return entity_picture == null ? 'none' : '1px 1px 5px rgba(18, 22, 23, 0.9)';
            ]]]
#################################################
#                                               #
#                     MEDIA                     #
#                                               #
#################################################

media:
  template:
    - base
    - base_media
  styles:
    custom_fields:
      icon:
        - width: 70%
        - margin-left: 2%
        - fill: '#9da0a2'
        - display: >
              [[[
                return variables.media_off || variables.entity_picture === undefined
                    ? 'initial'
                    : 'none';
              ]]]
    card:
      - background-color: none
      - background-size: cover
      - background-position: center
      - background-image: >
            [[[
              let entity_picture = entity === undefined || entity.attributes.entity_picture;
              if (variables.state === 'off' || variables.state === 'idle' || variables.state === 'standby' ||
                variables.state === 'unknown' || variables.state === 'unavailable' || entity === undefined) {
                return 'linear-gradient(0deg, rgba(115, 115, 115, 0.2) 0%, rgba(115, 115, 115, 0.2) 100%)';
              }
              return (variables.state != 'off' && variables.state != 'idle' && variables.state != 'standby') && (entity_picture == null) ?
                'linear-gradient(0deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.8) 100%)' :
                'linear-gradient(0deg, rgba(0,0,0,.8) 0%, rgba(0,0,0,0) 100%), url(' + entity_picture + ')';
            ]]]


#################################################
#                                               #
#               CONDITIONAL MEDIA               #
#                                               #
#################################################

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: >
    [[[
      if (entity) {
          let elt = this.shadowRoot,
              await = setTimeout(marquee, 100),
              data = entity.attributes.data,
              artist = entity.attributes.media_artist,
              title = entity.attributes.media_title;

            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 = artist === undefined && title !== undefined
                    ? title
                    : title === undefined && artist !== undefined
                        ? artist
                        : title !== undefined && artist !== undefined
                            ? `${artist} - ${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) {
                let data = entity.attributes.data;
                return data && (data[variables.i].fanart || data[variables.i].poster)
                    ? `url("${data[variables.i].fanart}"), url("${data[variables.i].poster}")`
                    : `url("${variables.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 ' ';
      ]]]

      #################################################
      #                                               #
      #                     MEDIA                     #
      #                                               #
      #################################################

      - type: grid
        title: Media
        view_layout:
          grid-area: media
        columns: 1
        cards:

          - type: custom:swipe-card
            parameters:
              roundLengths: true
              effect: coverflow
              speed: 650
              spaceBetween: 20
              threshold: 7
              coverflowEffect:
                rotate: 80
                depth: 300
            cards:

              - type: horizontal-stack
                cards:

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state_not: Spotify_D
                        - entity: select.conditional_media
                          state_not: Spotify_L
                        - entity: select.conditional_media
                          state_not: PLEX

                      card:
                        type: custom:button-card
                        entity: media_player.spotify_dennis
                        name: Currently playing
                        tap_action:
                          action: none
                        template:
                          - conditional_media
                          - icon_spotify

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state: Spotify_D
                      card:
                        type: custom:button-card
                        entity: media_player.spotify_dennis
                        template:
                          - conditional_media
                          - icon_spotify

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state: Spotify_L
                      card:
                        type: custom:button-card
                        entity: media_player.spotify_lillian
                        template:
                          - conditional_media
                          - icon_spotify

                    - type: conditional
                      conditions:
                        - entity: select.conditional_media
                          state: PLEX
                      card:
                        type: custom:button-card
                        entity: media_player.plex_dennis_plex_for_lg_lg_oled48cx6lb
                        template:
                          - conditional_media
                          - icon_plex

            # SWIPE CARD - 2nd PAGE
              - type: grid
                columns: 1
                cards:

what values are in the select.conditional_media entity?

have you modified the code in the select template sensor for select.conditional_media?

thanks with that I was able to test and I found the issue, you have 2 options to solve this

ignore the broken line that should just be an issue on my end.

option 1: update the circle template

in button_card_templates/circle.yaml

find this section of code

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *              OTHER              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            else if (variables.state_on) {
                return circle(state, input, unit);
            }

and replace it with this

            /* * * * * * * * * * * * * * * * * *
            *                                  *
            *              SENSOR              *
            *                                  *
            * * * * * * * * * * * * * * * * * */

            else if (domain === 'sensor') {
                return circle(state, input, unit);
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *              OTHER              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            else if (variables.state_on) {
                return circle(state, input, unit);
            }

changes to the base template like this can result in some issues and when you update you will need to remember to not remove that change

option 2: override the custom_fields

add the code to draw the circle to your custom template

template

graph:
  template:
    - base
    - circle
  custom_fields:
    circle: >
      [[[
        let r = 22.1,
          c = r * 2 * Math.PI,
          tspan = '<tspan dx=".2" dy="-.4">',
          domain = entity.entity_id.split('.')[0],
          state = variables.state_on,
          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: ${state ? 'var(--c-stroke-color-on)' : 'var(--c-stroke-color-off)'};
                fill: ${state ? '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: var(--c-font-color);
              }
              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>

          ${domain === 'light' && `
              <input id="circle_slider" type="range" min="0" max="100" value="${input}">
          `}
        `;
      ]]]
    graph:
      card:
        type: "custom:mini-graph-card"
        height: 200
        entities:
          - entity: "[[[ return variables.graph_entity ]]]"
        hours_to_show: 48
        points_per_hour: 1
        line_width: 8
        font_size: 75
        decimals: 0
        show:
          name: false
          icon: false
          state: false
          legend: false
          labels: false
          labels_secondary: false
          points: false
        color_thresholds:
          - value: 40
            color: "#276696"
          - value: 50
            color: "#228C22"
          - value: 55
            color: "#d35400"
          - value: 60
            color: "#c0392b"          
  styles:
    card:
      - border-width: 0  
    custom_fields:
      card:
        - border-width: 0
      graph: [bottom: 0%, left: 0%, width: 125%, position: absolute, margin: 0% 0% 0% 0% ] #-13% -14%]
  card_mod:
    style: |
      :host{
        --accent-color: #039be5;
        --ha-card-border-width: 0px;
      }

use like this now

              - type: custom:button-card
                entity: sensor.woonkamer_humidity
                name: Vochtigheid
                template:
                  - graph
                variables:
                  circle_input: >
                    [[[ return states['sensor.woonkamer_temperature'].state; ]]] 
                  circle_input_unit: "°C"
                  graph_entity: sensor.woonkamer_humidity
1 Like

Hi Mason,
Thank you for the homerun. Found the problem in a file called: tv_media.yaml
Used your previous posted conditional_media code and states are now correct and updating.
Will work on the Plex thumbnails or static image now when no players are active.
Many thanks.

1 Like