A different take on designing a Lovelace UI

yeah that’s the solution. Many Thanks
I ate hours there, I don’t understand enough javascript, I would never have thought of that

return variables.state === 'on' ? '45% -20% 0 0' : ' 8% -20% 0 0'

Thank you Thank you

of course, but its not animated only with color when the state is ON, its my Made in Shade
this is my “disgraced icon” it’s a bit ugly and deserves some love again when i have time

icon_pool:
    styles:
      custom_fields:
        icon:
          - width: 72%
    custom_fields:
      icon: >
        [[[
          
          let color;
          if (variables.state === 'on' && variables.timeout < 2000) {
            color = 'var(--primary-color)';
          } 
          if (variables.state === 'off' && variables.timeout < 2000) {
            color = '#a0a0a0';
          }
          if (variables.state === 'on' && variables.timeout > 2000) {
            color = 'var(--primary-color)';
          }
          if (variables.state === 'off' && variables.timeout > 2000) {
            color = '#a0a0a0';
          }
          let state = variables.state ? 'on' : null;
          return `
                <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
                 viewBox="0 0 480 480" style="enable-background:new 0 0 480 480;" xml:space="preserve">
                <path class="${state}" fill="${color}" d="M447.216,504.163h-64.784c-11.542,0-20.898-9.356-20.898-20.898V326.588h106.58v156.677
                C468.114,494.807,458.758,504.163,447.216,504.163z"/>
          
                <polygon fill="${color}" points="409.078,415.115 336.98,415.115 336.98,381.678 388.18,381.678 	"/>
                <rect x="190.694" y="33.959" fill="${color}" width="73.143" height="125.388"/>
                <polygon fill="${color}" points="91.429,504.163 60.082,504.163 60.082,478.041 101.878,478.041 	"/>
                <polygon fill="${color}" points="300.408,504.163 269.061,504.163 258.612,478.041 300.408,478.041 	"/>
                <path class="${state}" fill="${color}" d="M339.592,232.49H20.898c-7.214,0-13.061,5.847-13.061,13.061l0,0
                    c0,7.214,5.847,13.061,13.061,13.061h318.694c7.214,0,13.061-5.847,13.061-13.061l0,0
                    C352.653,238.337,346.806,232.49,339.592,232.49z"/>
           
                <path class="${state}" fill="${color}" d="M316.082,478.041H44.408c-11.542,0-20.898-9.356-20.898-20.898V258.612H336.98v198.531
                C336.98,468.685,327.624,478.041,316.082,478.041z"/>
           
                <path class="${state}" fill="${color}" d="M336.98,455.053v2.09c0,11.546-9.352,20.898-20.898,20.898H44.408
                    c-11.546,0-20.898-9.352-20.898-20.898v-2.09H336.98z"/>
                <path class="${state}" fill="${color}" d="M125.461,258.612v219.429H44.408c-11.546,0-20.898-9.352-20.898-20.898V258.612H125.461z"/>
           
                <path class="${state}" fill="${color}" d="M316.082,159.347H44.408c-11.542,0-20.898,9.356-20.898,20.898v52.245H336.98v-52.245
                C336.98,168.703,327.624,159.347,316.082,159.347z"/>
           
                <circle style="fill:#FFFFFF;" cx="227.265" cy="326.531" r="20.898"/>
                <circle style="fill:#FFFFFF;" cx="227.265" cy="410.122" r="20.898"/>
            
                <rect x="175.02" y="7.837" fill="${color}" width="104.49" height="26.122"/>
                <path class="${state}" d="M423.217,422.952l-30.694-49.11h-47.707v-108.08c8.999-2.329,15.673-10.494,15.673-20.21c0-5.583-2.173-10.831-6.119-14.778
                c-2.682-2.682-5.973-4.522-9.555-5.441v-45.087c0-15.845-12.891-28.735-28.735-28.735h-44.408v-45.704H256v45.704h-20.898v-43.637
                l82.934-49.96l-8.088-13.426l-38.275,23.057V41.796h15.673V0H167.184v41.796h15.673V151.51H44.408
                c-15.844,0-28.735,12.89-28.735,28.735v45.096C6.675,227.67,0,235.835,0,245.551c0,5.583,2.173,10.831,6.119,14.778
                c2.682,2.682,5.973,4.522,9.555,5.441v191.373c0,15.845,12.891,28.735,28.735,28.735h7.837V512h44.49l10.449-26.122h146.123
                L263.755,512h44.49v-26.122h7.837c15.844,0,28.735-12.89,28.735-28.735v-34.191H423.217z M383.836,389.515l11.102,17.763h-50.122
                v-17.763H383.836z M316.082,167.184c7.203,0,13.061,5.859,13.061,13.061v44.408h-80.98v15.673h91.429
                c1.397,0,2.709,0.543,3.695,1.529c0.986,0.986,1.53,2.299,1.53,3.696c0,2.881-2.344,5.224-5.224,5.224h-91.429v15.673h80.98v180.767
                H133.298V266.449h86.131v32.442c-12.046,3.42-20.898,14.512-20.898,27.64c0,15.845,12.891,28.735,28.735,28.735
                S256,342.375,256,326.531c0-13.128-8.852-24.219-20.898-27.64V167.184H316.082z M31.347,266.449h86.277v180.767H31.347V266.449z
                 M227.265,313.469c7.202,0,13.061,5.859,13.061,13.061s-5.859,13.061-13.061,13.061c-7.202,0-13.061-5.859-13.061-13.061
                S220.063,313.469,227.265,313.469z M182.857,15.673h88.816v10.449h-88.816V15.673z M198.531,41.796H256v35.19l-36.571,22.031v52.494
                h-20.898V41.796z M44.408,167.184h175.02v57.469H31.347v-44.408C31.347,173.042,37.206,167.184,44.408,167.184z M20.898,240.327
                h198.531v10.449H20.898c-1.397,0-2.709-0.543-3.695-1.529c-0.987-0.986-1.53-2.299-1.53-3.696
                C15.673,242.67,18.017,240.327,20.898,240.327z M86.123,496.327H67.918v-10.449h22.384L86.123,496.327z M292.571,496.327h-18.204
                l-4.18-10.449h22.384V496.327z M316.082,470.204H44.408c-5.138,0-9.58-2.989-11.711-7.314h295.096
                C325.661,467.215,321.219,470.204,316.082,470.204z"/>
                <path class="${state}" d="M227.265,381.388c-15.844,0-28.735,12.89-28.735,28.735c0,15.845,12.891,28.735,28.735,28.735S256,425.967,256,410.122
                C256,394.278,243.109,381.388,227.265,381.388z M227.265,423.184c-7.202,0-13.061-5.859-13.061-13.061s5.859-13.061,13.061-13.061
                c7.202,0,13.061,5.859,13.061,13.061S234.468,423.184,227.265,423.184z"/>
                <path class="${state}" d="M512,365.772h-36.049v-47.015H353.698v42.026h15.673V334.43h90.906v31.342v97.118v20.376
                c0,7.202-5.859,13.061-13.061,13.061h-64.784c-7.202,0-13.061-5.859-13.061-13.061v-47.25h-15.673v47.25
                c0,15.845,12.891,28.735,28.735,28.735h64.784c15.844,0,28.735-12.89,28.735-28.735V462.89H512V365.772z M496.327,447.216h-20.376
                v-65.771h20.376V447.216z"/>

             
              <style>
                @keyframes on {
                  0% {
                    transform: scale(0.85);
                  }
                  20% {
                    transform: scale(1.1);
                  }
                  40% {
                    transform: scale(0.95);
                  }
                  60% {
                    transform: scale(1.03);
                  }
                  80% {
                    transform: scale(0.97);
                  }
                  100% {
                    transform: scale(1);
                  }
                }
                .on {
                  animation: on 0.8s;
                  transform-origin: center;
                }
              </style>
                
            </svg>
          `;
        ]]]
 
1 Like

what is in your kids_climate then ?

I no longer use this approach for my ac so I can not provide the code, but it was the same AC popup that was provided by @Mattias_Persson with an added input select input to set the fan speed.

happy to help,

there might have been a way to get this working with the “low code” approach that you had taken, but as a developer I went straight to the code way and didn’t look into the low code approach.

this is what’s known as a ternary statement. its just a fancy inline if statement, you can read more in the javascript documentation.

with the same code, why is the result on my home assistant like this, please help

Capture.JPGddd

HI, maybe anyone can help, because obviously I´m to dumb to change the aspect ratio to fit it to a normal Full-HD screen.
I´ve already changed the size of the background.png and took a fresh browser to ensure the cache is clean.

The HTML-Tag seems to have the right size:

But the root element not :thinking:

@tommysusilo, welcome.

@Giblet has given a fantastic response below mine

This is a more recent graph card, the post you replied to is over a year old, lots of updates between now and then. A different take on designing a Lovelace UI - #4263 by karma

if you have any issues feel free to reach out.

@kamilk, I can’t tell what you are asking.
if you would like the layout to work on a larger screen then this is my solution

views:
  - type: custom:grid-layout
    path: 0
    layout:
      #default
      grid-gap: var(--custom-layout-card-padding)
      grid-template-columns: 0.7fr repeat(3, 1fr) 0.7fr
      grid-template-rows: 0 repeat(2, fit-content(100%)) 0fr
      grid-template-areas: |
        "sidebar  .           .       .       ."
        "sidebar  Living_Room  Rooms  Climate  footer"
        "sidebar  media       Home  People   footer"
        "sidebar   .       .   .  ."
      mediaquery: 
        #phone
        '(max-width: 800px)':
          grid-gap: calc(var(--custom-layout-card-padding) * 1.7)
          grid-template-columns: 0 repeat(2, 1fr) 0
          grid-template-rows: 0 repeat(5, fit-content(100%)) 0fr
          grid-template-areas: |
            ".  .           .        ."
            ".  sidebar     sidebar  ."
            ".  Living_Room  Climate   ."
            ".  Rooms      Home   ."
            ".  media       People    ."
            ".  footer      footer   ."
            ".  .           .        ."
        #portrait
        '(max-width: 1200px)':
          grid-gap: var(--custom-layout-card-padding)
          grid-template-columns: repeat(3, 1fr) 0
          grid-template-rows: 0 repeat(3, fit-content(100%)) 0fr
          grid-template-areas: |
            "sidebar  .           .       ."
            "sidebar  Living_Room  Climate  ."
            "sidebar  Rooms      Home  ."
            "sidebar  media       People   ."
            "sidebar  footer      footer  ."
            "sidebar  .           .       ."
        '(max-width: 1440px)':
          grid-gap: var(--custom-layout-card-padding)
          grid-template-columns: repeat(4, 1fr) 0
          grid-template-rows: 0 repeat(2, fit-content(100%)) 0fr
          grid-template-areas: |
            "sidebar  .           .       .      ."
            "sidebar  Living_Room  Rooms  Climate  ."
            "sidebar  media       Home  People   ."
            "sidebar  footer      footer  footer  ."
        theme: tablet

and I have added this to the team to stack the footer icons on larger screens


      #################################################
      #                                               #
      #                    FOOTER                     #
      #                                               #
      #################################################

      grid-layout$hui-horizontal-stack-card:
        $: |
          #root {
            flex-wrap: wrap;
            justify-content: center;
            overflow: visible;
            margin-top: -1.95vw !important;
            padding-bottom: 2.5em;
          }
          @media screen and (min-width: 1200px) {
            #root {
              flex-wrap: nowrap;
              justify-content: space-between;
              margin-top: -1.6vh;
              padding-bottom: 0;
            }
          }
          @media screen and (min-width: 1441px) {
            #root {
              flex-direction: column;
            }
          }
          /* phone */
          @media screen and (max-width: 800px) {
            #root {
              padding-top: 4vw !important;
            }
          }

posting YAML helps, the browser debug screenshots are fantastic but you should provide more context next time

I have created a more generalized “environment” card.
So first the environment.yaml (which should be in the button_card_templates directory)

environment:
  template:
    - base
    - circle
  state_display: >
    [[[ return '&nbsp;'; ]]]
  tap_action: !include /config/popup/environment.yaml
  double_tap_action: !include /config/popup/environment.yaml
  hold_action:
    action: none
  show_state: true
  variables:
    circle_input: >
      [[[
        if (entity) {
          return 100;
        }
      ]]]
    text_input: >
      [[[
        if (entity) {
          return entity.attributes.temperature === undefined ? null : Math.round(entity.attributes.temperature);
        }
      ]]]
    circle_input_unit: '°C'
  custom_fields:
    graph:
      card:
        type: sensor
        entity: >
          [[[ return entity.entity_id; ]]]
        graph: line
        card_mod:
          style: |
            .header, .value, .measurement {
              display: none !important;
            }
            :host {
              --ha-card-border-width: 0;
            }
    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:
      graph:
        - position: absolute
        - width: 100%
        - height: 100%
        - clip-path: inset(0 round var(--button-card-border-radius))
        - left: 0
        - bottom: 0
      icon:
        - width: 69%
        - fill: "#9da0a2"

Then in the lovelace.yaml you already use, you should “forward” the correct variables to this environment card
In my case this looks something like:

          - type: custom:button-card
            entity: sensor.es_bathroom_temperature
            name: Badkamer
            custom_fields:
              graph:
                card:
                  entities:
                    - sensor.es_bathroom_temperature
            template:
              - environment
              - icon_temp
            variables:
              popup_name: Badkamer

Then you might also want the popup environment.yaml card.
Keep in mind this is pretty specific to my setup, because it uses entity_id.replace to get the correct humidity sensor from the forwarded temperature sensor…
Meaning… In the lovelace.yaml I only have to forward the temperature sensor, and in the popup yaml it will “translate” it to the accompanying humidity sensor, which might only work with my specific environment sensor.
Anyhow:

action: fire-dom-event
browser_mod:
  service: browser_mod.popup
  data:
    title: > 
      [[[ return variables.popup_name ]]]
    card_mod:
      style:
        #popup header
        .:
    content:
      type: vertical-stack
      cards:

        - type: entities
          card_mod:
            class: content
            style: |
              #states {
                padding-top: 0.5em;
                padding-bottom: 0em;
              }
          entities:
            - name: Temperatuur
              entity: > 
                [[[ 
                  return entity.entity_id;
                ]]] 

            - name: Luchtvochtigheid
              entity: > 
                [[[ 
                  return entity.entity_id.replace("temperature", "humidity")
                ]]] 

            - name: Luchtdruk
              entity: > 
                [[[ 
                  return entity.entity_id.replace("temperature", "pressure")
                ]]] 
                
          footer:
            type: custom:apexcharts-card
            layout: minimal
            graph_span: 3h
            locale: en
            apex_config:
              chart:
                fontFamily: var(--primary-font-family)
                height: 180px
              tooltip:
                style:
                  fontSize: 14px
                x:
                  show: true
                  format: HH:mm
              xaxis:
                crosshairs:
                  show: false
              legend:
                fontSize: 14px
                fontWeight: 400
                height: 60
                offsetY: 20
                itemMargin:
                  horizontal: 25
                  vertical: 5
                formatter: |
                  EVAL: (seriesName, opts) => {
                    var arr = opts.w.globals.series[opts.seriesIndex],
                      value = arr[arr.length - 1];
                    return value == null
                      ? seriesName : "" ;
                  }
                markers:
                  width: 26
                  height: 23
                  customHTML:
                    - 'EVAL:() => {return `<ha-icon icon="mdi:water-percent" style="--mdc-icon-size: 23px;"></ha-icon>`}'
                    - 'EVAL:() => {return `<ha-icon icon="mdi:thermometer" style="--mdc-icon-size: 23px;"></ha-icon>`}'
                  fillColors:
                    - none
                    - none
              fill:
                type: gradient
                gradient:
                  type: vertical
                  opacityFrom: 0.8
                  opacityTo: 0
                  stops:
                    - 0
                    - 99
                    - 100
              stroke:
                width: 3
            all_series_config:
              type: area
              fill_raw: last
            series:
              - name: Humidity
                color: '#385581'
                entity: > 
                  [[[ 
                    return entity.entity_id.replace("temperature", "humidity");
                  ]]] 
              - name: Temperature
                color: green
                entity: > 
                  [[[ 
                    return entity.entity_id;
                  ]]] 

I suppose that should be all…

3 Likes

@masoncrawford1994 thanks for helping me find a solution

1 Like

@Giblet image
Thank you very much for your help, my problem is solved

HI Giblet,

thanks for the code So far everything works, but it doesn’t show the circles.

grafik

any idea why?

Ahh I probably forgot to mention there is also some change in the circle.yaml button_card_template file:
Again: it’s straying quite far from the original by Mathias
This might even start you on a rabbit hole, where you are going to need to change other button_card_templates / lovelace.yaml to make sure the circles show for them…

circle:
  styles:
    card:
      - --c-stroke-color-on: '#b0b0b0'
      - --c-stroke-color-off: '#313638'
      - --c-fill-color-on: none
      - --c-fill-color-off: rgba(255,255,255,0.04)
      - --c-stroke-width-on: 2.3
      - --c-stroke-width-off: 1.5
      - --c-stroke-width-dragging: 4
      - --c-font-color-off: '#97989c'
      - --c-font-color-on: '#25292a'
      - --c-font-size: 14px
      - --c-unit-font-size: 10.5px
      - --c-font-weight: 700
      - --c-letter-spacing: -0.02rem
    custom_fields:
      circle:
        - display: initial
        - width: 88%
        - margin: -3% 2% 0 0
        - justify-self: end
        - opacity: 1
  custom_fields:
    circle: >
      [[[
        if (entity) {
            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 || ' ',
                text = variables.text_input || ' ',
                unit = variables.circle_input_unit || ' ';
                if(text === ' ') {
                    text = variables.circle_input 
                }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *             CIRCLE              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            let circle = (state, input, unit, text) => {
                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: ${state ? 'var(--c-stroke-width-on)' : 'var(--c-stroke-width-off)'};
                        stroke: ${text !== '0d' && 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: ${text === '0d' ? 'var(--c-font-color-on)' : 'var(--c-font-color-off)'};
                      }
                      tspan {
                        font-size: var(--c-unit-font-size);
                      }
                      #circle_value, tspan {
                        text-anchor: middle;
                        dominant-baseline: central;
                      }
                    </style>
                    <circle id="circle_stroke" cx="25" cy="25" r="${r}" />
                    <text id="circle_value" x="51%" y="50%">${text}${tspan}${unit}</tspan></text>
                  </svg>

                  ${domain === 'light' && `
                      <input id="circle_slider" type="range" min="0" max="100" value="${input}">
                  `}
                  
                `;
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *              LIGHT              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            if (domain === 'light' && state) {

                // wait 0ms for shadow dom
                setTimeout(() => {

                    // then get elements
                    let elt = this.shadowRoot,
                        circle_slider = elt.getElementById('circle_slider'),
                        circle_value = elt.getElementById('circle_value'),
                        circle_stroke = elt.getElementById('circle_stroke');

                    // approximate position of thumb relative to circle
                    circle_slider.style.top = `${(circle_slider.value - 50) / 1.66 - 1}%`;

                    // debug position
                    let debug = false;
                    if (debug) circle_slider.style.opacity = 0.3;

                    // pass each event to handler
                    ['click', 'input', 'mousedown', 'mouseup', 'touchstart', 'touchend'].forEach((event) => {
                        circle_slider.addEventListener(event, handler, { passive: true })
                    });

                    function handler(event) {

                        // "this" refers to slider
                        if (event.target === this) {

                            // bypass button-card tap_action
                            event.stopPropagation();

                            // update circle_value
                            circle_value.innerHTML = `${this.value}${tspan}${unit}</tspan>`;

                            // update stroke
                            circle_stroke.style.strokeDashoffset = c - this.value / 100 * c;
                            circle_stroke.style.strokeWidth = 'var(--c-stroke-width-dragging)';
                            
                            // set cursor while dragging
                            if (event.type === 'mousedown' || event.type === 'input') {
                                this.style.cursor = 'grabbing';
                            } else {
                                this.style.cursor = 'grab';
                            }

                            // reset stroke width if value doesn't change
                            if (input == this.value && (event.type === 'click' || event.type === 'touchend'))
                                circle_stroke.style.strokeWidth = 'var(--c-stroke-width)';

                            // on release
                            if (event.type === 'mouseup' || event.type === 'touchend') {

                                // display loader if brightness is 0
                                if (circle_slider.value == 0 && elt.getElementById('loader')) {
                                    elt.getElementById('loader').style.display = 'initial';
                                    elt.getElementById('circle').style.display = 'none';
                                }

                                // set brightness
                                hass.callService('light', 'turn_on', {
                                    entity_id: entity.entity_id,
                                    brightness_pct: this.value
                                });
                            }
                        }
                    }
                }, 0);

                return circle(state, input, unit, text);
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *             PERSON              *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            else if (domain === 'person') {
                let time = c => {
                    let s = (c/1e3),
                        m = (c/6e4),
                        h = (c/36e5),
                        d = (c/864e5);
                    return s < 60
                        ? parseInt(s) + 's'
                        : m < 60 ? parseInt(m) + 'm'
                        : h < 24 ? parseInt(h) + 'h'
                        : parseInt(d) + 'd';
                };
                let input = states[variables.retain] === undefined || states[variables.retain].state === 'unavailable'
                        ? time(Date.now() - Date.parse(entity.last_changed))
                        : time(Date.now() - Date.parse(states[variables.retain].state)),
                    unit = ' ',
                    text = input;
                return circle(state, input, unit, text);
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *             LAUNDRY             *
            *                                 *
            * * * * * * * * * * * * * * * * * */

            else if (domain === 'sensor' && variables.time_input ) {
                let time = c => {
                    let s = (c/1e3),
                        m = (c/6e4),
                        h = (c/36e5),
                        d = (c/864e5);
                    return s < 60
                        ? parseInt(s) + 's'
                        : m < 60 ? parseInt(m) + 'm'
                        : h < 24 ? parseInt(h) + 'h'
                        : parseInt(d) + 'd';
                };
                let time_left = time(Date.parse(variables.time_input) - Date.now()),
                    unit = ' ',
                    text = time_left;
                    state = 'on'
                if (variables.time_input != 'False') {
                    if ( Date.parse(variables.time_input) > Date.now()){
                        return circle(state, input, unit, text);
                    }
                }
            }

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *             CLIMATE             *
            *                                 *
            * * * * * * * * * * * * * * * * * */

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

           /* * * * * * * * * * * * * * * * * *
            *                                 *
            *           Temperature           *
            *                                 *
            * * * * * * * * * * * * * * * * * */

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

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

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

I like your font style, size, and weight. Could you tell me what you are using and where to change it?

hi, keep in mind this comment that you replied to use old so my settings have changed between now and then. this is what that card looks like now.

This is all the locations that I think are needed to update the font and font size.

main font size is set in the themes file I have the following settings

    sidebar-font-size: 1.42vw
    button-card-font-size: 1.35vw
    footer-card-font-size: 1.22vw

and the font family itself is set in themes file mine is
primary-font-family: SF Pro Text, Roboto, system-ui

lastly the scaling for screen sizes is in button_card_templates/extra_styles.yaml

        /* magnification */
        :host {
          --card-portrait: 1.4;
          --card-phone: 2.271;
        }

and


        #name, #state {
          font-size: calc(var(--button-card-font-size)*0.85);
          font-weight: var(--button-card-font-weight);
          letter-spacing: var(--button-card-letter-spacing);
        }
        /* tablet */
        @media screen and (max-width: 1400px) {
          #name, #state {
            font-size: var(--button-card-font-size);
          }
        }
        /* portrait */
        @media screen and (max-width: 1200px) {
          #name, #state {
            font-size: calc(var(--button-card-font-size) * var(--card-portrait));
          }
        }
        /* phone */
        @media screen and (max-width: 800px) {
          #name, #state {
            font-size: calc(var(--button-card-font-size) * var(--card-phone));
          }
        }
1 Like

Thank you!

First of all thanks for all the work and inspiration!

I’m running in a problem that whenever i change something in the layout, sidebar or custom buttons there seems to be a border around the custom button and now the sidebar.

Any idea how to fix this? cant seem to find out where this border comes from.

Here is my code, used elements and idea’s from this post but always i get the border-line…

            
      - type: custom:vertical-stack-in-card
        view_layout:
          grid-area: sidebar
        mode: vertical
        cards:
          - type: markdown
            content: |
              {% set attributes = states.sensor.template_sidebar.attributes | default %}
              {% for template in attributes %}
              {{ state_attr('sensor.template_sidebar', template) }}
              {% endfor %}
          - type: "custom:hui-element"
            card_type: weather-forecast
            entity: weather.thuis
            show_forecast: false
          - type: "custom:hui-element"
            card_type: "custom:auto-entities"
            card:
              type: entities
            filter:
              include:
                - entity_id: sensor.sauna_temperature
                  state: <30
                - entity_id: sensor.anniversary_xyzs_birthday
                  state: <30
                - entity_id: sensor.anniversary_first_day_of_summer
                  state: <30
                - entity_id: sensor.anniversary_first_day_of_fall
                  state: <30
                - entity_id: sensor.anniversary_first_day_of_spring
                  state: <30
                - entity_id: sensor.anniversary_first_day_of_winter
                  state: <30
                - entity_id: sensor.anniversary_our_anniversary
                  state: <30
                - entity_id: sensor.anniversary_xyzss_birthday
                  state: <30
                - entity_id: sensor.anniversary_halloween
                  state: <30
                - entity_id: sensor.anniversary_christmas
                  state: <30
                - entity_id: sensor.anniversary_xyzs_birthday
                  state: <30
                - entity_id: sensor.anniversary_good_friday
                  state: <30
            show_empty: false
            sort:
              method: state
              numeric: true
          - type: grid
            view_layout:
              grid-area: sidebar
            columns: 4
            cards:
              - type: "custom:button-card"
                color_type: blank-card
              - type: button
                icon: mdi:robot-vacuum
              #                tap_action:
              #                  !include popup/sidebar_vacuum.yaml
              - type: button
                icon: mdi:cctv
              #                tap-action:
              #                  !include popup/sidebar_cameras.yaml
              - type: button
                icon: mdi:tune-vertical
      #                tap-action:
      #                  !include popup/sidebar_settings.yaml  
          - type: custom:button-card
            entity: sensor.sauna_temperature
            name: Sauna
#            tap_action: toggle switch.sauna
            state_display: >
              [[[ return '&nbsp;'; ]]]
            template:
              - base
              - circle
              - icon_climate
            custom_fields:
              icon: >
                <ha-icon icon="mdi:radiator" style="color: #9da0a2;"></ha-icon>
              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)}<tspan font-size="10">°C</tspan></text>
                      </svg>
                    `;
                  }
                ]]]
              graph:
                card:
                  type: sensor
                  entity: >
                    [[[ return entity.entity_id; ]]]
                  graph: line
                  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:
                graph:
                  - position: absolute
                  - width: 100%
                  - height: 100%
                  - clip-path: inset(0 round var(--custom-button-card-border-radius))
                  - left: 0
                  - bottom: 0

EDIT:

Found a workaround / solution:

Added this line to themes.yaml


    ha-card-border-width: 0
1 Like

hey @Mattias_Persson,
I got my self an Apple TV, and went to implement the YouTube thumbnail, (when I looked it needed the video ID, or more info than I could get out of my TV at the time), however there looks to be an issue with GitHub - matt8707/youtube-watching: 📦 Containerized flask app using yt-dlp, I only get the thumbnail for the most resent short that I have watched not the most recent video. I think YouTube recently made a change to the history page to show shorts in a new section.

is this something I have done to myself?
would this be an easy fix?
is this a known issues?

Hi Henkseegers
Did you manage to get this to work properly. I’ve tried a couple of times but with no success. Reverting to the default HA lovelace view, I cast perfectly well, but this customized, no luck. Any pointers?

Can you give advice on how you got the buttons aligned to the bottom?

I have my sidebar like this, but would like the person badged/picture entity aligned to the bottom. Any advice?

      #################################################
      #                                               #
      #                    SIDEBAR                    #
      #                                               #
      #################################################

      - type: custom:layout-card
        view_layout:
          grid-area: sidebar
        layout_type: grid
        layout:
          grid-template-columns: 5% 90% 5%
          grid-template-rows: auto
          grid-template-areas: |
            "left center right"
        cards:
          - type: vertical-stack
            view_layout:
              grid-area: center
            cards:
            
              - type: custom:button-card
                entity: sensor.template_sidebar
                template: sidebar
            
              - type: custom:simple-weather-card
                entity: weather.thuis
                name: ' '
                custom:
                  - temp: sensor.wupws_temp
                  - precipitation: sensor.wupws_preciptotal
                secondary_info:
                  - precipitation
                card_mod:
                  style: |
                    ha-card {
                    color: red;
                    width: 250px !important;
                    --primary-text-color: #6a7377;
                    }
                    
              - type: custom:layout-card
                layout_type: grid
                layout:
                  grid-template-columns: 10% 40% 40% 10%
                  grid-template-rows: auto
                  grid-template-areas: |
                    "links tim mieke rechts"
                cards:
                  - type: picture-entity
                    view_layout:
                      grid-area: tim         
                    entity: person.tim
                    image: /local/timkleur2.png
                    tap_action:
                      !include popup/tim.yaml
                    show_name: false
                    show_state: false
                    state_filter:
                      "home": grayscale(30%)
                      "not_home": grayscale(90%)
                    style: |
                      ha-card {
                        transform: scale(0.6,0.6)
                      }   
                  - type: picture-entity
                    view_layout:
                      grid-area: mieke
                      align-self: end
                    entity: person.mieke
                    image: /local/miekekleur2.png
                    show_name: false
                    show_state: false
                    tap_action:
                      !include popup/mieke.yaml
                    state_filter:
                      "home": grayscale(30%)
                      "not_home": grayscale(90%)
                    style: |
                      ha-card {
                        transform: scale(0.6,0.6)
                      }