Lovelace: Button card

@Mariusthvdb

the error is

Button-card template 'button_default_title' is missing!

type: 'custom:button-card' 
name: Home Assistant version info 
template: button_default_title

and

Button-card template 'button_updater' is missing!
type: 'custom:button-card'
template: button_updater
entity: binary_sensor.ha_update_available
name: Update available

so I think I’m just missing the template for button_updater and button_default_title

Thanks for your help

button_default_title:
  aspect_ratio: 12/1
  styles:
    card:
      - background-color: var(--background-color-off)
      - color: var(--text-color-off)
      - font-size: 20px
      - font-weight: bold

and

button_updater:
  template: button_body
  state:
    - value: 'on'
      styles:
        icon:
          - color: green
          - animation: blink 2s ease infinite
        state:
          - color: green
          - animation: blink 2s ease infinite
    - operator: default
      styles:
        icon:
          - color: grey
        state:
          - color: grey

Display of a timer directly on the card (another way ? tell me how :heart_eyes: ):
(we can imagine a change in speaker color, name, etc. depending on the state of the timer)

animate

Code
name: |
  [[[
    var finishes_at = new Date(states['timer.hacf_timer'].attributes.finishes_at);
    var remaining = states['timer.hacf_timer'].attributes.remaining;
    if (finishes_at == 'Invalid Date') {
      if (remaining) {
        var remaining_first_element = remaining.split(':');
        if (remaining_first_element[0].length < 2 ) {
          if (remaining_first_element[0] == '0' ) {
            remaining_first_element = remaining_first_element.splice(1);
          } else {
            remaining_first_element[0] = '0' + remaining_first_element[0];
          }

        }
          result = remaining_first_element.toString().replaceAll(',',':');

      } else {
        result = 'Sonorisation';
      }
    } else {
      var timestamp = finishes_at.getTime();
      var timestamp_now = Date.now();
      var difference_between = timestamp - timestamp_now
      var time_remaining = new Date(difference_between).toUTCString();
      var hours = time_remaining.split(' ');
      var hours_split = hours[4].split(':');
      if (parseInt(hours_split[0]) == 0 ) {
        hours_split = hours_split.splice(1);
      }
      var result = hours_split.toString().replaceAll(',',':');;
    }
    return result;
  ]]]
triggers_update:
  - sensor.aleatoire
show_name: true
icon: 'mdi:speaker'
styles:
  custom_fields:
    wave:
      - background-color: 'rgba(0, 0, 0, 0)'
      - position: absolute
      - right: 5%
      - top: 5%
      - font-size: 13px
      - line-height: 20px
      - display: |
          [[[
            if (states["input_boolean.test2"].state == 'on') return '';
            else return 'none';
          ]]] 
      - '--icon-color': |
          [[[
            if (states["input_boolean.test2"].state == 'on') return 'var(--mail-color)';
            else return 'var(--primary-color)';
          ]]]
  card:
    - border: 2px solid var(--primary-color)
    - border-radius: 10px
  icon:
    - color: var(--primary-color)
  name:
    - font-variant: small-caps
    - color: var(--primary-color)
custom_fields:
  wave: |
    [[[
     return `
       <div class="loader-container">
         <div class="loader-3">
          <div class="item-1"></div>
          <div class="item-2"></div>
          <div class="item-3"></div>
          <div class="item-4"></div>
          <div class="item-5"></div>
         </div>
       </div>`
    ]]]   
type: 'custom:button-card'
style: |
  .loader-3{
    width: 40px;
    height: 40px;
  }

  .loader-3 div {
    height: 100%;
    width: 3px;
    display: inline-block;
  }
  .loader-3 div .item-1{
    height: 50%;
  }
  .loader-3 .item-1 {
    animation: loader-3-first-last-div 1.2s infinite linear;
    background-color: red;
  }

  .loader-3 .item-2 {
    animation: loader-3-middle-div 1.2s infinite linear;
    animation-delay: -1.1s;
    background-color: darkorange;

  }

  .loader-3 .item-3 {
    animation: loader-3-middle-div 1.2s infinite linear;
    animation-delay: -1.0s;
    background-color: gold;

  }

  .loader-3 .item-4 {
    animation: loader-3-middle-div 1.2s infinite linear;
    animation-delay: -0.9s;
    background-color: green;
  }

  .loader-3 .item-5 {
    animation: loader-3-first-last-div 1.2s infinite linear;
    animation-delay: -0.8s;
    background-color: DarkOrchid;
  }

  @keyframes loader-3-first-last-div {
    25%,75% {
      transform: scaleY(0.2);
    }
    0%,50%,100%{
      transform: scaleY(0.6);
    }
  }  
  @keyframes loader-3-middle-div {
    25%,75% {
      transform: scaleY(0.4);
    }
    0%,50%,100%{
      transform: scaleY(1);
    }
  }  

A timer entity must be used:

sensor:
  - platform: random
    name: Aléatoire
    maximum: 999

We must add a random entity (which serves as a trigger to update the button-card):

sensor:
  - platform: random
    name: Aléatoire
    maximum: 999

You need an automation that runs every second to update this entity:

Automation to update entity
alias: mise a jour de sensor.aleatoire
description: Mise à jour pour affichage des timers sur les cartes
trigger:
  - platform: time_pattern
    seconds: '*'
condition: []
action:
  - service: homeassistant.update_entity
    target:
      entity_id: sensor.aleatoire
mode: restart

En français dans le texte

Edit: without random entity and automation but update directly in button-card with javascript (thanks @RomRider) :

3 Likes

@sd_dracula Can you post the code you came up with? This is exactly what I’m trying to do.

Cheers, Richard

Here you go

color: auto
color_type: card
entity: vacuum.robovac
hold_action:
  action: more-info
icon: 'mdi:robot-vacuum'
size: 50%
show_label: true
state:
  - color: white
    styles:
      card:
        - animation: blink 2s ease infinite
        - color: 'rgb(255, 0, 0)'
    value: 'on'
  - color: 'rgb(255, 255, 255)'
    value: 'off'
styles:
  grid:
    - grid-template-areas: '"t" "i" "n"'
    - grid-template-rows: min-content 1fr min-content
    - grid-template-columns: 1fr
  card:
    - height: 95px
    - width: 95px
  label:
    - font-size: 14px
  name:
    - font-size: 14px
custom_fields:
  t:
    card:
      type: 'custom:button-card'
      name: Timer
      entity: timer.robovac_timer
      margin: none
      show_name: false
      show_icon: false
      show_state: true
      styles:
        card:
          - box-shadow: none
          - font-size: 12px
        state:
          - color: |
              [[[
                if (states['timer.robovac_timer'].state == 'idle')
                  return "white";
                else
                  return "red";
              ]]]
type: 'custom:button-card'

2 Likes

Hi all,
i made a card with custom:button-card with my temperature sensors like this:


Is it possible to make a custom:button-card that change background color like mini-graph card with color thresholds concept? Like this:
52573150-cbd05900-2e19-11e9-9e01-740753169093
but working with background button card color?
Thanks in advance

1 Like

Yes, everything is possible if you develop some code to do it :slight_smile: but color threshold is not a simple javascript code.

This is how I build the color of the header in apexcharts-card based on a color_threshold list: https://github.com/RomRider/apexcharts-card/blob/68b1097cbd601b9c20cc6fc28146fe28a29b3d2e/src/apexcharts-card.ts#L986-L1008

Thanks, i’ll take a look at this Jcode.

The only way I’ve been able to make this work is with triggers_update: all
Is there any benefit to frontend workload to try to specify triggers_update for my variable?

button_card_templates:
  timer:
      variables:
        timer_var: "placeholder"
      triggers_update: all
      custom_fields:
        desc:
          card:
            type: custom:button-card
            entity: '[[[ return variables.timer_var ]]]'
            styles:
              state:
                - color: >
                    [[[
                      if (states[ variables.timer_var ].state == 'idle')
                        return "white";
                      else
                        return "red";
                    ]]]

TYPOs or NOT?

I found a very small typo on the github page, so I was thinking of posting a PR (just to prove my English professors wrong). Then I found a couple of other things I could improve in my estimation, but maybe not yours

First, the word “overload” is used four times in a context that I would normally consider to mean “override”, as in to replace an existing setting with a new one.

  1. Should I suggest changing overload to override?

Second, the variables section includes this example:

button_card_templates:
  variable_test:
    variables:
      var_name: "var_value"
      var_name2: "var_value2"
    name: '[[[ return variables.var_name ]]]'

That threw me for a minute since the example values look like some kind of entity.

  1. Should I suggest changing the example to this:
button_card_templates:
  variable_test:
    variables:
      var_name: "Example"
      var_name2: "Example 2"
    name: '[[[ return variables.var_name ]]]'
	
	etc. changing the rest of the example as appropriate

Cheers, Richard

Another animation card :

animate

code
custom_fields:
  circle: |
    [[[
     return `
       <div class="shadow_circle">
         <div class="shadow shadow_blue"></div>
         <div class="shadow shadow_red"></div>
       </div>
    `
    ]]]   
aspect_ratio: 5/2
card: null
hold_action:
  action: none
icon: 'mdi:alarm-light'
name: Alarme
show_name: true
styles:
  custom_fields:
    circle:
      - position: absolute
      - z-index: 1
      - height: inherit
      - width: inherit
      - top: 0
  card:
    - color: white
    - border: |
        [[[
          if (states["input_boolean.test2"].state == 'on') return '2px solid red';
          else return '2px solid var(--primary-color)';
        ]]]  
    - border-radius: 10px
  icon:
    - z-index: 2
    - top: 2%
    - color: '#fa3241'
  name:
    - z-index: 3
    - font-variant: small-caps
type: 'custom:button-card'
style: |
  .shadow_circle {
    height: 500px;width: 500px;
  }
  #card {
    {% if is_state('input_boolean.test2', 'on') %}
      animation: alert_border 2s infinite !important;
      -webkit-animation: alert_border 2s infinite !important;
    {% else %}
      border: 2px solid var(--primary-color);
    {% endif %}
    ;
  }
  #name {
    {% if is_state('input_boolean.test2', 'on') %}
      animation: alert_name 2s infinite !important;
      -webkit-animation: alert_name 2s infinite !important;
    {% else %}
      color: var(--primary-color);
    {% endif %}
    ;
  } #img-cell {
    {% if is_state('input_boolean.test2', 'on') %}
      animation: rotate_icon 1s !important;
      -webkit-animation: rotate_icon 1s !important;
      animation-delay: 1s;
    {% else %}
      color: var(--primary-color) !important;
    {% endif %}
    ;
  } #img-cell > #icon {
    {% if is_state('input_boolean.test2', 'on') %}
      animation: alert_icon 2s infinite ease !important;
      -webkit-animation: alert_icon 2s infinite ease !important;
      animation-delay: 1s !important;
    {% else %}
      color: var(--primary-color) !important;
    {% endif %}
    ;
  } .shadow {
    background-position: center center;
    background-size: 100% 100%;
    border-radius: 50%;
    bottom: 0px;
    left: 0px;
    margin: auto;
    right: 0px;
    top: 0px;
    position: absolute;
  } .shadow_blue {
    {% if is_state('input_boolean.test2', 'on') %}
      animation:alert_ani 2s infinite;
      -webkit-animation:alert 2s infinite;
      animation-delay: -1s;
    {% else %}
      display: none;    
    {% endif %}
  } .shadow_red {
    {% if is_state('input_boolean.test2', 'on') %}
      animation:alert_ani 2s infinite ease-in;
      -webkit-animation:alert 2s infinite;
    {% else %}
      display: none;
    {% endif %}
  } .shadow_blue {
    box-shadow: rgb(0 104 255 / 20%) 0px 0px 0px 0px, rgb(0 104 255 / 20%) 0px 0px 0px 800px inset;
  } .shadow_red {
    box-shadow: rgb(255 0 0 / 20%) 0px 0px 0px 0px, rgb(255 0 0 / 20%) 0px 0px 0px 800px inset;
  } @keyframes alert {
    0% {
      width: 0px;
      height: 0px
    }
    50% {
      width: inherit;
      height: inherit;
      box-shadow: 0 0 0 1px rgba(255, 0, 0, 0), 0 0 0 4px rgba(255, 0, 0, 0) inset;
    }
    51% {
      width: 0px;
      height: 0px;
    }
    100% {
      width: 0px;
      height: 0px;
    }
  } @keyframes rotate_icon {
    0% {
      transform: rotate(0);
    }
    100% {
      transform: rotate(3.142rad);
    }
  }
  @keyframes alert_icon {
    0%  {
      top: -2%;
      transform: rotate(3.142rad);
      color: blue;
    }
    24% {
      color: blue;
    }
    40% {
      color: blue;
    }    
    75% {
      color: #fa3241;
    }
    100% {
      top: -2%;
      transform: rotate(3.142rad);
      color: #fa3241;
    }
  } @keyframes alert_name {
    0%  {
      color: #fa3241;
    }
    24% {
      color: #fa3241;
      transform: scale(1.5);
    }
    55% {
      color: #fa3241;
      transform: scale(1);
    }    
    60% {
      color: blue  ;
    }    
    75% {
      color: blue;
      transform: scale(1.5);
    }
    100% {
      color: blue  ;
    }
  }
  @keyframes alert_border {
    0%  {
      border: 4px solid #fa3241;
    }
    55% {
      border: 4px solid #fa3241;
    }    
    60% {
      border: 4px solid blue;
    }    
    100% {
      border: 4px solid blue;
    }
  }

10 Likes

20210307_225508

Perfect in picture-elements card :

code
entity: input_boolean.wakestatus_1
show_icon: false
show_state: false
styles:
  toogle:
    - transform: translateX(26px)
  card:
    - width: 60px
    - height: 40px
    - background: transparent
    - box-shadow: none
custom_fields:
  toogle: |
    [[[
     return `
       <div class="switch">
            <span class="toggle-thumb">
                 <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" style="fill:#4ADE80;transform:;-ms-filter:"><path d="M10 15.586L6.707 12.293 5.293 13.707 10 18.414 19.707 8.707 18.293 7.293z"></path></svg>
                 <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" style="fill:#F87171;transform:;-ms-filter:"><path d="M16.192 6.344L11.949 10.586 7.707 6.344 6.293 7.758 10.535 12 6.293 16.242 7.707 17.656 11.949 13.414 16.192 17.656 17.606 16.242 13.364 12 17.606 7.758z"></path></svg>
            </span>
       </div>`
    ]]]   
type: 'custom:button-card'
style: |
  #toogle .switch {
      display: inline-block;
      width: 60px;
      height: 34px;
      position: relative;
    }
  .toggle-thumb {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      justify-content: space-between;
      align-items: center;
      background-color: #3f3F3f;
      border-radius: 40px;
      cursor: pointer;
    }
  .toggle-thumb:before {
       content: "";
       height: 26px;
       width: 26px;
       position: absolute;
       left: 4px;
       bottom: 4px;
       border-radius: 50%;
       background-color: #EFEFEF;
       transition: .4s all ease;
     }
  .checkbox {
      opacity: 0;
      width: 0;
      height: 0;
      z-index: 100;
    }
  .toggle-thumb:before {
    {% if is_state('input_boolean.wakestatus_1', 'on') %}
      transform: translateX(26px);
    {% endif %}
  }

Source

7 Likes

another silly question, but ive simply never run into it yet… Need a button with default tap_action on a cover (group) and a hold_action to navigate. The tap action must be guarded (with the confirmation). The navigate should be without confirmation.

I tried it like this, but now it confirms both the tap_action And the hold action.

type: custom:button-card
template:
  - button_summary
  - styles_summary_alert
entity: cover.bedrooms
name: Covers
confirmation:
  text: >
    [[[ return `Are you sure you want to toggle ${entity.attributes.friendly_name}?` ]]]

hold_action:
  action: navigate
  navigation_path: /lovelace/covers

You need to put confirmation under the action, not in root of the card.

tap_action:
  action: toggle
  confirmation:
    text: >
      [[[ return `Are you sure you want to toggle ${entity.attributes.friendly_name}?` ]]]

Leave it out under hold_action so that skips the confirmation.

Yah, I hoped I could use the default tap action, but I guess using a confirmation on that makes it non default :wink:

Another thing I see is, when the tap action is executed, I get a pop up window with extra controls (which of course is the more-info popup of the cover entity in the card) instead of a direct toggle action on that entity… Is that expected?

That shouldn’t happen, since toggle and more-info are 2 different actions. When I toggle my curtains, it just toggles it, no popup. I use hold_action for popups. I have defined tap_action and hold_action. Perhaps it’s buggy when you don’t define them and want to use default?

guess it was my error of the tap_action. using

type: custom:button-card
template:
  - button_summary
  - styles_summary_alert
entity: cover.bedrooms
name: All Covers
tap_action:
  action: toggle
  confirmation:
    text: >
      [[[ return `Are you sure you want to toggle all covers?` ]]]
hold_action:
  action: navigate
  navigation_path: >
    [[[ return (window.location.pathname.split('/')[2] == 'covers')
      ? null : '/lovelace/covers'; ]]]

now makes it all happen. thanks!

Mar-08-2021 11-59-05
great this is! thanks for sharing.
Ive adapted my own alarm button with this, and it is almost like your example.

Skip that about the border, I missed the else…

Now I only need to figure out how to get the pulsating circle be more visible on my smaller button…

edit:
this will do for now :wink:

Mar-08-2021 12-17-44

2 Likes

These css properties (color / opacity in percent) :slight_smile:

    .shadow_blue {
    box-shadow: rgb(0 104 255 / 20%) 0px 0px 0px 0px, rgb(0 104 255 / 20%) 0px 0px 0px 800px inset;
  } .shadow_red {
    box-shadow: rgb(255 0 0 / 20%) 0px 0px 0px 0px, rgb(255 0 0 / 20%) 0px 0px 0px 800px inset;
  }

yes, thanks, I will experiment there. Had a dark background that was in the way of the pulsating circle also, so I changed that already.

Its very nice to be able to do all these eyecatching stylings, so please dont hold back if you have more great examples :wink: