Lovelace: Button card

Thanks RomRider - very annoying, but does explain why nothing I tried worked (I should have asked yesterday and saved myself many hours!)

@RomRider

i tried it with

aspect_ratio: 1/0.3
color: 'rgb(47, 186, 229)'
icon: 'fas:couch'
label: >
  [[[ return states['sensor.heizung_wohnzimmer_temperature'].state + 'Ā°C / ' + 
  states['sensor.heizung_wohnzimmer_humidity'].state + '%' ]]]
name: Wohnzimmer
show_label: true
size: 100%
styles:
  img_cell:
    - justify-content: start
    - align-items: start
  card:
    - color: '#101012'
  icon:
    - justify-self: start
  label:
    - color: '#adadb7'
    - font-size: 13px
    - padding-top: 5px
  name:
    - justify-self: center
    - margin-bottom: 5%
    - font-size: 18px
    - font-weight: bold
    - font-family: 'Roboto,sans-serif'
tap_action:
  action: navigate
  navigation_path: /lovelace/wohnzimmer
type: 'custom:button-card'

But the icon is still centered:

image

Hi dave, Share your entire configuration for the button. Iā€™ve come up with work arounds for this issue and it should work for you. I just need to see your entire button config.

as far as I understand GitHub - thomasloven/lovelace-layout-card: šŸ”¹ Get more control over the placement of lovelace cards. this works for full views, and not for a ā€˜Cardā€™ within which one needs to show aligned items in a grid? Of course, I say this without having used the layout card myself yet.

what I imagined was the button-cards-in-card to allow for a grid config, and use columns: to determine the horizontal grid (maybe also have an ā€˜autoā€™ settingā€¦) and then show buttons in those grid positions. (which would in fact be a copy of the former tiles card)

allow for a column_span: to have it use more than 1 position, and also use row: and row_span: to do the same for the vertical positioning of the buttons.

this config could then be used on a view, instead of ā€˜beingā€™ a view?

You can use layout-card ā€œas a cardā€ also and it works great, not only ā€œas a viewā€ :slight_smile:

Hi petro - please see below. Still a bit of a work in progress. Basically I am trying to align the icon in the button-card section, with the min details in mini-graph-card. I know I can do it all in mini-graph-card, but sometimes I want to show different info, or have different layouts over all, so find this the best solution given button-card's flexibilty.

              - type: custom:button-card
                entity: sensor.landing_sensor_temperature
                icon: mdi:thermometer
                show_state: false
                show_label: false
                show_last_changed: false
                name: Temperature
                tap_action:
                  action: more-info
                styles:
                  card:
                    - padding: 16px 1px 0px 0px
                    - border-right: 1px solid var(--primary-background-color)
                  grid:
                    - grid-template-areas: '"i n" "graph graph"'
                    - grid-template-columns: min-content 1fr
                    - grid-template-rows: 2em auto
                  img_cell:
                    - padding-left: 8px
#                    - align-self: start
#                    - text-align: start
                    - justify-content: start # horizontal
                    - align-items: start # vertical
                    - width: 40px
                    - height: 40px
                    - border: 1px dashed red
                  icon:
#                    - justify-content: start # horizontal
#                    - align-items: start # vertical
                    - height: 24px
                    - width: 24px
                    - border: 1px dashed green
                  name:
                    - padding-right: 16px
                    - justify-self: end
                    - font-size: var(--paper-font-body1_-_font-size)
                    - font-weight: var(--paper-font-body1_-_font-weight)
                custom_fields:
                  graph:
                    card:
                      type: custom:mini-graph-card
                      entities:
                        - entity: sensor.landing_sensor_temperature
                      show:
                        name: false
                        icon: false
                        extrema: true
                        average: true
                      line_color: rgb(100, 149, 237)
                      align_state: center
                      line_width: 2
                      font_size: 75
                      decimals: 1
                      hour24: true

image

You can see the green border is aligned with the Min text below from mini-graph-card, but because the icon is centered its out of alignment and my OCD goes mad :slight_smile: I can remove the padding, which will align this icon, but then when I have a full width icon, its too far left, and as my ultimate goal is to template these, I canā€™t be faffing with padding depending on the icon used each time.

Thanks for any help!

1 Like

Ah, so what you should do is take the mini-graph-card out of the button card. Use a picture elements card and place the mini-graph-card behind the button card. Itā€™s a complciated setup but youā€™ll end up with a grid thatā€™s separated from the mini-graph card. Hereā€™s a full card that I built that uses that.

# lovelace_gen

{% set color = color|default('var(--paper-item-icon-active-color)') %}
{% set ratio = ratio|default('1/1') %}
{% set imgratio = 'transparent_' ~ 'to'.join(ratio.split('/')) ~ '.png' %}

type: picture-elements
image: /local/images/{{ imgratio }}
style: |
  ha-card { 
    border-radius: 15px;
  }
elements:
- type: custom:mini-graph-card
  style:
    top: 40%
    left: 50%
    width: 100%
    height: 80%
    translate: translate(-50%, -50%)
    '--paper-card-background-color': 'rgba(0, 0, 0, 0.0)'
    '--ha-card-background': "rgba(0, 0, 0, 0.0)"
    '--ha-card-box-shadow': 'none'
    z-index: 3
    pointer-events: none
  entities:
    - {{ entity }}
  group: true
  points_per_hour: 1
  hour24: true
  line_color: {{ color }}
  line_width: 10
  hours_to_show: 24
  update_interval: 600
  show:
    name: false
    icon: false
    state: false
    points: false
    legend: false
    average: false
    extrema: false
    labels: false
    fill: false
    labels_secondary: false
    name_adaptive_color: false
    icon_adaptive_color: false
- type: custom:button-card
  style:
    top: 50%
    left: 50%
    width: 100%
    translate: translate(-50%, -50%)
  aspect_ratio: {{ ratio }}
  entity: {{ entity }}
  icon: {{ icon }}
  color: var(--paper-item-icon-color)
  show_name: true
  show_label: true
  show_icon: true
  show_last_changed: true
  size: 70%
  tap_action:
    action: more-info
    haptic: light
  styles:
    icon:
    - opacity: 0.3
    - width: 100%
    img_cell:
    - top: 0%
    - left: 30%
    - position: absolute
    - z-index: 2
    grid:
    - grid-template-areas: '"info info" "n n" "l l"'
    - grid-template-columns: 40% 1fr
    - grid-template-rows: 1fr min-content min-content
    - position: relative
    card:
    - padding: 10px
    - z-index: 1
    name:
    - justify-self: start
    - align-self: end
    - font-weight: bold
    - font-family: Helvetica 
    - font-size: 12px
    - text-align: start
    - background-image: linear-gradient(to right, white 0%, white 80%, rgba(0,0,0,0))
    - -webkit-background-clip: text
    - -webkit-text-fill-color: transparent
    - position: relative
    - display: inline-block
    - width: 100%
    - align-content: start
    - text-align: start
    - text-overflow: unset
    - z-index: 5
    label:
    - justify-self: start
    - align-self: end
    - font-weight: bold
    - font-family: Helvetica 
    - font-size: 12px
    - text-align: start
    - background-image: linear-gradient(to right, var(--paper-item-icon-color) 0%, var(--paper-item-icon-color) 80%, rgba(0,0,0,0))
    - -webkit-background-clip: text
    - -webkit-text-fill-color: transparent
    - position: relative
    - display: inline-block
    - width: 100%
    - align-content: start
    - text-align: start
    - text-overflow: unset
    - z-index: 5
    custom_fields:
      info:
      - align-self: start
      - width: 40%
      - z-index: 5
  custom_fields:
    info: >
      [[[

        var entity_id = (entity === undefined) ? 'Invalid Entity' : entity.entity_id;
        var statestr = (entity === undefined || entity.state === undefined) ? null : entity.state;
        var units = (statestr && entity.attributes.unit_of_measurement) ? entity.attributes.unit_of_measurement : null;
        var date = (statestr && entity.attributes.device_class === 'timestamp') ? new Date(statestr) : null;
        var value;
        if (statestr && date === null) {
          if (statestr.split('.').length - 1 <= 1){
            var test = parseFloat(parseFloat(statestr).toFixed(2));
            value = (isNaN(test)) ? null : test;

            // test if units are in the state because some sensors are stupid.  Looking
            //   at you synology.

            const expr = /[^-.0-9]+/;
            var has_units = expr.test(statestr.trim());

            // console.log(`${entity_id}: "${statestr}" ${matches}`);
            if (value && has_units)
              units = statestr.replace(/[.0-9]+/, ''); 
          }
        }

        // console.log(`${entity_id}: ${statestr}, ${units}, ${date}, ${value}`);

        const length = 50;
        const width = 3;
        var radius = length / 2;

        if (date){
          let now = new Date();
          var tdelta = Math.floor((now - date)/1000);

          // console.log(`${entity_id}: ${tdelta}`);

          function plural(descriptor, divisor){
            var ret = Math.floor(tdelta/divisor);
            return (ret == 1) ? [ret, descriptor] : [ret, `${descriptor}s`];
          }

          var values;
          if (tdelta < 60)
            values = plural('second', 1);
          else if (tdelta < 60 * 60)
            values = plural('minute', 60);
          else if (tdelta < 60 * 60 * 24)
            values = plural('hour', 60 * 60);
          else if (tdelta < 7 * 60 * 60 * 24)
            values = plural('day', 60 * 60 * 24);
          else
            values = plural('week', 7 * 60 * 60 * 24);

          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}" fill="{{ color }}" />
              <text x="50%" y="43%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${values[0]}<tspan x="50%" dy="1.2em" font-size="10" font-weight="normal" >${values[1]}</tspan>
              </text>
            </svg>
            `;
        }

        else if (value && units && units === '%') {
          radius = (length - 3) / 2;
          const circumference = radius * 2 * Math.PI;
          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}" fill="none" stroke="var(--paper-item-icon-color)" opacity="0.5" stroke-width="${width}" />
              <circle style="
                  transform: rotate(-90deg);
                  transform-origin: 50% 50%;
                  stroke-dasharray: ${circumference};
                  stroke-dashoffset: ${circumference - value / 100 * circumference};
                "
                id="c_brightness" cx="25" cy="25" r="${radius}" stroke="var(--paper-item-icon-active-color)" stroke-width="${width}" fill="none" stroke-linecap="round" />
              <text x="50%" y="54%" fill="var(--primary-text-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan font-size="10" font-weight="normal" >%</tspan>
              </text>
            </svg>
            `;
          }
        else if (value && units && units.includes('Ā°')) {
          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}" fill="{{ color }}" />
              <text x="50%" y="54%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan font-size="10" font-weight="normal" >${units}</tspan>
              </text>
            </svg>
            `;
        }

        else if (value && units && units.length > 1) {
          var y = (String(value).length > units.length) ? "50%" : "43%";
          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}" fill="{{ color }}" />
              <text x="50%" y="${y}" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan x="50%" dy="1.2em" font-size="10" font-weight="normal" >${units}</tspan>
              </text>
            </svg>
            `;
          }

        else if (value) {
          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}" fill="{{ color }}" />
              <text x="50%" y="54%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan font-size="10" font-weight="normal" >${units}</tspan>
              </text>
            </svg>
            `;
        }
        return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}" fill="{{ color }}" />
              <text x="50%" y="54%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${statestr}
              </text>
            </svg>
            `; 
      ]]]

Just use it as a refernce to help out your style. Thereā€™s a few key elements that are required to make this work properly though.

  1. pointer-events: none on the mini-graph-card is required if you intend to click on the button. Otherwise youā€™ll have ā€˜dead spotsā€™ when clicking that do nothing.

  2. The man card background is the picture elements. That means that both the backgrounds for mini-graph-card and button-card must be transparent with no shadow. Thatā€™s provided with the following styles:

    '--paper-card-background-color': 'rgba(0, 0, 0, 0.0)'
    '--ha-card-background': "rgba(0, 0, 0, 0.0)"
    '--ha-card-box-shadow': 'none'
  1. Youā€™ll need to mess with the z order. Meaning, the order in which items appear. You can put the icon BEHIND the minigraph card if you like by adjusting the z order even though they are in different cards. Z order is controlled with the property z-index. Keep it simple, 1 will be the lowest where N will be the highest.
9 Likes

Wowser! Thanks petro, I will have a proper read through that and digest, but think I get the general idea. Appreciate the help :+1:

1 Like

Yes, Iā€™ve ā€˜borrowedā€™ these buttons and am using them. I think they are great. And once you get your head around how they work can be adjusted for almost anythingā€¦

@petro, when you gave me the code you said you were tidying them all up. Did that ever get done? Iā€™d like to see if I missed anything I can improve on.

image
image
image image
image image

Thatā€™s the current version. Thereā€™s a few issues but it should work if you use correct entity_idā€™s.

Just in the interest of completeness - on one of my old HA installs (0.93.2) I used FontAwesome icons. I could have sworn on that install I had left aligned the icon using button-card at some point - so I just checked, and indeed it does:

image

With everything that is going on with Home Assistant, I had decided to try and keep my re-install as stock as possible (that lasted all of 5 minutes!), so wanted to use mdi and not FontAwesome . Given I am already down the custom card route, I might as well give FontAwesome a try on 0.110x before I delve into petroā€™s alternative.

I tried it several times with all your tips but the icon does not take its way to the left ;-(

Could you help me once again?

What did you wind up doing? I was just thinking about doing the same thing! Would love to see your example.

This is the code for the three buttons inside the button card

custom_fields:
  down:
    card:
      type: 'custom:button-card'
      entity: >
        [[[ return entity.entity_id; ]]]
      icon: 'mdi:arrow-down'
      tap_action:
        action: call-service
        service: cover.close_cover
        service_data:
          entity_id: >
            [[[ return entity.entity_id; ]]]
      styles:
        icon:
          - opacity: >
              [[[
                if (entity.attributes.current_position > 0) return '1';
                else return '0.3';
              ]]]
  up:
    card:
      type: 'custom:button-card'
      entity: >
        [[[ return entity.entity_id; ]]]
      icon: 'mdi:arrow-up'
      tap_action:
        action: call-service
        service: cover.open_cover
        service_data:
          entity_id: >
            [[[ return entity.entity_id; ]]]
      styles:
        icon:
          - opacity: >
              [[[
                if (entity.attributes.current_position < 100) return '1';
                else return '0.3';
              ]]]
  stop:
    card:
      type: 'custom:button-card'
      entity: >
        [[[ return entity.entity_id; ]]]
      icon: 'mdi:stop'
      tap_action:
        action: call-service
        service: cover.stop_cover
        service_data:
          entity_id: >
            [[[ return entity.entity_id; ]]]
      styles:
        icon:
          - opacity: 1
styles:
  grid:
    - grid-template-areas: '"s s s s s" ". up stop down ." "n n n n n"'
    - grid-template-rows: min-content 1fr min-content
    - grid-template-columns: 5% 1fr 1fr 1fr 5%

Iā€™ve just created something like that:

How I can remove unnecessary spaces between cards?
When I edit lovelace UI I can see this:

As You can see there is a lerge blank space :frowning:
I donā€™t know hot to get rid of it.

1 Like

You gotta share your config for one of those to help

If you define the width and the height of the card, thatā€™s what you get. Better use aspect_ratio instead.

This is an example:

type: vertical-stack
cards:
  - type: 'custom:button-card'
    color_type: auto
    name: Rodzice
    entity: sensor.temperature_158d00022c7127
    show_state: true
    styles:
      card:
        - background-color: '#000044'
        - width: 165px
        - height: 80px
        - border-radius: 10%
        - padding: 3%
        - font-size: 15px
        - text-shadow: 0px 0px 5px black
      name:
        - position: absolute
        - left: 15px
        - top: 15px
        - font-size: 20px
      icon:
        - height: 25px
        - position: absolute
        - left: 80px
        - top: 15px
        - color: |
            [[[
              if (entity.state < 21) return 'rgb(102, 153, 255)';
              if (entity.state >= 25) return 'red';
              else return 'white';
            ]]]
      state:
        - position: absolute
        - left: 100px
        - top: 50px
        - color: |
            [[[
              if (entity.state < 21) return 'rgb(102, 153, 255)';
              if (entity.state >= 25) return 'red';
              else return 'white';
            ]]] 
      custom_fields:
        wilgotnosc:
          - position: absolute
          - left: 10px
          - top: 45px
          - '--text-color-sensor': >-
              [[[ if (states["sensor.humidity_158d00022c7127"].state <40) return
              "red"; if (states["sensor.humidity_158d00022c7127"].state >60)
              return "rgb(102, 153, 255)"; else return "white" ;]]]
    custom_fields:
      wilgotnosc: >
        [[[ return `<ha-icon icon="mdi:water-percent" style="width: 20px;
        height: 20px;"></ha-icon><span style="color:
        var(--text-color-sensor);">${states['sensor.humidity_158d00022c7127'].state}%</span>`
        ]]]
  - type: horizontal-stack
    cards:
      - type: 'custom:button-card'
        entity: light.rodzice
        color_type: auto
        icon: 'mdi:lamp'
        name: Lampka
        state:
          - value: 'on'
            color: red
          - value: 'off'
            color: white
        show_last_changed: true
        styles:
          name:
            - font-size: 80%
          label:
            - font-size: 60%
          card:
            - width: 80px
            - height: 80px
            - border-radius: 10%
            - padding: 3%
            - font-size: 15px
            - text-shadow: 0px 0px 5px black
      - type: 'custom:button-card'
        entity: light.gateway_light_7811dc6c98d7
        color_type: auto
        icon: 'mdi:circle-slice-8'
        name: Bramka
        state:
          - value: 'on'
            color: red
          - value: 'off'
            color: white
        show_last_changed: true
        styles:
          name:
            - font-size: 80%
          label:
            - font-size: 60%
          card:
            - width: 80px
            - height: 80px
            - border-radius: 10%
            - padding: 3%
            - font-size: 15px
            - text-shadow: 0px 0px 5px black
  - type: horizontal-stack
    cards:
      - type: 'custom:button-card'
        entity: binary_sensor.natgas_sensor_158d00024f11d2
        color_type: auto
        name: Gaz
        show_state: true
        show_last_changed: true
        styles:
          name:
            - font-size: 80%
          state:
            - font-size: 70%
            - position: relative
            - left: 0%
            - top: '-00%'
          label:
            - font-size: 60%
          icon:
            - color: |
                [[[
                 if (states['binary_sensor.natgas_sensor_158d00024f11d2'].state == 'on')
                  return "red";
                 return "white";
                ]]]
          card:
            - width: 80px
            - height: 80px
            - border-radius: 10%
            - padding: 3%
            - font-size: 15px
            - text-shadow: 0px 0px 5px black
      - type: 'custom:button-card'
        entity: binary_sensor.door_window_sensor_158d0001b706f5
        color_type: auto
        state:
          - value: 'on'
            color: red
          - value: 'off'
            color: white
        show_last_changed: true
        styles:
          name:
            - font-size: 80%
          label:
            - font-size: 60%
          card:
            - width: 80px
            - height: 80px
            - border-radius: 10%
            - padding: 3%
            - font-size: 15px
            - text-shadow: 0px 0px 5px black
1 Like

yep, as @RomRider said, youā€™re defining the height/width. Remove that and use the aspect ratio field.

Can You be more specific? Can You show me how to use aspect_ratio to achieve similar effect as Iā€™ve done with width and height parameters ?