Clean Tile-Based Lovelace UI - only 2 cards needed!

Installation instructions further down

Just thought I would share what I’ve been working on. I’ve spent a while trying to make traditional lovelace cards look good but the variance in sizes and designs left me frustrated with the final result. I realised that a tile-based UI would go a long way to making things look cohesive - similar sized tesselating tiles - and took a great deal of inspiration from existing Homekit-style designs. However, Home Assistant isn’t Homekit, and I didn’t want some pseudo-imitation - I want my own thing!

This is the end result:

As with any installation you can add and add to it but about 90% of this UI can be created from a fresh HA setup using just two custom cards:

  • The simply fantastic Button Card by @RomRider - every card (excluding pop-ups) and text element in this UI is made from these button cards. I have made templates for entities, media players, alarm panels, weather UI, sensors, persons, text headers, and temperature cards, all of which can be quickly replicated.

  • The excellent Layout Card by @thomasloven - all cards slot into a user-definable grid configuration making quick rearrangements of the layout very straightforward - no more mucking about with nesting millions of vertical-and-horizontal-stack cards!

  • …that’s pretty much it!

Things I used to make it prettier:

Thank you to everyone mentioned for your contributions to the Home Assistant Community!

Installation Instructions

You’ll need the two required cards (Button Card and Layout Card), either manually or preferentially using HACS by @ludeeus.

You’ll first need to set up your view as a grid:

title: My House
views:
  - path: home
    title: Home
    theme: Backend-selected
    badges: []
    panel: true
    cards:
      - type: 'custom:layout-card'
        column_width: 100%
        layout: vertical
        cards:
          - type: 'custom:layout-card'
            layout: grid
            gridcols: 158px 158px 158px 158px 158px 158px # more on this below
            gridrows: 158px 158px 158px 158px 158px 158px # more on this below
            cards:

Your column and row sizes need to be based on your desired layout and the sizes of the cards you’re using. The card templates I have made are mostly 150px tall by 150px wide and I’ve been using an 8px gap between cards, so by default I set my rows and columns to 158px in size to allow for the card and the gap. You can of course set a wider gap if you like!

The double-sized cards are therefore 150px tall by 308px wide in order to line up with the other cards (150px + 150px + 8px gap). You don’t need to make the column 316px wide because the wide card will simply overlap 2 grid spaces - just make sure if your wide card is in column 1 that you don’t put anything in column 2 next to it because they will overlap!

To use the card templates, add the following to the start of either lovelace.yaml or the built-in configuration editor (above where you define your views):

button_card_templates:

Then add any of the following:

Alarm card:
308px wide

button_card_templates:
  alarm:
    show_state: true
    size: 20%
    state:
      - icon: 'mdi:shield-check'
        styles:
          icon:
            - color: var(--label-badge-green)
        value: disarmed
      - icon: 'mdi:shield-lock'
        styles:
          icon:
            - color: var(--label-badge-red)
        value: armed_away
    styles:
      card:
        - width: 308px
        - height: 150px
      grid:
        - grid-template-areas: '"i" "n" "s"'
        - grid-template-columns: 1fr
        - grid-template-rows: 1fr min-content min-content
      img_cell:
        - align-content: start
        - justify-content: start
        - margin-left: 20px
      name:
        - justify-self: start
        - margin-left: 10px
      state:
        - justify-self: start
        - margin-left: 10px
        - margin-bottom: '-6px'
        - font-weight: lighter
    tap_action:
      action: call-service
      service: script.alarm_toggle
# You'll need to make a quick script to toggle your alarm on/off -
# I tried doing this with templates in-card but it caused issues
# with the state not updating correctly

Default (for lights, switches, etc):

  default:
    tap_action:
      action: toggle
    hold_action:
      action: more-info
    show_state: true
    size: 30%
    state:
      - styles:
          card:
            - filter: opacity(50%)
          icon:
            - filter: grayscale(100%)
        value: 'off'
      - styles:
          card:
            - filter: opacity(25%)
          icon:
            - filter: grayscale(100%)
        value: unavailable
    styles:
      card:
        - width: 150px
        - height: 150px
      grid:
        - grid-template-areas: '"i" "n" "s"'
        - grid-template-columns: 1fr
        - grid-template-rows: 1fr min-content min-content
      img_cell:
        - justify-content: start
        - margin-left: 20px
        - margin-bottom: 30px
      name:
        - justify-self: start
        - margin-left: 10px
      state:
        - justify-self: start
        - margin-left: 10px
        - font-weight: lighter

Garbage (requires Garbage Collection sensor by @bruxy70):

  garbage:
    show_label: true
    size: 30%
    state:
      - label: '[[[ return `in ${entity.attributes.days} days` ]]]'
        value: 2
      - label: Tomorrow
        value: 1
      - label: Today
        value: 0
    styles:
      card:
        - width: 150px
        - height: 150px
      grid:
        - grid-template-areas: '"i" "n" "l"'
        - grid-template-columns: 1fr
        - grid-template-rows: 1fr min-content min-content
      img_cell:
        - justify-content: start
        - margin-left: 20px
        - margin-bottom: 30px
      label:
        - justify-self: start
        - margin-left: 10px
        - font-weight: lighter
      name:
        - justify-self: start
        - margin-left: 10px

Header:
Thin header text for above a group of cards. 40px tall so needs a row height of 40px if more text below it or 60px if directly above a set of cards:

  header:
    show_icon: false
    show_name: true
    show_state: false
    styles:
      card:
        - width: 300px
        - height: 40px
        - background: none
        - box-shadow: none
      name:
        - justify-self: start
        - margin-left: 10px
        - font-weight: lighter
        - font-size: x-large

Media (my favourite card!):
308px wide

  media:
    tap_action:
      action: call-service
      service: media_player.toggle
      service_data:
        entity_id: entity
    show_state: true
    show_label: true
    state:
      - value: idle
        label: ' '
      - styles:
          card:
            - filter: opacity(50%)
          icon:
            - filter: grayscale(100%)
        label: ' '
        value: 'off'
      - styles:
          card:
            - filter: opacity(25%)
          icon:
            - filter: grayscale(100%)
        label: ' '
        value: unavailable
    label: >-
      [[[ if (entity.attributes.media_title == undefined) return " ";
      else if (entity.attributes.media_artist == undefined) return
      `${entity.attributes.media_title}`; else return
      `${entity.attributes.media_title} - ${entity.attributes.media_artist}` ]]]
    size: 12%
    styles:
      card:
        - width: 308px
        - height: 150px
        - background-size: cover
        - background-image: '[[[ return `url("${entity.attributes.entity_picture}")` ]]]'
        - background-position: center center
      grid:
        - grid-template-areas: '"i i" "n n" "s l"'
        - grid-template-columns: 1fr 2fr
        - grid-template-rows: 1fr min-content min-content
        - background-image: >
            [[[ return `linear-gradient(to top,
            var(--paper-card-background-color),
            var(--paper-card-background-color-transparent))` ]]]
        - margin-bottom: '-25px'
      img_cell:
        - align-content: start
        - justify-content: start
        - margin-left: 0px
        - margin-bottom: 50px
      icon:
        - filter: drop-shadow(0px 0px 10px var(--paper-card-background-color))
        - padding-left: 15px
      name:
        - justify-self: start
        - margin-left: 10px
      state:
        - justify-self: start
        - margin-left: 10px
        - margin-bottom: 6px
        - font-weight: lighter
      label:
        - justify-self: start
        - margin-left: '-20px'
        - margin-bottom: 6px
        - font-weight: lighter

Person:

  person:
    show_entity_picture: true
    show_state: true
    state:
      - operator: '!='
        styles:
          card:
            - filter: opacity(50%)
          icon:
            - filter: grayscale(100%)
        value: home
    styles:
      card:
        - width: 150px
        - height: 150px
      grid:
        - grid-template-areas: '"i" "n" "s"'
        - grid-template-columns: 1fr
        - grid-template-rows: 1fr min-content min-content
      icon:
        - border-radius: 50%
      img_cell:
        - justify-content: start
        - margin-left: 20px
        - margin-bottom: 30px
      name:
        - justify-self: start
        - margin-left: 10px
      state:
        - justify-self: start
        - margin-left: 10px
        - font-weight: lighter

Sensor:

  sensor:
    show_state: true
    size: 30%
    styles:
      card:
        - width: 150px
        - height: 150px
      grid:
        - grid-template-areas: '"i" "n" "s"'
        - grid-template-columns: 1fr
        - grid-template-rows: 1fr min-content min-content
      img_cell:
        - justify-content: start
        - margin-left: 20px
        - margin-bottom: 30px
      name:
        - justify-self: start
        - margin-left: 10px
      state:
        - justify-self: start
        - margin-left: 10px
        - font-weight: lighter

Temperature:
Shows a large temperature reading in-card

  temperature:
    label: Temperature
    show_label: true
    show_state: true
    size: 80%
    styles:
      card:
        - width: 150px
        - height: 150px
      grid:
        - grid-template-areas: '"i s" "n n" "l l"'
        - grid-template-columns: 1fr 2fr
        - grid-template-rows: 1fr min-content
      img_cell:
        - margin-bottom: 30px
      label:
        - justify-self: start
        - margin-left: 10px
        - font-weight: lighter
      name:
        - justify-self: start
        - margin-left: 10px
      state:
        - margin-bottom: 30px
        - margin-right: 40px
        - font-size: x-large

Weather:
308px wide. I’m using DarkSky as my source… at least until Apple shuts it down…

  weather:
    label: '[[[ return `${entity.attributes.temperature}°C` ]]]'
    show_label: true
    show_state: true
    size: 70%
    state:
      - icon: 'mdi:weather-night'
        value: clear-night
      - icon: 'mdi:weather-cloudy'
        value: cloudy
      - icon: 'mdi:weather-fog'
        value: fog
      - icon: 'mdi:weather-hail'
        value: hail
      - icon: 'mdi:weather-lightning'
        value: lightning
      - icon: 'mdi:weather-lightning-rainy'
        value: lightning-rainy
      - icon: 'mdi:weather-partly-cloudy'
        value: partlycloudy
      - icon: 'mdi:weather-pouring'
        value: pouring
      - icon: 'mdi:weather-rainy'
        value: rainy
      - icon: 'mdi:weather-snowy'
        value: snowy
      - icon: 'mdi:weather-snowy-rainy'
        value: snowy-rainy
      - icon: 'mdi:weather-sunny'
        value: sunny
      - icon: 'mdi:weather-windy'
        value: windy
      - icon: 'mdi:weather-windy-variant'
        value: windy-variant
      - icon: 'mdi:weather-cloudy-alert'
        value: exceptional
    styles:
      card:
        - width: 308px
        - height: 150px
      grid:
        - grid-template-areas: '"i l" "n n" "s s"'
        - grid-template-columns: 1fr 2fr
        - grid-template-rows: 1fr min-content min-content
      img_cell:
        - align-content: start
        - justify-content: start
        - margin-left: 20px
      label:
        - justify-self: start
        - font-size: 250%
      name:
        - justify-self: start
        - margin-left: 10px
      state:
        - justify-self: start
        - margin-left: 10px
        - margin-bottom: '-6px'
        - font-weight: lighter

Good morning/afternoon/evening header: See here.

Active entities headers: See here.

Putting it all together

To add your first card to the view, simply add a button card, assign it an entity and one of the templates above, and at it to a column and row. For example:

title: My House
views:
  - path: home               #
    title: Home              #
    theme: Backend-selected  # Your view setup
    badges: []               #
    panel: true              #
    cards:
      - type: 'custom:layout-card'    #
        column_width: 100%            # For setting up panel view
        layout: vertical              #
        cards:
          - type: 'custom:layout-card'                         #
            layout: grid                                       # Grid
            gridcols: 158px 158px 158px 158px 158px 158px      # setup
            gridrows: 158px 158px 158px 158px 158px 158px      #
            cards:
              - type: 'custom:button-card'           #
                entity: light.living_room_tv_lamp    #
                gridcol: 1/6                         # Your first
                gridrow: 1/6                         # card
                name: TV Lamp                        #
                template: default                    #

…will produce this:

Rinse, lather, repeat to create your own modular, clean, tile-based UI!

Known issues
Wide (308px) tiles positioned at the end of a row (e.g. gridcol 5/6) will display slightly incorrect margins for name and state text. The workaround for this is simply to add an extra column to the end of your grid!

14 Likes

…you forgot your config files :wink:

None of those custom cards are addons.

You’re right, I’ve changed the wording.

Fixed! :smiley: Let me know if there’s any other bits you’d like explained!

1 Like

This looks really nice and clean! Maybe you can also share how you created the “Good afternoon” part (along with the two lines below) on the Home page?

Thanks! Here’s the config for the Good Morning/Afternoon/Evening card:

Welcome:
Can also be repurposed as a general title card. 60px tall so needs a row height of at least this. Not a template - copy directly into your view config

      - type: 'custom:button-card'
        gridcol: 1/6
        gridrow: 1/6
        name: >-
          [[[ var d = new Date(); var n = d.getHours(); if (0 <= n && n
          < 12) return "Good morning."; else if (12 <= n && n < 18)
          return "Good afternoon."; else if (18 <= n && n < 24) return
          "Good evening."; else return "ERROR";]]]
        styles:
          card:
            - width: 300px
            - height: 60px
            - background: none
            - box-shadow: none
          name:
            - justify-self: start
            - margin-left: 10px
            - font-weight: normal
            - font-size: xx-large
2 Likes

Active Entities:
Requirements:

  • Header template (see top of page)
  • Popup Card (or Browser Mod, if you prefer)
  • Auto-Entities Card (+ Card Mod, if not installed)

First thing you need is a sensor to count the number of active devices (I’m using lights as an example):
Config.yaml:

  - platform: template
    sensors:
      active_lights:
        value_template: "{{ states.light|selectattr('state','equalto','on')|list|count }}"

This sensor won’t update automatically so you’ll need an automation to do this:
automation.yaml:

  - alias: Update Light Count
    initial_state: True
    trigger:
      platform: event
      event_type: state_changed
    condition:
      condition: template
      value_template: "{{ trigger.event.data.entity_id.startswith('light') }}"
    action:
      service: homeassistant.update_entity
      entity_id: sensor.active_lights

Then add your card to your view:

  - type: 'custom:button-card'
    entity: sensor.active_lights
    gridcol: 1/6
    gridrow: 2/6
    state:
      - name: All lights off.
        value: 0
      - name: 1 light on.
        value: 1
      - name: '[[[ return `${entity.state} lights on.` ]]]'
        operator: '>='
        value: 2
    template: header
    tap_action:
      action: more-info

Finally, define the popup card:

    popup_cards:
      sensor.active_lights:
        title: ' '
        card:
          type: 'custom:auto-entities'
          card:
            type: entities
            title: Active Lights
            show_header_toggle: true
          filter:
            include:
              - domain: light
                state: 'on'
          show_empty: true

Add this to your config as below:

title: My House
views:
  - path: home               #
    title: Home              #
    theme: Backend-selected  # Your view setup
    badges: []               #
    panel: true              #
    popup_cards:                              #
      sensor.active_lights:                   #
        title: ' '                            #
        card:                                 #
          type: 'custom:auto-entities'        #
          card:                               #
            type: entities                    # defining the popup
            title: Active Lights              # card
            show_header_toggle: true          #
          filter:                             #
            include:                          #
              - domain: light                 #
                state: 'on'                   #
          show_empty: true                    #
    cards:
      - type: 'custom:layout-card'    #
        column_width: 100%            # For setting up panel view
        layout: vertical              #
        cards:
          - type: 'custom:layout-card'                         #
            layout: grid                                       # Grid
            gridcols: 158px 158px 158px 158px 158px 158px      # setup
            gridrows: 158px 158px 158px 158px 158px 158px      #
            cards:
              - entity: sensor.active_lights       # the active lights card
                gridcol: 1/6
                gridrow: 2/6
                state:
                  - name: All lights off.
                    value: 0
                  - name: 1 light on.
                    value: 1
                  - name: '[[[ return `${entity.state} lights on.` ]]]'
                    operator: '>='
                    value: 2
                template: header
                tap_action:
                  action: more-info
                type: 'custom:button-card'
1 Like

… not since 11 November…

…really?! I completely missed that, sorry! Original post updated accordingly.

Wow. this is exactly what I was looking for. How does it look like on a mobile?

I use a modified layout for mobile and it looks like this. I have been slowly tweaking the design to make it more universal across browsers, and make it more customisable. There’s now better indicators for bulb brightness and percentages, and I have made the media and camera widgets much more consistent in design. Can upload code updates if anyone is interested!

I will be very grateful if you can share your code. I have noticed that the current one has not been updated with the latest layout card settings. For some reason, I am getting everything in the middle of the screen

Question around your light count template. I use light groups and then group lights into rooms.

e.g. Office light group contains 2 lights, lounge light group contains 3 lights.

If both of these rooms are on the light count is 7 as the light group is counted in the template. Anyway to avoid this and only count the individual lights?

Edit. Don’t worry I managed to do it expanding the group and counting the entities that show on.

1 Like

Updated: 30/03/2021

So with layout-card being rewritten to include better CSS support for grid elements I took the time to simplify and rewrite parts of my setup. The new layout features require layout-card 2.0 or later to work properly.

Most of my button-card templates now draw basic elements from a base template - this means you can edit multiple cards simultaneously just by changing properties in the base template.

I’ve also tweaked some UI elements to make it prettier and more consistent.

Also improved:

  • Auto-generating/iterating grids, less work to add more elements and move them around
  • Simplified yaml with fewer stacked elements
  • Ability to adjust aspects of all cards
  • Circular elements to display additional data, and can show a partial circle for percentage properties, using the circle and circle_dynamic templates

How to set up

Set YAML mode for Lovelace, install button-card and layout-card.

ui-lovelace.yaml:

button_card_templates: !include ui-resources.yaml #required for button card templates
popup_cards: !include ui-popups.yaml #if using browser

title: My Home
views:
  - title: Home
    path: home
    type: custom:grid-layout #new simpler grid setting
    layout:
      grid-auto-columns: 150px #default card width is 150px (small) or 308px (medium)
      grid-auto-rows: 150px #default card height is 150px
      grid-template-rows: 50px 30px 50px #use if you want some specific row heights
      grid-column-gap: 8px #default size
      grid-row-gap: 8px #default size
    cards:
      - type: 'custom:button-card' #your first card
        template: greeting #template specified in ui-resources.yaml
        view_layout:
          grid-column-start: 1 #grid x coordinate
          grid-row-start: 1 #grid y coordinate

See next post for ui-resources.yaml and templates.

1 Like

How to set up (part 2)

ui-resources.yaml:

  base: #this is used by most cards for base characteristics
    color: var(--state-icon-active-color)
    show_state: true
    styles:
      card:
        - width: 150px
        - height: 150px
      icon: 
        - width: 60px
        - height: 60px
        - top: 5px
        - left: 10px
        - position: absolute
      name:
        - bottom: 25px
        - left: 10px
        - position: absolute
        - justify-self: start
      state:
        - bottom: 5px
        - left: 10px
        - position: absolute
        - justify-self: start
        - font-weight: lighter

  circle: #this puts a data field in a circle on the card
    custom_fields:
      circle: >
        [[[
            const input = variables.circle_input;
            const radius = 23;
            const circumference = radius * 2 * Math.PI;
            return `
              <svg viewBox="0 0 50 50">
                <circle cx="25" cy="25" r="${radius}" stroke="var(--disabled-text-color)" stroke-width="3" fill="none" />
                <text x="50%" y="52%" fill="var(--primary-text-color)" font-size="12" text-anchor="middle" alignment-baseline="middle">${input}</text>
              </svg>
            `;
        ]]]
    styles:
      custom_fields:
        circle:
          - top: 15px
          - right: 15px
          - width: 50px
          - position: absolute

  circle_dynamic: #as per circle, but the circle will be partially complete depending on percentage
    custom_fields:
      circle_dynamic: >
        [[[
          if (entity.state === 'on' || entity.state === 'Printing') {
            const input = variables.circle_input;
            const radius = 23;
            const circumference = radius * 2 * Math.PI;
            return `
              <svg viewBox="0 0 50 50">
                <style>
                  circle {
                    transform: rotate(-90deg);
                    transform-origin: 50% 50%;
                    stroke-dasharray: ${circumference};
                    stroke-dashoffset: ${circumference - input / 100 * circumference};
                  }
                </style>
                <circle cx="25" cy="25" r="${radius}" stroke="var(--disabled-text-color)" stroke-width="3" fill="none" />
                <text x="50%" y="52%" fill="var(--primary-text-color)" font-size="12" text-anchor="middle" alignment-baseline="middle">${input}%</text>
              </svg>
            `;
          }
        ]]]
    styles:
      custom_fields:
        circle_dynamic:
          - top: 15px
          - right: 15px
          - width: 50px
          - position: absolute

  alarm: #for alarm_control_panel entities
    lock:
      enabled: true
    template:
      - base
    state:
      - icon: 'mdi:shield-check'
        styles:
          icon:
            - color: var(--label-badge-green)
        value: disarmed
      - icon: 'mdi:shield-lock'
        styles:
          icon:
            - color: var(--label-badge-red)
        value: armed_away
    styles:
      lock:
        - position: absolute
        - top: 19px
        - right: 12px
    tap_action:
      action: call-service
      service: script.alarm_toggle
  
  camera: #to display a live camera feed
    template:
      - base
    show_live_stream: true
    show_state: false
    custom_fields:
      background1: " "
      background2: " "
    styles:
      card:
        - width: 308px
        - z-index: 2
      img_cell:
        - top: -10px
        - left: -10px
        - z-index: 1
        - width: 318px
        - height: 156px
      entity_picture:
        - width: 318px
        - position: absolute
      name:
        - z-index: 4
      custom_fields:
        background1:
          - z-index: 3
          - bottom: 0px
          - left: 0px
          - position: absolute
          - width: 308px
          - height: 75px
          - background-image: >
              [[[ return `linear-gradient(to top, var(--card-background-color), var(--card-background-color-transparent)` ]]]
        background2:
          - z-index: 3
          - bottom: 0px
          - left: 0px
          - position: absolute
          - width: 154px
          - height: 150px
          - background-image: >
              [[[ return `linear-gradient(to right, var(--card-background-color), var(--card-background-color-transparent)` ]]]

  climate: #for climate entities
    template:
      - base
      - circle
    state_display: >-
      [[[ return `Set: ${(entity.attributes.temperature)}°C` ]]]
    variables:
      circle_input: >
        [[[ return `${(entity.attributes.current_temperature)}°C` ]]]
    state:
      - styles:
          card:
            - filter: opacity(25%)
          icon:
            - filter: grayscale(100%)
          state:
            - filter: opacity(0%)
        value: unavailable


  default: #a starting card if you're not sure what to use
    template:
      - base
    color: var(--state-icon-active-color)
    hold_action:
      action: more-info
    state:
      - styles:
          card:
            - filter: opacity(50%)
          icon:
            - filter: grayscale(100%)
        value: 'off'
      - styles:
          card:
            - filter: opacity(25%)
          icon:
            - filter: grayscale(100%)
        value: unavailable
    tap_action:
      action: toggle
  
  garbage: #for use with the Garbage Collection (HACS) sensors
    template:
      - base
    tap_action:
      action: none
    state_display: >-
      [[[ if (entity.state == "2") return `in ${entity.attributes.days} days`;
      else if (entity.state == "1") return "Tomorrow";
      else if (entity.state == "0") return "Today"
      ]]]
  
  greeting: #Good Morning/Afternoon/Evening
    name: >-
      [[[ var d = new Date(); var n = d.getHours(); if (0 <= n && n
      < 12) return "Good morning."; else if (12 <= n && n < 18)
      return "Good afternoon."; else if (18 <= n && n < 24) return
      "Good evening."; else return "ERROR";]]]
    styles:
      card:
        - width: 300px
        - height: 60px
        - background: none
        - box-shadow: none
      name:
        - justify-self: start
        - margin-left: 10px
        - font-weight: normal
        - font-size: xx-large
    tap_action:
      action: none
  
  header: #a basic header for pages
    tap_action:
      action: none
    show_icon: false
    show_name: true
    show_state: false
    styles:
      card:
        - width: 300px
        - height: 40px
        - background: none
        - box-shadow: none
      name:
        - justify-self: start
        - margin-left: 10px
        - font-weight: lighter
        - font-size: x-large
  
  light: #for light entities. brightness displayed in dynamic circle
    template:
      - base
      - circle_dynamic
    variables:
      circle_input: >
        [[[ return Math.round(entity.attributes.brightness / 2.54); ]]]
    color: auto-no-temperature
    hold_action:
      action: more-info
    show_state: true
    state:
      - styles:
          card:
            - filter: opacity(50%)
          icon:
            - filter: grayscale(100%)
          label:
            - background-color: var(--card-background-color)
        value: 'off'
      - styles:
          card:
            - filter: opacity(25%)
          icon:
            - filter: grayscale(100%)
          label:
            - background-color: var(--card-background-color)
        value: unavailable
      - operator: template
        value: >-
          [[[ if (entity.attributes.rgb_color == "255,255,255") return true; else return false ]]]
        styles:
          icon:
            - color: var(--state-icon-active-color)
    tap_action:
      action: toggle
  
  media: #for media players; set up for my Sonos system - cards will appear faded if media is paused
    template:
      - base
    state_display: >-
      [[[ if (entity.attributes.media_title == undefined) return `${entity.state}`[0].toUpperCase() + `${entity.state}`.substring(1);
      else if (entity.attributes.media_artist == undefined) return `${entity.state}`[0].toUpperCase() + `${entity.state}`.substring(1) + `: ${entity.attributes.media_title}`;
      else return `${entity.state}`[0].toUpperCase() + `${entity.state}`.substring(1) + `: ${entity.attributes.media_title} - ${entity.attributes.media_artist}` ]]]
    state:
      - styles:
          card:
            - filter: opacity(50%)
          icon:
            - filter: grayscale(100%)
        value: 'paused'
      - styles:
          card:
            - filter: opacity(25%)
          icon:
            - filter: grayscale(100%)
        value: unavailable
    custom_fields:
      background1: " "
      background2: " "
    styles:
      card:
        - background-size: cover
        - background-image: '[[[ return `url("${entity.attributes.entity_picture}")` ]]]'
        - background-position: center center
        - width: 308px
        - height: 150px
      icon: 
        - z-index: 2
      name:
        - z-index: 2
      state:
        - z-index: 2
      custom_fields:
        background1:
          - top: 75px
          - right: 0px
          - position: absolute
          - width: 308px
          - height: 75px
          - background-image: >
              [[[ return `linear-gradient(to top, var(--card-background-color), var(--card-background-color-transparent)` ]]]
        background2:
          - top: 0px
          - right: 154px
          - position: absolute
          - width: 154px
          - height: 150px
          - background-image: >
              [[[ return `linear-gradient(to right, var(--card-background-color), var(--card-background-color-transparent)` ]]]
  
  person: #for person entities
    template:
      - base
    show_entity_picture: true
    state:
      - operator: '!='
        styles:
          card:
            - filter: opacity(50%)
          icon:
            - filter: grayscale(100%)
        value: home
    styles:
      icon:
        - border-radius: 50%
  
  sensor: #for sensor entities. needs work
    template:
      - base

  task: #keep track of chores, displays when chore last done using an input_datetime, tap the card to reset the timer
        #cards go red when the chore is overdue, defaults to 7 days
        #to change the overdue limit on a specific card in ui-lovelace.yaml, add:
        #variables:
        #  days: x (where x is number of days)
    template:
      - base
    variables:
      days: 7
    state_display: >-
      [[[
      return html`
        <ha-relative-time
          .hass="${hass}"
          .datetime="${(entity.attributes.timestamp)*1000}"
        ></ha-relative-time>`
      ]]]
    state:
      - operator: template
        value: >-
          [[[ if (Math.round(Date.now()/1000) - entity.attributes.timestamp > (variables.days*86400)) return true; else return false ]]]
        styles:
          icon:
            - color: var(--label-badge-red)
    tap_action:
      action: call-service
      service: input_datetime.set_datetime
      service_data:
        entity_id: entity
        timestamp: '[[[return Math.round(Date.now()/1000);]]]'
      confirmation:
        text: '[[[ return `Complete this task?` ]]]'
    hold_action:
      action: more-info

  weather: #for weather entities. I have only tested with Dark Sky
    template:
      - base
      - circle
    variables:
      circle_input: '[[[ return `${entity.attributes.temperature}°C` ]]]'
    show_state: true
    state:
      - icon: 'mdi:weather-night'
        value: clear-night
      - icon: 'mdi:weather-cloudy'
        value: cloudy
      - icon: 'mdi:weather-fog'
        value: fog
      - icon: 'mdi:weather-hail'
        value: hail
      - icon: 'mdi:weather-lightning'
        value: lightning
      - icon: 'mdi:weather-lightning-rainy'
        value: lightning-rainy
      - icon: 'mdi:weather-partly-cloudy'
        value: partlycloudy
      - icon: 'mdi:weather-pouring'
        value: pouring
      - icon: 'mdi:weather-rainy'
        value: rainy
      - icon: 'mdi:weather-snowy'
        value: snowy
      - icon: 'mdi:weather-snowy-rainy'
        value: snowy-rainy
      - icon: 'mdi:weather-sunny'
        value: sunny
      - icon: 'mdi:weather-windy'
        value: windy
      - icon: 'mdi:weather-windy-variant'
        value: windy-variant
      - icon: 'mdi:weather-cloudy-alert'
        value: exceptional

Examples of cards

Alarm:
Screenshot 2021-03-30 at 23.19.14

Camera:
Screenshot 2021-03-30 at 23.21.00

Climate:
Screenshot 2021-03-30 at 23.19.28

Default:
Screenshot 2021-03-30 at 23.20.29

Garbage:
Screenshot 2021-03-30 at 23.19.23

Greeting:
Screenshot 2021-03-30 at 23.19.32

Header:
Screenshot 2021-03-30 at 23.23.01

Light:
Screenshot 2021-03-30 at 23.19.57

Media:
Screenshot 2021-03-30 at 23.20.09

Person:
Screenshot 2021-03-30 at 23.19.08

Sensor:
Screenshot 2021-03-30 at 23.20.22

Task:
Screenshot 2021-03-30 at 23.21.12

Weather:
Screenshot 2021-03-30 at 23.19.18

Hey

So im using the above but have warnings in my logs about the automation already running. Do you ge this?

Yes. Doesn’t affect functionality but something I’ve been meaning to fix. Usually happens if several lights get turned on simultaneously. Will fix at some point

EDIT: I don’t get this any more because I use nodered for the automation - however try this and let me know if it works:

- alias: Update Light Count
    initial_state: True
    mode: restart    #this line is new
    trigger:
      platform: event
      event_type: state_changed
    condition:
      condition: template
      value_template: "{{ trigger.event.data.entity_id.startswith('light') }}"
    action:
      service: homeassistant.update_entity
      entity_id: sensor.active_lights

Did the floors get mopped yet? I laughed when I saw that todo as mine need mopping too.

1 Like