Lovelace: Button card

For a test I tried a similar decluttering template with button card, it works with double quotes. Probably in his case the sensor will be unavailable.
my templates looks like this
decluttering template

test_template:
  card:
    type: custom:button-card
    entity: '[[sensor1-2]]'
    show_name: false
    show_state: false
    show_label: true
    show_icon: false
    label: >
      [[[
        if (states['[[sensor1-2]]'].state == "[[state1-2]]") {
          return `<ha-icon icon="mdi:[[icon1-2]]"></ha-icon> Turn Off`;
        } else {
          return `<ha-icon icon="mdi:[[icon1-1]]"></ha-icon> Turn On`;
        }
      ]]]

button-card

cards:
  - type: custom:decluttering-card
    template: test_template
    variables:
      - sensor1-2: light.ceiling_lights
      - state1-2: 'on'
      - icon1-2: lightbulb-on
      - icon1-1: lightbulb-off

2024-10-21 17.18.24

1 Like

compound post I previously posted in card-mod.… since I have found no card-mod solution but do all in button-card now, Ill repost the set below:

this could be posted in a tile card or custom:button-card thread too, but since I am trying to find the right card-mod, I’ll start here…

I have this tile card mod

      type: tile
      icon: mdi:water
      name: ' '
      vertical: true
      tap_action:
        action: more-info
      card_mod:
        style: |
          .icon-container {
            border-radius: 24px;
            background: radial-gradient(var(--card-background-color) 60%,transparent calc(60% + 1px)),
            conic-gradient(var(--tile-color) {{states(config.entity)}}% 0%,
            var(--card-background-color) 0% 100%);
          }

which draws a perfect border according to the percentage of the entity:

I’d love to do the same with my light buttons, but cant find the right mod to do so:

I have some of these on a custom field:

  custom_fields:
    info: &info_light
      >
        [[[ if (entity.state === 'on' && entity.attributes.brightness) {
            var brightness = Math.round(entity.attributes.brightness/2.54);
            const radius = 20.5;
            const circumference = radius * 2 * Math.PI;
            return `
              <svg viewBox="0 0 50 50">
                <circle cx="25" cy="25" r="${radius}"
                  stroke="var(--button-card-light-color,var(--active-color))" stroke-width="2" fill="none"
                  style="transform: rotate(-90deg);transform-origin: 50% 50%;
                  stroke-dasharray: ${circumference};
                  stroke-dashoffset: ${circumference - brightness / 100 * circumference};" />
                <text x="50%" y="54%" fill="black" font-size="16" font-weight= "bold"
                  text-anchor="middle" alignment-baseline="middle">
                  ${brightness}<tspan font-size="10">%</tspan>
                </text>
              </svg>
            `;
          }
        ]]]

and that works fine:

Scherm­afbeelding 2024-10-23 om 17.44.58

. but in this case I want the border to be drawn on the img_cell of the button-card, or, checking the element in the dom, the .img-cell

so I copied that tile card mod over to

  card_mod:
    style: |
      .img-cell {
        border-radius: 24px;
        background: radial-gradient(var(--card-background-color) 60%,transparent calc(60% + 1px)),
        conic-gradient(var(--button-card-light-color) {{states(config.entity)}}% 0%,
        var(--card-background-color) 0% 100%);
      }

note I did change to the button-card-light-color, but nothing is happening…

this is the original button with a regular style section on the image_cell:

    img_cell:
      - justify-content: center
      - background: >
          [[[ var rgb = (entity.state === 'on')
                ? entity.attributes.rgb_color : '211,211,211';
              return 'rgba(' + rgb + ',0.2)'; ]]]
      - border-radius: 24px
      - place-self: start
      - width: 42px
      - height: 42px

Scherm­afbeelding 2024-10-23 om 17.39.29

but when I take that out, and change to the card-mod there is nothing:

tbh, I wouldn’t yet know how to write the background in styles syntax native to the custom:button-card, thats why I tried it with card-mod

please have a look what could be done to fix this?

Follow up

well there is progress:

      - background: >
          [[[ var rgb = (entity.state === 'on')
                ? entity.attributes.rgb_color : '211,211,211';
              var rgba = 'rgba(' + rgb + ',0.2)';
              if (entity.state === 'on')
              return `radial-gradient(${rgba} 80%,transparent calc(60% + 1px)),
                      conic-gradient(var(--button-card-light-color) ${Math.round(entity.attributes.brightness/2.55)}% 0%,
                      var(--card-background-color) 0% 100%)`;
             return `${rgba}`; ]]]

and

  triggers_update:
    - entity

does a lot of good, thanks to @khaisilk1910 for helping me in Discord!

the final thing I need now is get rid of the actual conic, and have the background inside the img_cell only show that conic in the outer border…

just like in the print cartridge buttons…
I cant see it though

alternative since there is no apparent way to do as I hoped:

    img_cell:
      - justify-content: center
      - background: >
          [[[ var rgb = entity.state === 'on'
                ? entity.attributes.rgb_color : '211,211,211';
              var rgba = 'rgba(' + rgb + ',0.2)';

              return entity.state === 'on'
              ? `radial-gradient(var(--card-background-color) 60%,transparent calc(60% + 1px)),
                 conic-gradient(var(--button-card-light-color) ${Math.round(entity.attributes.brightness/2.55)}% 0%,
                 ${rgba} 0% 100%)`
              : `${rgba}`; ]]]

___ And finally at least 1 solution___

it has to do with the left over section of the overlay (but no, it didnt make any difference in solar I could find out)

I’ve found another solution now though:

I am using the same info custom field, and I overlay it exactly over the icon img_cell of the button-card. It feels a bit hacky, and ofc its not 100% linked to the img_cell itself (not all as a matter of fact, it’s just a custom field after all) but is is the best I could come up with for now.

CSS for the border percentage....
  custom_fields:
    info: >
      [[[ if (entity.state === 'on' && entity.attributes.brightness) {
          var brightness = Math.round(entity.attributes.brightness/2.54);
          var rgb = (entity.state === 'on')
                  ? entity.attributes.rgb_color : '211,211,211';
          var rgba = 'rgba(' + rgb + ',0.2)';
          const radius = 20.5;
          const circumference = radius * 2 * Math.PI;
          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}"
                stroke="var(--button-card-light-color,var(--active-color))" stroke-width="2" fill="${rgba}"
                style="transform: rotate(-90deg);transform-origin: 50% 50%;
                stroke-dasharray: ${circumference};
                stroke-dashoffset: ${circumference - brightness / 100 * circumference};" />
            </svg>
          `;
        }
      ]]]

  styles:
    custom_fields:
      info:
        - position: absolute
        - left: 2%
        - top: 4%
        - width: 48px

Scherm­afbeelding 2024-10-25 om 11.50.34

OMT:

I can omit the

triggers_update:
  - entity

after all. It was unexpected I needed it before, but now the templates are working, the card successfully triggers automatically

2 Likes

Love it! Great CSS work…

1 Like

Brilliant - thanks for sharing!

1 Like

small update, because there were some anomalies in the on state without rgb_color attribute (showing the 211,211,211 color…) and I added some unavailable/unknown colors.

  custom_fields:
    info: >
      [[[ if (entity.state === 'on' && entity.attributes.brightness) {
            var brightness = Math.round(entity.attributes.brightness/2.54);
            var rgb = (entity.attributes.rgb_color)
                       ? entity.attributes.rgb_color : '254,181,13' ;
          }
          if (entity.state === 'off') {
            var rgb = '211,211,211';
          }
          if (entity.state in ['unknown','unavailable']) {
            var rgb = '189,189,189';
          }
          var rgba = 'rgba(' + rgb + ',0.2)';
          const radius = 20.5;
          const circumference = radius * 2 * Math.PI;
          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}"
                stroke="var(--button-card-light-color,var(--active-color))"
                stroke-width="3"
                fill="${rgba}"
                style="transform: rotate(-90deg);transform-origin: 50% 50%;
                stroke-dasharray: ${circumference};
                stroke-dashoffset: ${circumference - brightness / 100 * circumference};" />
            </svg>
          `;
      ]]]

  styles:
    custom_fields:
      info:
        - position: absolute
        - left: 1%
        - top: 4%
        - width: 50px

also notice the styling/position change… it had to be 50 because of the viewbox using 50px too…
next to that, you’ve got to be aware the button-card’s background still exists. So, when using a darker theme the color of the circle gets frustrated by that darker background color.

Ive tried background transparent, but obviously that doesnt help, as it makes the card background even more prominent :wink:

so for now I’ve settled to do something like:

    img_cell:
      - justify-content: center
      - background: >
          [[[ if (entity.state === 'on') return 'ivory';]]]

but, that isnt optimal either (as it leaves the unused brightness area in that color. o well, its not simple at all

light theme is perfect though:

Other options to play with:
set the color of the img_cell background to the exact same color as the fill of the custom-field, not really any help

set

fill-opacity:1
fill-rule="nonzero"

to the svg view box, and play with that. doesn’t really help either.

3 Likes

Hey all, I’m trying to get colors represented on my button card when entity state changes and it seems to be ignoring the entire section where that is defined.

type: custom:button-card
name: Mark
show_name: false
show_state: false
show_icon: true
show_entity_picture: true
entity: person.mark_ramos
tap_action:
  action: perform-action
  perform_action: icloud3.find_iphone_alert
  data:
    device_name: abcdef0123456
entity_id: device_tracker.mark_iphone
styles:
  card:
    - background-color: var(--contrast2)
    - padding: 10px 10px 10px 0px
  grid:
    - grid-template-areas: "\"icon name btn\" \"icon state btn\""
    - grid-template-columns: 60px 1fr min-content
    - grid-template-rows: min-content
  img_cell:
    - justify-content: start
    - position: absolute
    - width: 40px
    - height: 40px
    - left: 0
    - bottom: 0
    - margin: 0 0 8px 10px
    - border-radius: 50%
    - border: |
        [[[
          if (entity.state == 'home') {
            return '1px solid var(--green)';
          } else {
            return 'none';
          }
        ]]]
  entity_picture:
    - justify-content: start
    - position: absolute
    - width: 40px
    - height: 40px
    - left: 0
    - bottom: 0
    - margin: 0 0 0 0
    - border-radius: 500px
  icon:
    - width: 50px
    - color: var(--contrast1)
  custom_fields:
    name:
      - align-self: start
      - justify-self: start
      - background: none
      - padding: 0
    state:
      - align-self: start
      - justify-self: start
      - background: none
      - padding: 0
      - margin-top: "-5px"
    btn:
      - align-self: end
      - justify-self: end
    icon:
      - align-self: start
      - justify-self: start
    badge:
      - position: absolute
      - left: 38px
      - top: 7px
custom_fields:
  name:
    card:
      type: custom:button-card
      name: Mark
      styles:
        card:
          - color: var(--contrast20)
          - font-size: 14px
          - font-weight: 600
          - background: none
  state:
    card:
      type: custom:button-card
      entity: "[[[ return entity.entity_id ]]]"
      show_icon: false
      name: |
        [[[ 
          return states['person.mark_ramos'].state;
        ]]]
      state:
        - value: home
          name: Home
        - value: not_home
          name: Away
      styles:
        card:
          - color: var(--contrast20)
          - font-size: 12px
          - background: none
          - opacity: "0.7"
  badge:
    card:
      type: custom:button-card
      entity: "[[[ return entity.entity_id ]]]"
      show_icon: true
      show_name: false
      icon: mdi:home
      state:
        - value: home
          icon: mdi:home
          styles:
            card:
              - background: var(--green)
        - value: not_home
          icon: mdi:home-export-outline
          styles:
            card:
              - background: var(--red)
      styles:
        card:
          - border-radius: 50%
          - width: 16px
          - height: 16px
          - background: none
        icon:
          - color: black
          - width: 12px
  btn:
    card:
      type: custom:mushroom-chips-card
      chips:
        - type: entity
          tap_action:
            action: more-info
          entity: sensor.mark_iphone_battery_3
          content_info: none
          card_mod:
            style: |
              ha-card {
                --chip-background: {{ 'var(--contrast4)' if states('sensor.mark_iphone_battery_3') | float > 10  else 'var(--red)' }};
                --color: {{ 'var(--contrast20)' if states('sensor.mark_iphone_battery_3') | float > 10  else 'var(--black)' }};
                padding: 0px!important;
                border-radius: 100px!impportant;
                --primary-text-color: var(--contrast20);

Didn’t you try to set a specific color for debug instead of css variables? Isn’t there a problem with them?

Thanks, that got me a bit further. I do think it’s related to an override somewhere in my dashboard, not within the card. I’m searching…

hey all,

super new to this but i want to do something simple, hopw to i format the date to a short date so it looks identical to the “start time” card? (i want to keep the time as is in am/pm)

my button-card styling is getting rusty…

Dec-04-2024 13-41-39

had this working with the html marquee tag before,

name: >
  [[[ return `<div style='display: flex;
                          padding: 0px 5px 0px 5px;
                         align-items: center;
                         background: transparent;'>
      <div></div>
      <marquee>
      <span style='color: var(--primary-color);align-items: center;'>${variables.cond}</span>
      </marquee>`; ]]]

Dec-04-2024 14-01-40

can even cut it short to

name: >
  [[[ return `<marquee> ${variables.cond} </marquee>`; ]]]
size: 100%
aspect_ratio: 1
styles:
  name:
    - display: flex;
    - padding: 0px 5px
    - font-size: 13px
    - font-weight: bold
    - color: var(--primary-color)
    - overflow: visible

and it still marquees nicely

but now want to replace that with the modern css translateX animation…

cant get the name to remain inside the padding of the card, not even if I set it explicitly with

    - display: flex;
    - padding: 0px 5px 0px 5px

the translateX ignores that and makes it overflow…

type: custom:button-card
template:
  - styles_cf_notification
  - styles_cf_notification_left
show_entity_picture: true
variables:
  cond: >
    [[[ return states['sensor.buienradar_woensdrecht'].attributes.weatherdescription; ]]]
show_name: true
name: >
  [[[ return variables.cond; ]]]
size: 100%
aspect_ratio: 1

extra_styles: |
  @keyframes marquee {
    from {transform: translateX(20%);}
    to {transform: translateX(-50%);}
  }
styles:
  name:
    - font-size: 13px
    - font-weight: bold
    - color: var(--primary-color)
    - overflow: visible
    - animation: >
        [[[ return variables.cond.length > 20? 'marquee 3s linear infinite' : 'none' ; ]]]

could anyone help me out seeing what’s wrong?

How to limit the translateX to remain inside the cards padding.

it’s not the overflow property, I need to set that as visible, or it will be truncated with the ..... if longer than the available space

Here’s my recipe. This is a gif capture; the actual result is much smoother.
Kept the states/variables/styles/extras, you can trim down as needed.
You can play with speed depending on your string lenght.
Took me while to figure out how to get that continuous loop without breaks.
doubling the string + 50% transition does the trick.
Hope this helps, cheers.

Animation

type: custom:button-card
name: |-
  [[[return `<div class="wrapper">
    <div class="marquee">
      <p>
        🚨TEMPERATURE:
        ${states['sensor.ss_temperature'].state}°C /
        ENERGY:
        ${ parseFloat(states['sensor.energymon3_home_power_total_kw'].state).toFixed(2) } kWh
      </p>
      <p>
        🚨TEMPERATURE:
        ${states['sensor.ss_temperature'].state}°C /
        ENERGY:
        ${ parseFloat(states['sensor.energymon3_home_power_total_kw'].state).toFixed(2) } kWh
      </p>
      </div>
      </div>`]]]
extra_styles: |
  .wrapper {
    max-width: 100%;
    overflow: hidden;
  }

  .marquee {
    white-space: nowrap;
    overflow: hidden;
    display: inline-block;
    animation: marquee 3s linear infinite;
  }

  .marquee p {
    display: inline-block;
  }

  @keyframes marquee {
    0% {
      transform: translatex(0);
    }
    100% {
      transform: translatex(-50%);
    }
  }
  @keyframes glow {
    50% {box-shadow: 0 0 20px 10px red;}
  }           
styles:
  card:
    - height: 3rem
    - padding: 5rem
    - background: black
    - animation: glow 1s linear infinite
  name:
    - font-size: 3rem
    - font-weight: bold
    - white-space: normal
    - background: black
    - color: red
4 Likes

thanks, experimenting a bit, and struggling to get the positioning on the button correctly. and need to force it using

styles:  name:
    - position: absolute
    - top: 73%
    - left: 5%
    - right: 5%

otherwise the scroll box simply isnt wide enough

… but its scrolling alright :wink:
I want to conditionally scroll and had

    - animation: >
        [[[ return variables.cond.length > 20? 'marquee 3s linear infinite' : 'none' ; ]]]

before as a test length. I really it should only scroll if the length of the string is longer than the available width, but for now I believe I can count the pixels :wink:
Andy thoughts how we can achieve conditional scrolling using the marquee class?

what exactly do you mean with:

I dont see you calculating the string length anywhere? or apply this in the styles?

fwiw, I now use this (left out all non relevant stuff):

type: custom:button-card
variables:
  cond: >
    [[[ return states['sensor.buienradar_woensdrecht'].attributes.weatherdescription; ]]]
show_name: true
name: >
  [[[return `<div class="wrapper">
               <div class="marquee"> <p> ${variables.cond}</p> </div>
             </div>`; ]]]
size: 100%
aspect_ratio: 1

extra_styles: |
  @keyframes marquee {
    from {transform: translateX(100%);}
    to {transform: translateX(-100%);}
  }

  .marquee {
    animation: marquee 3s linear infinite;
  }

styles:
  name:
    - position: absolute
    - top: 73%
    - left: 5%
    - right: 5%

to get this
Dec-06-2024 10-16-22

as you can see I forced the box for the wrapper, without the positioning it is

Dec-06-2024 10-17-46

What I wanted Is a continuous loop with no break. In your example above, the whole text has to be out of the frame before it starts again.
In my example, you will notice the text (“name” section) is duplicated once. So if you transition half of it left (-50%) and restart, you will get a continuous loop/no break.

right, I see what you mean now, thx.

I’ve moved some of that styling to the div and made it conditional (on a manual count of the max button width of 20…)

name: >
  [[[return variables.cond_length > 20
      ? `<div class="wrapper"
               style='margin: 0px 10px -15px 10px;'>
               <div class="marquee"> <p>${variables.cond}</p> </div>
             </div>`
       : variables.cond; ]]]

or, can also move it to the extra_styles for that element:

extra_styles: |
  @keyframes marquee {
    from {transform: translateX(100%);}
    to {transform: translateX(-150%);}
  }
  .marquee {
    animation: marquee 3s linear infinite;
    margin: -5px 10px -15px 10px;
  }

that styling moves the scrolling name to the desired spot more or less. Still feels hacky.

Here’s a trimmed down version for reference; just need to ensure the text is repeated twice.

type: custom:button-card
name: |-
  [[[return `
    <div class="marquee">
        🚨ANY TEXT YOU WANT HERE🚨ANY TEXT YOU WANT HERE
      </div>
      </div>`]]]
extra_styles: |

  .marquee {
    white-space: nowrap;
    display: inline-block;
    animation: marquee 3s linear infinite;
  }

  @keyframes marquee {
    0% {
      transform: translatex(0);
    }
    100% {
      transform: translatex(-50%);
    }
   
styles:
  card:
    - height: 5rem
    - padding: 2rem
    - background: black
  name:
    - font-size: 3rem
    - background: black
    - color: red

And you can use layout card to manage elements/locations/paddings/margins/etc:

Animation2

Using layout-card in a custom:button-card ? that is new to me, and seems a bit clumsy, when button-card has all the options to place the elements wherever we need them?

the trick I use from that class combination, is that the translateX is defined to the marquee class, which itself is inside the .wrapper.

without that .wrapper the traslateX overflows the name element itself, and with that, the paddings of the card itself.

keeping the marquee inside that wrapper, allows us to place it correctly.
that is why I decided to set the exact positioning on that element.

making the marquee properties truly dependent on the length and size of the screen (available space in the elements on Mobile and Desktop to name but 1 variable) would require a lot more work, and for this button that is overdoing it.

it is possible, and I have a few that do use that, see this How to make scroll-time/speed and TranslateX string length dependent in css animation - #14 by Mariusthvdb for some background…

For complex layouts, I tend to use layout-card instead of button-card as precisely positioning elements inside a button card can get tricky if you use multiple devices/screen sizes (ref: your link above). This is what I was referring to above, just a personal preference.

Core Update 2024.12.1 erro in flex Card

Custom element doesn’t exist: flex-horseshoe-card.
type: custom:flex-horseshoe-card
image

wrong thread, should be here