A different take on designing a Lovelace UI

Perfect thanks for your quick response.

1 Like

I have a problem with light entity that doesn’t have brightness attribiute. When I turn on the light the custom button card shows NaN% in the upper right corner. Is there a way to disable this feature for entities without such attribiutes? The same goes for hue parameter.

Looks amazing, is there an option you can share all the code please?

Super please share it :sunglasses:amazing what we can acomplish togheter

cool

check out line:

  event1time: |
    [[[
      return "Kl: " + (states['sensor.ical_daniel_kalender_event_0'].attributes.start).substr(11,5);
      ]]]

if you change substr(11,5) to (0,10) for example, you will get the day instead. the substr just “clips” out the string that you specify.

1 Like

Here is one more widget clock/quote of the day

bild

type: custom:button-card
view_layout:
  grid-area: one
template:
  - widgets
entity: sensor.date
show_state: true
tap_action:
  action: fire-dom-event
  browser_mod:
    command: popup
    title: Ställ in ett alarm
    hide_header: false
    card:
      type: vertical-stack
      cards:
        - type: custom:time-picker-card
          style: |
            ha-card {
              box-shadow: none;
            }
          entity: input_datetime.date
          hour_mode: 24
          hour_step: 1
          minute_step: 1
          second_step: 5
          name: Alarm
          layout:
            embedded: true
            align_controls: center
            name: header
          hide:
            name: true
          seconds: true
        - type: entities
          style: |
            ha-card {
              box-shadow: none;
            }
          entities:
            - entity: automation.alarm
              name: Aktivera
              icon: ios:clock-badge-exclamationmark-fill
name: |
  [[[ return states['sensor.time'].state; ]]]
state_display: |
  [[[ return "- " + states['sensor.quote'].attributes.author; ]]]
styles:
  state:
    - position: absolute
    - bottom: 20px
    - left: 50%
    - transform: translate(-50%, 0%)
    - font-weight: bold
  name:
    - position: absolute
    - color: rgb(255,255,255,0.9)
    - top: 5%
    - left: 50%
    - transform: translate(-50%, 0%)
    - font-size: 70px
  custom_fields:
    quote:
      - position: absolute
      - top: 43%
      - left: 5%
      - width: 90%
      - height: 34%
      - white-space: normal
      - color: rgb(255,255,255,0.6)
      - font-size: 14px
      - font-style: italic
      - letter-spacing: 0.05vw
custom_fields:
  quote: |
    [[[ return "´" + states['sensor.quote'].attributes.quote + "´"; ]]]

And the quote sensor in configuration.yaml:

sensor:
  - platform: rest
    resource: https://quotes.rest/qod?language=en
    name: Quote
    scan_interval: 42000
    json_attributes_path: '$.contents.quotes[0]'
    json_attributes:
      - quote
      - author
      - category
4 Likes

Great idea, its working, thank you!

1 Like

Beautiful. This is exactly what I was looking for. Would love your code once you are ready to share!

Here is a nicer icon if someone is interested.

bild

type: custom:button-card
view_layout:
  grid-area: seven
template:
  - widgets
name: BATTERIER
styles:
  name:
    - font-size: 14px
    - font-weight: bold
    - top: 20px
    - left: 20px
    - position: absolute
  custom_fields:
    iphonese:
      - top: 40px
      - left: '-5px'
      - width: 110%
      - border-bottom: 1px solid rgb(255,255,255,0.1)
      - padding: 10px
      - position: absolute
      - font-size: 16px
      - text-shadow: 0px 0px 1px black
    iphonesebattery:
      - top: 60px
      - right: 15px
      - position: absolute
      - font-size: 14px
    iphone11:
      - top: 100px
      - left: '-5px'
      - width: 110%
      - border-bottom: 1px solid rgb(255,255,255,0.1)
      - padding: 10px
      - position: absolute
      - font-size: 16px
      - text-shadow: 0px 0px 1px black
    iphone11battery:
      - top: 120px
      - right: 15px
      - position: absolute
      - font-size: 14px
    ipad:
      - top: 160px
      - left: '-5px'
      - width: 110%
      - padding: 10px
      - position: absolute
      - font-size: 16px
      - text-shadow: 0px 0px 1px black
    ipadbattery:
      - top: 180px
      - right: 15px
      - position: absolute
      - font-size: 14px
custom_fields:
  iphonese: |
    [[[
           return `<div align="left"> <ha-icon
             icon="mdi:cellphone"
             style="width: 20px; height: 20px; color:grey; padding: 10px;">
             </ha-icon>iPhone SE</div>`
         ]]]
  iphonesebattery: |
    [[[                       
      if (states["sensor.iphone_battery_level"].state > 40) { 
        let input = states["sensor.iphone_battery_level"].state / 3,
          state = states["sensor.iphone_battery_level"].state,
          color = '#08ff4e';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="${input}" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;                    
      } if (states["sensor.iphone_battery_level"].state > 15) {   
        let input = states["sensor.iphone_battery_level"].state / 3,
          state = states["sensor.iphone_battery_level"].state,
          color = 'orange';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="${input}" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;  
      } else {   
        let state = states["sensor.iphone_battery_level"].state,
          color = 'red';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="5" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;  
      } 
    ]]]  
  iphone11: |
    [[[
           return `<div align="left"> <ha-icon
             icon="mdi:cellphone"
             style="width: 20px; height: 20px; color:grey; padding: 10px;">
             </ha-icon>iPhone 11</div>`
         ]]]
  iphone11battery: |
    [[[                       
      if (states["sensor.malins_iphone_battery_level"].state > 40) { 
        let input = states["sensor.malins_iphone_battery_level"].state / 3,
          state = states["sensor.malins_iphone_battery_level"].state,
          color = '#08ff4e';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="${input}" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;                    
      } if (states["sensor.malins_iphone_battery_level"].state > 15) {   
        let input = states["sensor.malins_iphone_battery_level"].state / 3,
          state = states["sensor.malins_iphone_battery_level"].state,
          color = 'orange';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="${input}" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>

        `;  
      } else {   
        let state = states["sensor.malins_iphone_battery_level"].state,
          color = 'red';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="5" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;  
      } 
    ]]]
  ipad: |
    [[[
           return `<div align="left"> <ha-icon
             icon="mdi:tablet-android"
             style="width: 20px; height: 20px; color:grey; padding: 10px;">
             </ha-icon>iPad Pro</div>`
         ]]]
  ipadbattery: |
    [[[                       
      if (states["sensor.daniels_ipad_battery_level"].state > 40) { 
        let input = states["sensor.daniels_ipad_battery_level"].state / 3,
          state = states["sensor.daniels_ipad_battery_level"].state,
          color = '#08ff4e';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="${input}" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;                    
      } if (states["sensor.daniels_ipad_battery_level"].state > 15) {   
        let input = states["sensor.daniels_ipad_battery_level"].state / 3,
          state = states["sensor.daniels_ipad_battery_level"].state,
          color = 'orange';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="${input}" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;  
      } else {   
        let state = states["sensor.daniels_ipad_battery_level"].state,
          color = 'red';
        return `
          <svg width="86" height="24">
            <rect x="40" y="1" rx="5" ry="5" width="40" height="18" stroke="gray" fill="transparent" stroke-width="2"/>
            <rect x="43" y="4" rx="3" ry="3" width="5" height="12" fill="${color}" stroke-width="0"/>
            <rect x="82" y="6" rx="2" ry="2" width="2" height="9" fill="grey" stroke-width="0"/>
        `;  
      } 
    ]]]

5 Likes

Hi. Can anyone help me with the sidebar? I need to add a margin-left here… But I can’t figure out how…

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

      - type: vertical-stack
        view_layout:
          grid-area: sidebar
        cards:
          - type: custom:button-card
            entity: sensor.template_sidebar
            template: sidebar

          - type: horizontal-stack
            cards:   
                - type: custom:button-card
                  entity: switch.bedroom
                  name: Cold lights
                  template: 
                    - icon_only 
    
                - type: custom:button-card
                  entity: switch.bedroom
                  name: Cold lights
                  template: 
                    - icon_only

like this?

action: fire-dom-event
browser_mod:
  command: popup
  title: Frontyard
  style:
    .: |
      :host .content {
        width: calc(385px + 495px);
        max-width: 90vw;
      }
      /* phone */
      @media screen and (max-width: 800px) {
        :host .content {
          max-width: 100vw;
        }
      }
    layout-card$grid-layout:
      $: |
        hui-vertical-stack-card {
          border-right: 1.5px solid rgba(0, 0, 0, 0.2);
          border-radius: 0;
          transition: none;
        }
        /* phone */
        @media screen and (max-width: 800px) {
          hui-vertical-stack-card {
            border-right: none;
            border-bottom: 1.5px solid rgba(0, 0, 0, 0.2);
          }
        }
      $hui-vertical-stack-card:
        $: |
          hui-horizontal-stack-card {
            padding: 0em 2em 2.3em 2em;
          }
        $hui-entities-card$: |
          .card-content {
            padding: var(--card-content-padding);
          }
        $hui-horizontal-stack-card$: |
          #root {
            justify-content: space-evenly;
            max-width: 82vw; /* iphonex */
          }
  card:
    type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      grid-template-columns: 385px 495px
      grid-template-rows: 1fr
      grid-template-areas: |
        "info map"
      mediaquery:
        #phone
        "(max-width: 800px)":
          grid-template-columns: 1fr
          grid-template-rows: repeat(2, 1fr)
          grid-template-areas: |
            "info"
            "map"
    cards:

      - type: vertical-stack
        view_layout:
          grid-area: info
        cards:
        - type: entities
        #   title: Backyard Control
          card_mod:
            class: header
          entities:

          - entity: binary_sensor.door_sensor
            name: Girls Door Sensor
          - entity: binary_sensor.multipurpose_sensor_contact
            name: Home Door Sensor

        - type: horizontal-stack
          cards:
            - type: grid
              columns: 3
              cards:
                - type: custom:button-card
                  entity: switch.front_yard_lights
                  name: Front Yard
                  show_state: false
                  custom_fields:
                    icon: >
                        <ha-icon icon="mdi:light-flood-down"></ha-icon>
                  template: 
                    - base

                - type: custom:button-card
                  entity: switch.hall_front_yard_lights
                  name: Hall
                  show_state: false
                  custom_fields:
                    icon: >
                        <ha-icon icon="mdi:light-flood-up"></ha-icon>
                  template: 
                    - base
                
                - type: custom:button-card
                  entity: switch.garage_lights
                  name: Garage
                  show_state: false
                  custom_fields:
                    icon: >
                        <ha-icon icon="mdi:track-light"></ha-icon>
                  template: 
                    - base

      
      - type: vertical-stack
        view_layout:
          grid-area: map
        cards:
        
          - type: 'custom:aarlo-glance'
            entity: camera.aarlo_front_yard_camera
            name: front door
            image_view: direct
            image_bottom: 'motion,library,stream,battery'
            image_click: 'recordings'
            library_sizes: '3,4,2'
            template: base
9 Likes

This UI has given me the perfect foundation to build mine upon. Thanks @Mattias_Persson for sharing and answering our questions.
I have modified the sidebar considerably (I only need the part you see here with the 2 rows of nameless buttons). I cannot for the life of me figure out how to get rid of the space that follows the grid with the sidebar buttons.


The part I’ve marked in red is what I want to get rid of so the cameras area follows the sidebar without a space. Here are the relevant bits of code

This is the layout definition (sidebar is called “modes” and cameras is “camera”

views:
  - type: custom:grid-layout
    path: 0
    layout:
      #default
      grid-gap: var(--custom-layout-card-padding)
      grid-template-columns: 20% 1fr 1fr 1fr
      grid-template-rows: 0 repeat(3, fit-content(100%))
      grid-template-areas: |
        "   .      .        .       .       ."
        "modes    lights  media    controls   ."
        "camera   lights  scenes   controls   ."
        "   .     lights  climate  controls   ."

This is the grid with the sidebar:

      - type: grid
        square: false
        view_layout:
          grid-area: modes
        columns: 1
        cards:
          - type: 'custom:button-card'
            template: control_container
            color: '#07171c'
            name: 'CONTROLS'
            custom_fields:
              buttons:
                card:
                  type: vertical-stack
                  cards:
                    - type: horizontal-stack
                      cards:
                        - type: custom:button-card
                          entity: lock.front_door
                          name: ' '
                          tap_action:
                            action: toggle
                          template: monitoring_button
                        - type: custom:button-card
                          entity: binary_sensor.active_lights
                          tap_action: !include popup/active_lights.yaml
                          name: ' '
                          template: monitoring_button
                        - type: custom:button-card
                          entity: binary_sensor.home_modes
                          tap_action: !include popup/modes.yaml
                          name: ' '
                          template: monitoring_button
                        - type: custom:button-card
                          tap_action: !include popup/boilers.yaml
                          entity: binary_sensor.boilers
                          name: ' '
                          template: monitoring_button
                        - type: custom:button-card
                          tap_action: !include popup/boilers.yaml
                          entity: binary_sensor.boilers
                          name: ' '
                          template: monitoring_button
                    - type: horizontal-stack
                      cards:
                        - type: custom:button-card
                          entity: binary_sensor.perimeter
                          tap_action: !include popup/perimeter.yaml
                          name: ' '
                          template: monitoring_button
                        - type: custom:button-card
                          entity: binary_sensor.active_media_players
                          tap_action: !include popup/active_media_players.yaml
                          name: ' '
                          template: monitoring_button
                        - type: custom:button-card
                          entity: binary_sensor.perimeter
                          tap_action: !include popup/perimeter.yaml
                          name: ' '
                          template: monitoring_button
                        - type: custom:button-card
                          entity: binary_sensor.active_media_players
                          tap_action: !include popup/active_media_players.yaml
                          name: ' '
                          template: monitoring_button
                        - type: custom:button-card
                          entity: binary_sensor.active_media_players
                          tap_action: !include popup/active_media_players.yaml
                          name: ' '
                          template: monitoring_button

and this is the camera area:

      - type: grid
        square: false
        view_layout:
          grid-area: camera
        columns: 1
        cards:
          - type: 'custom:button-card'
            template: control_container
            color: '#0f1416'
            name: CAMERAS
            custom_fields:
              buttons:
                card:
                  type: vertical-stack
                  cards:
                    - type: horizontal-stack
                      cards:
                        - aspect_ratio: 92%
                          camera_view: live
                          entity: camera.adam_substream
                          name: ADAM
                          show_name: true
                          show_state: false
                          type: picture-entity

                        - aspect_ratio: 92%
                          camera_view: live
                          entity: camera.porch_substream
                          name: PORCH
                          show_name: true
                          show_state: false
                          type: picture-entity
                    - type: horizontal-stack
                      cards:
                        - aspect_ratio: 92%
                          camera_view: live
                          entity: camera.garage_substream
                          name: GARAGE
                          show_name: true
                          show_state: false
                          type: picture-entity

                        - aspect_ratio: 92%
                          camera_view: live
                          entity: camera.entryway_substream
                          name: ENTRYWAY
                          show_name: true
                          show_state: false
                          type: picture-entity

Here’s the whole UI as it currently stands for perspective (still under construction)

2 Likes

Laffer;

Can u share the code for your weather card also?

I’m not too sure about the colors, yet.

image image

lovelace-ui.yaml

          - type: custom:button-card
            entity: weather.openweathermap
            name: Location
            tap_action:
              !include popup/weather.yaml
            template:
              - base
              - weather_forecast
            variables:
              temp_min: sensor.weather_min_temp
              temp_max: sensor.weather_max_temp

button_card_templates.yaml

  weather_forecast:
    variables:
      temp_min: ''
      temp_max: ''
    aspect_ratio: 1/1
    show_icon: false
    show_entity_picture: true
    show_name: true
    show_state: false
    show_label: true 
    tap_action:
      action: more-info
    styles:
      grid:
        - grid-template-areas: |
            "n"
            "temp"
            "i"
            "s"
            "l"
        - grid-template-columns: 1fr
        - grid-template-rows: min-content repeat(2, 1fr) repeat(2, min-content)
        - gap: 0%
      card:
        - color: > #rgba(157, 160, 162, 1) #rgba(84, 85, 85, 1) #rgba(85, 86, 86, 1)
            [[[
              if (states['sun.sun'].state == 'below_horizon'){
                return 'rgba(157, 160, 162, 1)';
              } else
                return 'rgba(84, 85, 85, 1)';
            ]]]  
        - background: > # else linear-gradient(to top, rgba(123,168,197,0.8) 0%, rgba(61,132,176,0.8) 100%)
            [[[
              if (states['sun.sun'].state == 'below_horizon'){
                return 'linear-gradient(to top, rgba(53,59,83,0.8) 0%, rgba(10,14,34,0.8) 100%)';
              } else
                return 'linear-gradient(to top, rgb(123 168 197) 0%, #b0c5d3 100%)';
            ]]]
          # return 'linear-gradient(to top, rgb(123 168 197) 0%, #b0c5d3 100%)'; --- rgb(213, 215, 216)
      name:
        - place-self: start
        - text-transform: uppercase
        - font-weight: 400
      img_cell:
        - justify-content: start
      icon:
        - width: 37%
      label:
        - place-self: start
        - margin-left: -5px
      custom_fields:
        temp:
          - place-self: start
          #- margin-top: -10px
    label: >   
      [[[
        return `
          <ha-icon icon="mdi:arrow-up-thin" style="width: 1.3em; height: 1.3em; margin-right: -0.3em;"></ha-icon>
          <span> ${states[variables.temp_max].state}°</span>
          <ha-icon icon="mdi:arrow-down-thin" style="width: 1.3em; height: 1.3em; margin-right: -0.3em;"></ha-icon>
          <span> ${states[variables.temp_min].state}°</span>
        `
      ]]]
    custom_fields:
      temp: >
        [[[ return entity.attributes.temperature + "°"; ]]]
    entity_picture: |
      [[[
        if ((entity.state == 'sunny') && (states['sun.sun'].state == 'above_horizon'))
          return "/local/svg/weather/animated/clear-day.svg";
          if ((entity.state == 'sunny') || (entity.state == 'clear-night') && (states['sun.sun'].state == 'below_horizon'))
            return "/local/svg/weather/animated/clear-night.svg";
              if (entity.state == 'fog')
                return "/local/svg/weather/animated/fog.svg";
                  if ((entity.state == 'partlycloudy') && (states['sun.sun'].state == 'above_horizon'))
                    return "/local/svg/weather/animated/partly-cloudy-day.svg";
                      if ((entity.state == 'partlycloudy') && (states['sun.sun'].state == 'below_horizon'))
                        return "/local/svg/weather/animated/partly-cloudy-night.svg";    
                          if (entity.state == 'rainy')
                            return "/local/svg/weather/animated/rain.svg";                  
                              if (entity.state == 'sleet')
                                return "/local/svg/weather/animated/sleet.svg";         
                                  if (entity.state == 'snow')
                                    return "/local/svg/weather/animated/snow.svg";  
                                      if (entity.state == 'cloudy')
                                        return "/local/svg/weather/animated/cloudy.svg";    
        else (entity.state == 'wind')
          return "/local/svg/weather/animated/wind.svg";                        
      ]]]
    extra_styles: |
      [[[
        return `
          #name {
            font-size: 0.8vw;
          }
          #temp {
            font-size: 1.8vw;
          }
          #state {
            font-size: 0.8vw;
          }  
          #label {
            font-size: 0.8vw;
          }                    
          /* portrait */
          @media screen and (max-width: 2000px) {
            #name {
              font-size: 0.7vw !important;
            }
            #temp {
              font-size: 1.8vw !important;
            }
            #state {
              font-size: 0.7vw !important;
            }  
            #label {
              font-size: 0.7vw !important;
            }  
          }
          /* phone */
          @media screen and (max-width: 800px) {
            #name {
              font-size: 2vw !important;
            }              
            #temp {
              font-size: 4.5vw !important;
            }    
            #state {
              font-size: 2vw !important;
            }   
            #label {
              font-size: 2vw !important;
            }                        
          }
          @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);
            }
          }
        `
      ]]]

4 Likes

Can you share the code one more time I am trying to copy to the system for a separate window and for some reason it does not work. :slight_smile:

Which code? It’s right there

Thanks Laffer!

I believe the credit should go to pex81, or whoever made it, but you’re welcome :slight_smile:

3 Likes

What about the weather svg icons, From where i can download it?

1 Like

If anyone ever runs into this, I ended up solving it by creating a separate button-card of the right size only for the state display portion and a second button-card for the rest of the button I want to display (essentially two buttons on the same line). Works like a charm, thanks @Mattias_Persson for the marquee code!