A different take on designing a Lovelace UI

Do you want to show me your yaml for sensor.garbage_tomorrow ?

@Schocker

I’m just cramming in cards over the markdown card, I think it’ll be easier if you replace that grid area with a vertical stack and place your cards in that instead.

Excellent idea! Thanks @Mattias_Persson

Sure, integration through HACS: GitHub - bruxy70/Garbage-Collection: 🗑 Custom Home Assistant sensor for scheduling garbage collection (or other regularly re-occurring events - weekly on given days, semi-weekly or monthly)

And this is my garbage.yaml file:

sensor:
  - platform: template
    sensors:
      garbage_today:
          friendly_name: Garbage Today
          value_template: >-
            {% if states.sensor.bio_afval.attributes.days == 0 %} gft
            {% elif states.sensor.plastic_afval.attributes.days == 0 %} plastic
            {% elif states.sensor.papier_afval.attributes.days == 0 %} papier
            {% else %} geen
            {% endif %}
  
  - platform: template
    sensors:
      garbage_tomorrow:
          friendly_name: Garbage Tomorrow
          value_template: >-
            {% if states.sensor.bio_afval.attributes.days == 1 %} gft
            {% elif states.sensor.plastic_afval.attributes.days == 1 %} plastic
            {% elif states.sensor.papier_afval.attributes.days == 1 %} papier
            {% else %} geen
            {% endif %}
            
  - platform: template
    sensors:
      bio_afval_date:
          friendly_name: GFT
          value_template: "{{ as_timestamp(states.sensor.bio_afval.attributes.next_date) | timestamp_custom('%d %b') }}"

  - platform: template
    sensors:
      plastic_afval_date:
          friendly_name: Plastic
          value_template: "{{ as_timestamp(states.sensor.plastic_afval.attributes.next_date) | timestamp_custom('%d %b') }}"

  - platform: template
    sensors:
      papier_afval_date:
          friendly_name: Papier
          value_template: "{{ as_timestamp(states.sensor.papier_afval.attributes.next_date) | timestamp_custom('%d %b') }}"
1 Like

@Schocker hope you can share once you’ve managed xD

1 Like

Thank you. At the moment I am using a “normal” calendar which contains the collection days as a whole day event.

Dont want to change to a extra component. Thought you are maybe using it the same way.

Just need a sensor which is on on the day before.

Rehauled my garbage-button a little bit.
I use a google calendar with collection days as a full-day event. This is provided by the garbage collector.
The button-card turns on one day before they will be collected, in the color of the bin. And it turns off the day after. On every day, it shows me, how many days till collection.
The days till the collection are done with a separate sensor.

At the moment I have problems with the icon, something from the basetemplate is overwriting here, but I dont get it.

ui-lovelace.yaml

          - type: custom:button-card
            entity: calendar.abfallkalender
            template:
              - abfall

button-card-templates.yaml

base:
  variables:
    state: >
      [[[ return entity === undefined || entity.state; ]]]
    timeout: >
      [[[ return entity === undefined || Date.now() - Date.parse(entity.last_changed); ]]]
    light_color: >
      [[[ return entity === undefined ? 'var(--state-icon-color)' : 'var(--button-card-light-color-no-temperature)'; ]]]
  aspect_ratio: 1/1
  show_state: true
  show_icon: false
  state_display: >
    [[[ if (variables.state === true) return 'Unbekannt'; ]]]
  tap_action:
    ui_sound_tablet: |
      [[[
        const tablet = states['switch.dashboard_screensaver'];
        const screensaver = states[tablet] === undefined || states[tablet].state;

        if (variables.state === 'off' && screensaver === 'off') {
          hass.callService('media_player', 'play_media', {
            entity_id: 'media_player.dashboard_fullykiosk',
            media_content_id: '/local/sound/on.m4a',
            media_content_type: 'music'
          });
        }
        if (variables.state === 'on' && screensaver === 'off') {
          hass.callService('media_player', 'play_media', {
            entity_id: 'media_player.dashboard_fullykiosk',
            media_content_id: '/local/sound/off.m4a',
            media_content_type: 'music'
          });
        }
      ]]]
    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  circle"
          "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: 115%
    state:
      - justify-self: start
      - line-height: 115%
    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: 10%
      - --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.34vw;
      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);
      }
    }

...

abfall:
  show_icon: true
  icon: mdi:delete
  template:
    - base
  name: >
    [[[
      if (states[entity.entity_id].attributes.message === 'Biotonne in Memmingen' )
        return 'Biotonne';
      if (states[entity.entity_id].attributes.message === 'Restmülltonne in Memmingen')
        return 'Restmüll';
      else
        return 'Altpapier';
    ]]]
  state_display: >
    [[[ 
      { return states['sensor.abfall_morgen'].state; }
    ]]]
  state:
    - operator: template
      value: >
        [[[ return (states[entity.entity_id].attributes.message == 'Biotonne in Memmingen') && ((states['sensor.abfall_morgen'].state == 'Morgen') || (states['sensor.abfall_morgen'].state == 'Heute')) ]]]
      styles:
        card: [background-color: "rgba(186, 146, 0, 0.8)"]
        name: [color: "rgba(0, 0, 0, 0.6)"]
        state: [color: "rgba(0, 0, 0, 0.6)"]
    - operator: template
      value: >
        [[[ return (states[entity.entity_id].attributes.message == 'Restmülltonne in Memmingen') && ((states['sensor.abfall_morgen'].state == 'Morgen') || (states['sensor.abfall_morgen'].state == 'Heute')) ]]]
      styles:
        card: [background-color: "rgba(255, 255, 255, 0.8)"]
        name: [color: "rgba(0, 0, 0, 0.6)"]
        state: [color: "rgba(0, 0, 0, 0.6)"]
    - operator: template
      value: >
        [[[ return (states[entity.entity_id].attributes.message == 'Altpapier') && ((states['sensor.abfall_morgen'].state == 'Morgen') || (states['sensor.abfall_morgen'].state == 'Heute')) ]]]
      styles:
        card: [background-color: "rgba(0, 100, 200, 0.8)"]
        name: [color: "rgba(0, 0, 0, 0.6)"]
        state: [color: "rgba(0, 0, 0, 0.6)"]

abfall_morgen.yaml

platform: template
sensors:
  abfall_morgen:
    friendly_name: abfall_morgen
    entity_id: sensor.date
    value_template: >
      {% set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
      {% set event = state_attr('calendar.abfallkalender', 'start_time') | as_timestamp %}
      {% set delta = ((event - midnight) // 86400) | int %}
      {% if delta == 0 %}
        Heute
      {% elif delta == 1 %}
        Morgen
      {% elif delta == 2 %}
        Übermorgen
      {% else %}
        In {{ delta }} Tagen
      {% endif %}
1 Like

Awesome work! I managed to use your UI as well. I am just wondering how to also adopt the background in the very top bar of my phone (iphone XS)? It is currently just displaying a color from the themes.yml. Did you manage to solve this?

Attempted to replace the side bar with a vertical stack since I’d like the graphical weather and don’t have a Dark Sky acct anyways so I couldn’t be bothered to simulate the sensors with SMHI.

However… the sidebar suddenly renders with a huge height, pushing the grid way down to 2000+px in height.

Any ideas why this is happening?

@Davst, Can you paste your template code for us to take a look?

@Vasco Here is what my sidebar looks like now.

Here is my code:

      - 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.regina
            show_forecast: false
          - type: "custom:hui-element"
            card_type: "custom:auto-entities"
            card:
              type: entities
            filter:
              include:
                - entity_id: sensor.anniversary_xyzs_birthday
                  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

I’ve commented out the popups as I don’t have them built out yet and I “xyz’d” out the innocent in my anniversary’s card :laughing:

Here is my template code:

    #################################################
    #                                               #
    #             SIDEBAR MARKDOWN CARD             #
    #                                               #
    #################################################

    "grid-layout$hui-markdown-card:first-of-type":
      "$ha-card>ha-markdown":
        .: |
          ha-markdown {
            padding: var(--custom-layout-card-padding) 2vw 0 var(--custom-layout-card-padding) !important;
            pointer-events: none;
          }
          /* phone */
          @media screen and (max-width: 800px) {
            ha-markdown {
              padding: 0 !important;
            }
        $: |
          /* clock */
          p {
            font-family: SF Text;
            font-size: var(--markdown-sidebar-clock-font-size);
            font-weight: 200;
            line-height: var(--markdown-sidebar-clock-line-height);
            letter-spacing: -0.05vw;
            color: var(--ha-card-header-color);
            margin: 0 0 0 0;
          }
          /* colon */
          p > span > span {
            position: relative;
            top: -.09em;
          }
          /* text */
          p > font > b {
            font-family: SF Display;
            font-size: var(--markdown-sidebar-font-size);
            line-height: var(--markdown-sidebar-line-height);
            font-weight: 300;
            letter-spacing: 0.06vw;
            color: #6a7377;
            margin: 0 0 0 0;
          }
          
          b > p {
            font-family: SF Display;
            font-size: var(--markdown-sidebar-font-size);
            line-height: var(--markdown-sidebar-line-height);
            font-weight: 300;
            letter-spacing: 0.06vw;
            color: var(--ha-card-header-color);
          }

          /* phone */
          @media screen and (max-width: 800px) {
            p {
              font-size: calc(var(--markdown-sidebar-clock-font-size) * 2.6 );
              line-height: calc(var(--markdown-sidebar-clock-line-height) * 2.6 );
            }

            p > font > b {
              font-size: calc(var(--markdown-sidebar-font-size) * 2.6 );
              line-height: calc(var(--markdown-sidebar-line-height) * 2.6 );
              letter-spacing: 0.16vw;
            }

            b > p {
              font-size: calc(var(--markdown-sidebar-font-size) * 2.6 );
              line-height: calc(var(--markdown-sidebar-line-height) * 2.6 );
              letter-spacing: 0.16vw;
            }
          }

    #################################################
    #                                               #
    #             Weather Card                      #
    #                                               #
    #################################################

    "grid-layout$hui-element:first-of-type":
      $: |
        ha-card {
          padding: 15% 10% 0 7% !important;
        }

        /* phone */
        @media screen and (max-width: 800px) {
          ha-card {
            padding: 0 0 0 0 !important;
          }
        }

    #################################################
    #                                               #
    #             Special Dates Card                #
    #                                               #
    #################################################

    "grid-layout$hui-entities-card":
      $: |
        ha-card {
          height: 33vh !important;
          padding: 10% 5% 0 0 !important;
        }

        /* phone */
        @media screen and (max-width: 800px) {
          ha-card {
            padding: 0 0 0 0 !important;
          }
        }

    #################################################
    #                                               #
    #             SIDEBAR BUTTON CARDS              #
    #                                               #
    #################################################

    "grid-layout$hui-grid-card:first-of-type":
      .: |
        hui-grid-card:first-of-type {

        }

        @keyframes fade {
          0%, 30% {
            opacity: 0;
          }
          100% {
            opacity: 1;
          }
        }

        /* phone */
        @media screen and (max-width: 800px) {
          hui-grid-card:first-of-type {
            align-items: flex-start;
          }
        }

      $: |
        #root {
          padding: 30% 13% 13% 13%;
          gap: 8% !important;
        }

        /* phone */
        @media screen and (max-width: 800px) {
          #root {
            padding: 0 0 0 60%;
          }
        }

Special thanks to @Mattias_Persson for the awesome template and the suggestion to use a vertical stack card.

5 Likes

Nice work mate. That looks amazing!

Does anyone know if it’s possible to change a custom:button-card style based on the lovelace view you are on?
Just thinking about navigation buttons and would like the active view nav button to be different.
I’ve done this with the sidebar card, but I’d ideally not like to rely on another card that could go away at any time. Plus, doing it all myself means the placement of elements is under my control. :slight_smile:

You should head over to the correct thread, since that is a very specific button-cards question. Anyway, I asked this myself a while ago and it should be possible, though it will require some work :wink:

I experimented with this and had an issue with the cards updating (and showing the correct color when active:

However, I haven’t tried that code from Marius in the first link. I’ll try sometime and hope it works. For now I have kept my own implementation, which is a row of button cards on each dashboard. And then give the button that is active a different color on the dashboard it’s in. So each dashboard contains the same buttons, but each dashboard has a different one with an ‘active’ style color.

Ah yeah I see what you are doing.
I thought of doing that as well, but it means that each view would have to have the same sidebar. Not impossible, but would just be repeated code which I was trying to stay away from.

Thanks for the ideas though! It gives me a good starting point.

Yes, the solution is the first link of Marius. I haven’t gotten around that myself yet, so I still use the ‘duplicate on each dashboard and change 1 button-card’-method.

1 Like

Hi @Schocker thanks for sharing! got it almost, but now have same issue with the issue you had with the padding.

Can you tell me how you fixed that so I have it fixed too? Thanks!

Interested too, have the same padding problem, and the markdown with hour and date is out from the screen on the left.
Thx !

In my case I removed the COVID markdown card but forgot to remove the template code that referred to the COVID markdown card. Once I removed the template code for the COVID markdown, the padding worked as expected.

1 Like

Alright so the concept of this is absolutely amazing and this dashboard looks incredible - after about 12 hours I have finally got to the stage that the dashboard is installed but doesn’t look anything like the above…

Is there anyway to get this installed as a package? I have just realized the amount of rework that I need to do to even get this to work for my house and I have no idea where to start.

i.e. we have a robot vaccum but I have no idea how to make that show up. We don’t have Plex here so how do I remove that… We don’t have lights but we have an alarm system I need to add and a pool. Anyone have any ideas where I can start?