A different take on designing a Lovelace UI

Thanks.

If anyone has any idea about how to implement a multi-storey grid layout I’ll be grateful to learn!

Thanks. It helps a lot. Is it possible to make the conditional_media have media_player.media_next_track,too?

Hi @RoRoW

I like your temp card. can you please share “climate and icon_temperature” template. And also probably climate_livingroom.yaml?

1 Like

Beautiful work. I’m basically stealing your design but not your code, trying to piece things together in a way that makes sense to my less technically inclined brain. First and foremost, thanks for sharing.

Mini question if you have the time: Could you point me to the style/card mod code you’re using to style the popup windows? I’m trying to increase the border radius for the entire popup card, but I’m having a hard time accessing the right tags.

Thanks!
-J

Hi, is anyone know how to change the size of text? Mine is like this

I’m using this UI for a while now and just starting to use the latest one ( August). Is it far to say that the issue with the custom swipe card cutting off the last bit of the cards isnt fix yet?
Or do i miss something?

I can only replicate when placing swipe card outside of layout card

1 Like

Use button card instead of markdown #august-2021

card-mod-more-info-yaml:

sure :slight_smile:

The climate tempate is based on “base_2” template and on “additional_values” template:

base_2:

  #################################################
  #                                               #
  #                     BASE_2                    #
  #                                               #
  #################################################
  base_2:
    variables:
      state: >
        [[[ return entity === undefined || entity.state; ]]]
      timeout: >
        [[[ return entity === undefined || Date.now() - Date.parse(entity.last_changed); ]]]            
    aspect_ratio: 1/1
    show_state: true
    show_icon: false
    state_display: >
      [[[ if (variables.state === true) return 'unknown'; ]]]
    tap_action:      
      animation_card: |
        [[[
          const animation_speed_ms = 900;
          const animation = `card_bounce ${animation_speed_ms}ms cubic-bezier(0.22, 1, 0.36, 1)`;
          this.shadowRoot.getElementById("card").style.animation = animation;
          window.setTimeout(() => {
            this.shadowRoot.getElementById("card").style.animation = "none";
          }, animation_speed_ms)
        ]]]
      action: toggle
      haptic: medium
    styles:
      grid:
        - grid-template-areas: |
            "icon  values"
            "n     n"
            "s     s"
        - grid-template-columns: repeat(2, 1fr)
        - grid-template-rows: auto repeat(2, min-content)
        - gap: 2%
        - align-items: start
      name:
        - justify-self: start
        - line-height: 105%
      state:
        - justify-self: start
        - line-height: 105%           
      card:
        - font-family: Sf Display
        - border-radius: var(--custom-button-card-border-radius)
        - -webkit-tap-highlight-color: rgba(0,0,0,0)
        - transition: none        
        - padding: 8%
        - --mdc-ripple-color: >
            [[[
              return (variables.state === 'on' || variables.state === 'home') ?
                'rgb(0, 0, 0)' :
                'rgba(255, 255, 255, 0.3)';
            ]]]
        - color: >
            [[[
              return (variables.state === 'on' || variables.state === 'home') ?
                'rgba(0, 0, 0, 0.6)' :
                'rgba(255, 255, 255, 0.3)';
            ]]]
        - background-color: >
            [[[
              return (variables.state === 'on' || variables.state === 'home') ?
                'rgba(255, 255, 255, 0.8)' :
                'rgba(115, 115, 115, 0.2)';
            ]]]
    extra_styles: |
      #name, #state {
        font-size: 1.1vw;
        letter-spacing: 0.05vw;
      }
      /* portrait */
      @media screen and (max-width: 1200px) {
        #name, #state {
          font-size: 2vw;
          letter-spacing: 0.05vw;
        }
      }
      /* phone */
      @media screen and (max-width: 800px) {
        #name, #state {
          font-size: 3.1vw;
          letter-spacing: 0.12vw;
        }
      }
      @keyframes card_bounce {
        0% {
          transform: scale(1);
        }
        15% {
          transform: scale(0.9);
        }
        25% {
          transform: scale(1);
        }
        30% {
          transform: scale(0.98);
        }
        100% {
          transform: scale(1);
        }
      }

additional_values:

  #################################################
  #                                               #
  #              ADIDITIONAL VALUES               #
  #                                               #
  #################################################
  additional_values:
    custom_fields:
      values: >
        [[[
          if ( variables.state != 'problem' && variables.state != 'unavailable' )  {
            const value1 = variables.value_1;            
            const value2 = variables.value_2;     
            const value3 = variables.value_3;
            return `
              <svg viewBox="0 0 50 50">                 
                <text x="5%" y="28%" fill="#8d8e90" font-size="12" text-anchor="start" alignment-baseline="middle" dominant-baseline="middle">${value1}</text>
                <text x="5%" y="62%" fill="#8d8e90" font-size="12" text-anchor="start" alignment-baseline="middle" dominant-baseline="middle">${value2}</text>                                
              </svg>
            `;       
          } else if( variables.state === 'problem'){
              const string1 = entity.attributes.problem;
              if ( string1.includes('unavailable')) { 
                return `
                  <svg viewBox="0 0 50 50">                 
                    <path fill="#855151" d="M19.817 18.932L1.068.183c-.244-.244-.64-.244-.884 0s-.244.64 0 .884l1.935 1.935c-.621.286-1.231.596-1.819.952-.142.086-.245.227-.284.39a.62.62 0 0 0 .08.476l1.671 2.655a15.05 15.05 0 0 1 3.224-1.6l.995.995c-1.27.389-2.469.942-3.554 1.661l1.33 2.112c1.275-.852 2.745-1.414 4.315-1.682l1.1 1.1c-1.745.144-3.372.711-4.75 1.639l1.334 2.117C6.969 12.988 8.428 12.5 10 12.5c.644 0 1.265.09 1.86.244l7.072 7.072c.122.122.282.184.442.184a.63.63 0 0 0 .442-.182c.244-.245.244-.64 0-.885zm.165-14.587a.62.62 0 0 0-.284-.391C16.787 2.185 13.433 1.25 10 1.25a18.77 18.77 0 0 0-5.322.776l3.135 3.135C8.528 5.056 9.258 5 10 5c3.04 0 5.866.915 8.232 2.475l1.671-2.654a.62.62 0 0 0 .079-.476zM10 6.25l-1.057.041 2.574 2.574c1.727.235 3.334.851 4.721 1.78l1.33-2.114A13.66 13.66 0 0 0 10 6.25zm3.189 4.287l1.911 1.911.471-.749c-.73-.491-1.534-.876-2.382-1.162zM10 13.749a6.2 6.2 0 0 0-3.569 1.132l3.039 4.826a.63.63 0 0 0 .53.291c.215 0 .415-.11.529-.291l3.039-4.826A6.19 6.19 0 0 0 10 13.749z"/>                    
                  </svg>
                `;
              }else if( string1.includes('moisture low')){
                return `
                  <svg viewBox="0 0 50 50">                 
                    <path d="M15.341 12.328a7.68 7.68 0 0 1-7.67 7.67A7.68 7.68 0 0 1 0 12.328a7.68 7.68 0 0 1 1.788-4.922L7.67 0l5.882 7.406c1.153 1.377 1.788 3.124 1.788 4.922z" fill="#4abbf0"/><path d="M15.341 12.328a7.68 7.68 0 0 1-7.67 7.67V0l5.882 7.406c1.153 1.377 1.788 3.124 1.788 4.922z" fill="#009be5"/><path d="M18.131 10.529a5.24 5.24 0 0 1-5.23 5.23 5.24 5.24 0 0 1-5.23-5.23c0-1.226.433-2.417 1.219-3.356l4.011-5.05 4.011 5.05c.786.939 1.219 2.13 1.219 3.356z" fill="#78d5f9"/><path d="M18.131 10.529a5.24 5.24 0 0 1-5.23 5.23V2.123l4.011 5.05c.786.939 1.219 2.13 1.219 3.356z" fill="#4abbf0"/>                    
                  </svg>
                `;
              }
          }
        ]]]
    styles:
      card:
        - font-size: 0.8vw;        
      custom_fields:
        values:
          - display: initial
          - width: 95%
          - letter-spacing: 0.03vw
          - margin: -6% -6% 0 0
          - justify-self: end
          - opacity: 1   

climate:

  #################################################
  #                                               #
  #                    CLIMATE                    #
  #                                               #
  #################################################
  climate:
    show_state: false
    show_icon: true
    template:
      - base_2
      - additional_values

    state:
      - value: 'problem'
        styles:
          card: [box-shadow: '0px 0px 10px 2px #FFCCCC']
      - value: 'ok'
        styles:
          card: [box-shadow: '0px 0px 10px 2px #E5FFCC']
    variables:
      value_1: >
        [[[ return entity === undefined || Math.round((Math.abs(entity.attributes.pressure) > 999 ? Math.sign(entity.attributes.pressure)*((Math.abs(entity.attributes.pressure)/1000).toFixed(1)) + 'k' : Math.sign(entity.attributes.pressure)*Math.abs(entity.attributes.pressure))) + ' hPa'; ]]]              
      value_2: >
        [[[ return entity === undefined || Math.round(entity.attributes.humidity) + '%'; ]]]  

climate_livingroom.yaml only shows the current sensor values of an aqara temperature and humidity sensor. I use grafana for temperatuer and humdity graph:

              action: fire-dom-event
              browser_mod:
                command: popup
                title: Wohnzimmer                
                style:
                  .: |
                    :host .content {
                      width: calc(385px + 770px);
                      max-width: 90vw;
                      max-height: 95vw;                                
                    }
                card:
                  type: custom:layout-card
                  layout_type: custom:grid-layout
                  layout:
                    grid-template-columns: repeat(2, 1fr)
                    grid-template-rows: 1fr
                    grid-template-areas: |
                      "info graph"
                    mediaquery:
                      #phone
                      "(max-width: 800px)":
                        grid-template-columns: 1fr
                        grid-template-rows: repeat(2, 1fr)
                        grid-template-areas: |
                          "info"
                          "graph" 
                  cards:
                    - type: entities
                      view_layout:
                        grid-area: info                      
                      show_header_toggle: false
                      style: &border |
                        ha-card {
                          border-radius: 0;
                          animation: ha-card 1s forwards;
                        }

                        @keyframes ha-card {
                          0%, 100% {
                              border-right: 1.5px solid rgba(0, 0, 0, 0.2);
                          }
                        }

                        /* phone */
                        @media screen and (max-width: 800px) {
                          ha-card {
                              border-bottom: 1.5px solid rgba(0, 0, 0, 0.2);
                              padding-right: 0;
                              animation: none;
                          }
                        }  
                      entities:
                        - entity: sensor.aqara_temp_1_temperature
                          secondary_info: last-updated            
                        - entity: sensor.aqara_temp_1_humidity
                          secondary_info: last-updated
                        - entity: sensor.aqara_temp_1_pressure
                          secondary_info: last-updated      
                        - type: divider
                        - entity: sensor.aqara_temp_1_battery
                        - entity: sensor.aqara_temp_1_linkquality
          
############# Grafana Graph #########################     
                    #- break
                    - type: custom:hui-element
                      view_layout:
                        grid-area: graph                    
                      card_type: iframe
                      url: #link to grafana graf here
                      card_mod:
                        style: |
                            ha-card {
                              border-radius: 1em;
                            }                action: fire-dom-event
              browser_mod:
                command: popup
                title: Wohnzimmer                
                style:
                  .: |
                    :host .content {
                      width: calc(385px + 770px);
                      max-width: 90vw;
                      max-height: 95vw;                                
                    }
                card:
                  type: custom:layout-card
                  layout_type: custom:grid-layout
                  layout:
                    grid-template-columns: repeat(2, 1fr)
                    grid-template-rows: 1fr
                    grid-template-areas: |
                      "info graph"
                    mediaquery:
                      #phone
                      "(max-width: 800px)":
                        grid-template-columns: 1fr
                        grid-template-rows: repeat(2, 1fr)
                        grid-template-areas: |
                          "info"
                          "graph" 
                  cards:
                    - type: entities
                      view_layout:
                        grid-area: info                      
                      show_header_toggle: false
                      style: &border |
                        ha-card {
                          border-radius: 0;
                          animation: ha-card 1s forwards;
                        }

                        @keyframes ha-card {
                          0%, 100% {
                              border-right: 1.5px solid rgba(0, 0, 0, 0.2);
                          }
                        }

                        /* phone */
                        @media screen and (max-width: 800px) {
                          ha-card {
                              border-bottom: 1.5px solid rgba(0, 0, 0, 0.2);
                              padding-right: 0;
                              animation: none;
                          }
                        }  
                      entities:
                        - entity: sensor.aqara_temp_1_temperature
                          secondary_info: last-updated            
                        - entity: sensor.aqara_temp_1_humidity
                          secondary_info: last-updated
                        - entity: sensor.aqara_temp_1_pressure
                          secondary_info: last-updated      
                        - type: divider
                        - entity: sensor.aqara_temp_1_battery
                        - entity: sensor.aqara_temp_1_linkquality
          
############# Grafana Graph #########################     
                    - type: custom:hui-element
                      view_layout:
                        grid-area: graph                    
                      card_type: iframe
                      url: #link to grafana graph here
                      card_mod:
                        style: |
                            ha-card {
                              border-radius: 1em;
                            }  
1 Like

Thanks so much!
Heavy use of card-mod to style the theme itself then – not something I’ve explored much. Fun times ahead!

Much appreciated!
-J

thanks a lot. icon_temperature template is missing here, could you share that too.

1 Like

Here you go:


  icon_temperature:
    show_state: true
    custom_fields:   
      icon: >
        [[[ 
        if (entity.state != 'unavailable')
        {
          var temp_color ="'#9da0a2";
          if (entity.state >= '-10' && entity.state < 0) {
            temp_color = "#0302FC"; 
          } else if(entity.state >= '0' && entity.state < 12) {
            temp_color = "#2A00D5";  
          } else if(entity.state >= '12' && entity.state < 22) {
            temp_color = "#63009E";  
          } else if(entity.state >= '22' && entity.state < 30) {
            temp_color = "#A1015D";  
          } else if(entity.state >= '30' && entity.state < 35) {
            temp_color = "#D80027";  
          } else if(entity.state >= '35' && entity.state < 40) {
            temp_color = "#FE0002";  
          }               
          return `<svg viewBox="-949 951 100 125"><style>@keyframes animate{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);}}.animate{animation: animate 0.8s; transform-origin: center;}</style>
          <path fill="#9da0a2" d="M-895.9,1020.1v-56.7c0-5.5-4.5-10-10-10c-5.5,0-10,4.5-10,10v56.7c-3.7,3-6,7.6-6,12.4c0,8.8,7.2,16,16,16 c8.8,0,15.9-7.2,15.9-16C-890,1027.7-892.2,1023.1-895.9,1020.1z M-905.9,1045.1c-6.9,0-12.5-5.6-12.5-12.5c0-4.4,2.3-8.4,6-10.7 v-58.4c0-3.6,2.9-6.6,6.6-6.6c3.6,0,6.6,2.9,6.6,6.6v58.4c3.6,2.3,6,6.3,6,10.7C-893.4,1039.5-899,1045.1-905.9,1045.1z"/>
          <path fill="${temp_color}" d="M-902.8,1024v-22.9c0-1.7-1.4-3.1-3.1-3.1c-1.7,0-3.1,1.4-3.1,3.1v22.9c-3.5,1.3-6,4.6-6,8.5c0,5,4.1,9.1,9.1,9.1 c5,0,9.1-4.1,9.1-9.1C-896.8,1028.6-899.3,1025.3-902.8,1024z"/>
          <rect fill="#9da0a2" x="-892.4" y="963.6" width="16.3" height="3.4"/>
          <rect fill="#9da0a2" x="-892.4" y="974.9" width="7.7" height="3.4"/>
          <rect fill="#9da0a2" x="-892.4" y="986.3" width="16.3" height="3.4"/>
          <rect fill="#9da0a2" x="-892.4" y="997.6" width="7.7" height="3.4"/>
          <rect fill="#9da0a2"v x="-892.4" y="1008.9" width="16.3" height="3.4"/>              
          </svg>`;
        } ]]]

It’s a quite lazy implementation, but it works

3 Likes

thanks. looks very nice :slight_smile:
image

I noticed that top right corner where i have icon and humidity percentage, the text is slightly higher than the icon. do you how can i align them? i am using following code

  additional_values:
    custom_fields:
      temp: >
        [[[
          return `<text>${variables.temp}°C</text>`
        ]]]
      values: >
        [[[
            const value1 = variables.value_1;            
            const value2 = variables.value_2;
            return `
              <ha-icon icon="mdi:radiator" style="width: 20px; height: 20px;"></ha-icon><span> ${value1}</span>
              <br>
              <ha-icon icon="mdi:water-percent" style="width: 20px; height: 20px;"></ha-icon><span> ${value2}</span>
            `
        ]]]

Hey @Mattias_Persson! Awesome design on the UI and clean code on the backend! You inspired me to redo my entire HA install and use your basecode. I even created a fork of it here (still working on the README). I changed several things and added some functionality but I am not a CSS expert by any stretch.

I am having trouble with these icons going haywire

.

This is happening in the latest version of Brave Browser v1.29.76. It does not happen in Edge or Chrome. I did some developer analysis and saw that this line is affecting the size. flex: auto;

hui-grid-card {
    display: flex;
    flex: auto;
    align-items: flex-end;
}

I rolled back the Brave version and the problem went away. Wondering if you or anyone else has seen this issue?

I was able to fix it in this change. Not sure if it was the “right” way though. Hopefully, this helps anybody else that comes across this problem.

Same problem here. Your fix kinda solves it, sometimes. Firefox looks good though.

Hmm hard to say. Can’t really help you here… I would try with something like text-align: center; But I’m not really a CSS expert.

@wjbridge @RoRoW

2 Likes

That was fast! Thx, it works.