🟣 Rounded - Dashboard guide

Hey there! I wanted to share something with this wonderful community that has already been so helpful to me regarding my own Home Assistant installation. A while back, I sent in my dashboard for Everything Smart Home’s video and I noticed there was quite a bit of interest in how I built it. So, it’s about time I finally shared my process.

This is my Rounded dashboard made for our mobile devices. It’s my second dashboard that I’ve created using various custom cards made by other amazing community members. I’m by no means a developer, more a designer with development interests, so I’m standing on the shoulder of giants.

Custom cards

These are the custom cards I used in the dashboard, make sure you’ve installed all of them before continuing.

And in my specific case, I am using a Roborock S5 Max robot vacuum, so I also installed these Xiaomi custom cards.

Side note

There are different ways to style the custom cards, and I have tried quite a few methods. I believe that the current method is the most stable and easiest to understand. However, this does mean that it may not be the most scalable method, since I have chosen to style each custom button-card individually. I have previously tried to assign a card-mod class to each custom button-card and modify it in the theme.yaml, but this caused some complications, so I moved away from it.

Oh and my dashboard is mainly in Dutch, feel free to ask a question if something is unclear due to the different language :slightly_smiling_face:

Theme setup

Let’s first make sure everything is set up correctly so Home Assistant picks up the right theme. I have placed all my theme.yaml files in their own folder to keep things organized. Follow the following steps if you also prefer this.

  1. Go to your configuration.yaml and add this code:

    frontend:
      themes: !include_dir_merge_named themes/
    
  2. Now create a ‘themes’ folder in your root folder and create a new file called Rounded.yaml in the themes folder.

Tints & Colors

Tints

The intention was for this dashboard to be usable in both light and dark modes. To make this workable, I pre-determined shades of gray for both modes. Depending on the active mode, the light or dark shade will be chosen. The shades of gray are used in the theme as ‘contrast’ variables. Variable ‘contrast1’ represents the base color, so black for the dark mode and white for the light mode. And the higher we make the contrast number, the more contrast the shade will have compared to the base color. In other words, variable ‘contrast20’ will be the opposite, white for the dark mode and black for the light mode.

Colors

Regarding the colors, I’ve kept it simple and chosen 6 colors that work well in both light and dark modes and provide enough contrast. The only difference is the faded color tones for the backgrounds. For the light mode, the color has 20% opacity and for the dark mode, 15% opacity.

Rounded.yaml

Let’s start by setting up the Rounded.yaml. The theme has three sections:

  • Default global variables
  • Custom global variables
  • Mode variables

Default global variables

Here I adjust all variables that already exist within Home Assistant. These are mainly colors, but can also be margins, paddings, border-radiuses, or anything else. I then adjust the existing variables to link them to new custom variables.

Custom global variables

Here we create new custom variables to define the colors mentioned above. Since the colors and 20 shades of gray will not differ in value between light and dark mode, we can define them as global variables. However, the use of the 20 shades of gray is different between light and dark mode, which is why we will also define custom variables per mode.

Mode variables

Now that we have defined the global variables for the 20 shades of gray (black1 through black20 and white1 through white20), we still need to indicate how they should be used for each mode. For this, we use the variable ‘contrast’. As discussed above, variable ‘contrast1’ will be the base color and ‘contrast20’ will provide the most contrast compared to the base color. So in light mode, ‘contrast1’ will be equal to ‘white1’ and in dark mode, ‘contrast1’ will be equal to ‘black1’. We do this so that in the future, we can simply give a card the background color ‘contrast1’ and a title color ‘contrast20’, and this will be properly handled for both modes.

Code

Finally, we end up with this code for the first part of the Rounded.yaml:

Rounded:
  ########################################################
  ############### Default global variables ###############
  ########################################################

  # Spacings and radius
  horizontal-stack-card-margin: 0px 8px
  vertical-stack-card-margin: 8px 0px
  grid-card-gap: 16px
  ha-card-border-width: "0px" # Removes default 1px line
  ha-card-border-radius: 24px
  masonry-view-card-margin: 40px 20px
  # Main Interface Colors
  primary-color: var(--blue)
  accent-color: var(--blue)
  primary-background-color: var(--contrast1)
  secondary-background-color: var(--contrast2)
  divider-color: var(--contrast3)
  # Text
  primary-text-color: var(--contrast20)
  secondary-text-color: var(--contrast9)
  text-primary-color: var(--contrast20)
  disabled-text-color: var(--contrast6)
  text-accent-color: var(--contrast1)
  # Header:
  app-header-background-color: var(--contrast1)
  app-header-text-color: var(--contrast20)
  app-header-selection-bar-color: transparant
  app-header-edit-background-color: var(--contrast2)
  app-header-edit-text-color: var(--contrast20)
  # Cards
  card-background-color: var(--contrast2)
  ha-card-background: var(--contrast2)
  ha-card-border-color: var(--contrast6)
  paper-listbox-background-color: var(--contrast3)
  # Tile card
  state-unavailable-color: var(--contrast6)
  state-light-off-color: var(--contrast10)
  state-light-on-color: var(--yellow)
  # Sidebar Menu
  sidebar-icon-color: var(--contrast6)
  sidebar-text-color: var(--contrast20)
  sidebar-background-color: var(--contrast2)
  sidebar-selected-icon-color: var(--blue)
  sidebar-selected-text-color: var(--blue)
  # Buttons
  paper-item-icon-color: var(--contrast9)
  mdc-button-outline-color: var(--contrast6)
  # States and Badges
  state-icon-color: var(--contrast9)
  # Sliders
  paper-slider-knob-color: var(--contrast20)
  paper-slider-knob-start-color: var(--contrast15)
  paper-slider-pin-color: var(--contrast5)
  paper-slider-pin-start-color: var(--contrast4)
  paper-slider-active-color: var(--contrast15)
  paper-slider-secondary-color: var(--contrast7)
  paper-slider-container-color: var(--contrast5)
  # Switches
  switch-checked-button-color: var(--green)
  switch-checked-track-color: var(--green)
  switch-unchecked-button-color: var(--contrast9)
  switch-unchecked-track-color: var(--contrast6)
  # Toggles
  paper-toggle-button-checked-button-color: var(--switch-checked-button-color)
  paper-toggle-button-checked-bar-color: var(--switch-checked-track-color)
  paper-toggle-button-unchecked-button-color: var(--switch-unchecked-button-color)
  paper-toggle-button-unchecked-bar-color: var(--switch-unchecked-track-color)
  # Table
  table-row-background-color: var(--contrast2)
  table-row-alternative-background-color: var(--contrast3)
  data-table-background-color: var(--contrast1)
  mdc-text-field-fill-color: var(--contrast3)
  # Input
  input-fill-color: var(--contrast3)
  input-dropdown-icon-color: var(--contrast9)
  material-background-color: var(--contrast2)
  input-ink-color: var(--contrast20)
  input-label-ink-color: var(--contrast9)
  input-idle-line-color: var(--contrast7)
  input-hover-line-color: var(--contrast20)
  mdc-select-fill-color: var(--input-fill-color)
  mdc-select-ink-color: var(--input-ink-color)
  mdc-select-label-ink-color: var(--input-label-ink-color)
  mdc-select-idle-line-color: var(--input-idle-line-color)
  mdc-select-dropdown-icon-color: var(--input-dropdown-icon-color)
  mdc-select-hover-line-color: var(--input-hover-line-color)
  mdc-text-field-disabled-fill-color: var(--contrast3)
  # Modal screen
  mdc-theme-surface: var(--contrast2)
  # Checkboxes
  mdc-checkbox-unchecked-color: var(--contrast15)
  # Colors
  orange-color: var(--orange)
  green-color: var(--green)
  blue-color: var(--blue)
  red-color: var(--red)
  purple-color: var(--purple)
  yellow-color: var(--yellow)
  grey-color: var(--contrast10)

  #######################################################
  ############### Custom global variables ###############
  #######################################################

  # Black / White
  black: "#000000"
  white: "#FFFFFF"
  # Colors
  purple: rgb(var(--purple-rgb))
  yellow: rgb(var(--yellow-rgb))
  orange: rgb(var(--orange-rgb))
  red: rgb(var(--red-rgb))
  green: rgb(var(--green-rgb))
  blue: rgb(var(--blue-rgb))
  # Color tints
  purple-tint: rgba(var(--purple-rgb),var(--color-tint))
  yellow-tint: rgba(var(--yellow-rgb),var(--color-tint))
  orange-tint: rgba(var(--orange-rgb),var(--color-tint))
  red-tint: rgba(var(--red-rgb),var(--color-tint))
  green-tint: rgba(var(--green-rgb),var(--color-tint))
  blue-tint: rgba(var(--blue-rgb),var(--color-tint))
  # Gradients
  brightness: linear-gradient(90deg, rgba(var(--brightness-low-rgb), 0.4) 0%, rgba(var(--brightness-high-rgb), 1) 100%)
  brightness-tint: linear-gradient(90deg, rgba(var(--brightness-low-rgb), 0.06) 0%, rgba(var(--brightness-high-rgb), var(--color-tint)) 100%)
  temperature: linear-gradient(90deg, rgba(var(--temperature-low-rgb), 01) 0%, rgba(var(--temperature-high-rgb), 1) 100%)
  temperature-tint: linear-gradient(90deg, rgba(var(--temperature-low-rgb), var(--color-tint)) 0%, rgba(var(--temperature-high-rgb), var(--color-tint)) 100%)
  # Color RGB variables
  purple-rgb: 239, 177, 255
  yellow-rgb: 255, 218, 120
  orange-rgb: 255, 181, 129
  red-rgb: 255, 145, 138
  green-rgb: 206, 245, 149
  blue-rgb: 144, 191, 255
  # Gradient RGB variables
  brightness-low-rgb: 232, 176, 29
  brightness-high-rgb: 255, 211, 94
  temperature-low-rgb: 177, 197, 255
  temperature-high-rgb: 255, 175, 131
  # Contrast variables
  black1: "#000000"
  black2: "#111318"
  black3: "#171A21"
  black4: "#1C1F27"
  black5: "#262A35"
  black6: "#353946"
  black7: "#434856"
  black8: "#535865"
  black9: "#636774"
  black10: "#777A83"
  white10: "#898C94"
  white9: "#969AA6"
  white8: "#A4A9B6"
  white7: "#B3B8C6"
  white6: "#C3C8D5"
  white5: "#D4D8E2"
  white4: "#E1E5EF"
  white3: "#EAEDF6"
  white2: "#F4F6FB"
  white1: "#FFFFFF"

  ########################################################
  ############### Variables based on modes ###############
  ########################################################

  modes:
    dark:
      # Black white contrats
      contrast1: var(--black1)
      contrast2: var(--black2)
      contrast3: var(--black3)
      contrast4: var(--black4)
      contrast5: var(--black5)
      contrast6: var(--black6)
      contrast7: var(--black7)
      contrast8: var(--black8)
      contrast9: var(--black9)
      contrast10: var(--black10)
      contrast11: var(--white10)
      contrast12: var(--white9)
      contrast13: var(--white8)
      contrast14: var(--white7)
      contrast15: var(--white6)
      contrast16: var(--white5)
      contrast17: var(--white4)
      contrast18: var(--white3)
      contrast19: var(--white2)
      contrast20: var(--white1)
      # Color tint transparancy
      color-tint: "0.15"
      # Contrast RGB variables
      contrast1-RGB: 0,0,0

    light:
      # Black white contrats
      contrast1: var(--white1)
      contrast2: var(--white2)
      contrast3: var(--white3)
      contrast4: var(--white4)
      contrast5: var(--white5)
      contrast6: var(--white6)
      contrast7: var(--white7)
      contrast8: var(--white8)
      contrast9: var(--white9)
      contrast10: var(--white10)
      contrast11: var(--black10)
      contrast12: var(--black9)
      contrast13: var(--black8)
      contrast14: var(--black7)
      contrast15: var(--black6)
      contrast16: var(--black5)
      contrast17: var(--black4)
      contrast18: var(--black3)
      contrast19: var(--black2)
      contrast20: var(--black1)
      # Color tint transparancy
      color-tint: "0.20"
      # Contrast RGB variables
      contrast1-RGB: 255,255,255

Apply theme

  1. Go to Developer Tools → Services.
  2. Run the service named “Home Assistant Frontend: Reload themes”.
  3. Next, open the service named “Home Assistant Frontend: Set theme” and run the service twice with both the “Dark” and “Light” modes checked.
  4. Now go to your profile and set the Theme to “Backend-selected”. You can also skip step 3 and directly set the theme to “Rounded” in your profile.

My dashboard

Let’s start with a quick look at my setup. My dashboard uses 4 main screens.

  • Home (mostly lights)
  • Robot vacuum
  • Television
  • Speaker

That’s it, at the moment we still live in an apartment so this gives us the opportunity to get away with few screens. I personally prefer to assign each screen a specific function, as this keeps things organised and prevents them from becoming too cluttered or difficult to manage.

Styling the cards

Let’s start styling the different cards. I will go through them one by one so we can work through the entire list.

Title card

This appears at the top of every screen. It provides some breathing space for each screen and immediately shows where you are located.

The title card is created using a custom button-card. See the code below. My dashboard also uses a custom font, which I repeat in the custom button-card for reliability, so it always loads correctly.

type: custom:button-card
name: Title here
styles:
  card:
    - font-family: In case of a custom font, otherwise you can remove this line
    - background: none
    - padding: 16px 0
    - '--mdc-ripple-press-opacity': 0
  name:
    - font-size: 32px
    - color: var(--contrast20)

Title card with badge

In addition, we also have a variant of the title card that can display an entity value. This should not be confused with the ‘chip’ card that you sometimes see. In this case, I use it only to display one entity, for example, the status of a robot vacuum. If you look at the code, you will see that we place a custom button-card inside another custom button-card. This also gives us the flexibility to make the overarching card non-clickable, but the badge clickable.

type: custom:button-card
name: Title here
custom_fields:
  badge:
    card:
      type: custom:button-card
      name: '[[[return states["vacuum.roborock_s5_max"].attributes.status]]]'
      label: >-
        [[[return states["vacuum.roborock_s5_max"].attributes.battery_level +
        "%"]]]
      show_label: true
      show_icon: false
      entity: vacuum.roborock_s5_max
      tap_action:
        action: more-info
        haptic: medium
      styles:
        grid:
          - grid-template-areas: '"n gutter l"'
          - grid-template-columns: min-content 5px min-content
          - grid-template-rows: min-content
        card:
          - font-family: In case of a custom font, otherwise you can remove this line
          - padding: 6px 10px
          - font-size: 12px
          - line-height: 18px
          - font-weight: 500
          - background: var(--contrast20)
        name:
          - color: var(--contrast1)
        label:
          - color: var(--contrast12)
styles:
  grid:
    - grid-template-areas: '"n" "badge"'
  card:
    - font-family: In case of a custom font, otherwise you can remove this line
    - background: none
    - padding: 16px 0
    - '--mdc-ripple-press-opacity': 0
  name:
    - font-size: 32px
    - color: var(--contrast20)
  custom_fields:
    badge:
      - margin: 16px auto 0 auto
      - '--mdc-ripple-press-opacity': 0.5

“Swipable” Graph card

This is a combination of the pre-installed Custom swipe card ****(by Bram Kragten) and the standard sensor card. Because we are going to style the standard sensor card, we will use card-mod. Card-mod offers two ways to style the cards. The first is directly in the code of the card, and the second is by adding a CSS class to the card and styling it from the Rounded.yaml. Since we are going to use the sensor card multiple times, it is more convenient to choose the second method. But first, let’s take a look at the code for the card. Currently, I have three temperature sensors in different rooms, and I place all three in the custom swipe-card. Then, I added some parameters to the custom slide-card so that all spacings and margins are correct. Feel free to check out the Github repo of the custom swipe-card to add other swipe effects if you like. I personally prefer the default effect.

type: custom:swipe-card
card_width: calc(100% - 48px)
parameters:
  centeredSlides: true
  slidesPerView: auto
  spaceBetween: 16
  initialSlide: 0
cards:
  - type: sensor
    entity: sensor.aqara_multi_sensor_woonkamer_temperature
    hours_to_show: 24
    detail: 1
    graph: line
    name: Woonkamer
    icon: none
    card_mod:
      class: graph
  - type: etc.. (copy the sensor card above to add more)

So now that we added the cards, we need to style them. As you can see, we added a card-mod class named ‘graph’ to each sensor card. Let’s now go back to our Rounded.yaml and add this code below the already placed code. Don’t forget to replace or remove the font declaration.

card-mod-theme: Rounded

  card-mod-view-yaml: |

    hui-masonry-view:
      $: |

        /* Swipe-card full width on mobile */

        @media screen and (max-width: 599px) {
          #columns .column swipe-card {
            margin-left: -4px;
            margin-right: -4px;
          }
        }

  card-mod-card-yaml: |

    .: |

      /* General changes */ 

      ha-card {
        transition: none !important;
        font-family: 'custom font, otherwise remove this line', 'Roboto', sans-serif !important;
      }

      /* Graph card style */

      .graph {
        background: var(--blue-tint);
        display: flex;
        overflow: hidden; /* Temporary fix for graph overflow bug */
      }
      
      .graph .name {
        font-size: 12px;
        line-height: 18px;
        background: var(--black);
        color: var(--white);
        padding: 6px 10px;
        border-radius: 100px;
        z-index: 1;
      }

      .graph .icon {
        display: none;
      }

      .graph .info {
        margin-top: 0;
        padding: 24px 24px 0 24px;
        order: 1;
      }

      .graph hui-graph-header-footer {
        order: 3;
      }

      .graph .header {
        padding: 0 24px;
        order: 2;
        margin: 4px 0 -16px 0;
        z-index: 1;
      }

Reload the theme

  1. Go to Developer Tools → Services.
  2. Run the service named ‘Home Assistant Frontend: Reload themes’.

The sensor cards should now be styled.

Scene buttons

Next on the list are the scene buttons. In our household, we often use a couple of scenes, which is why we have given them a prominent place. The buttons are created using custom button-cards in a grid card.

columns: 4
type: grid
cards:
  - type: custom:button-card
    icon: mdi:sofa-single
    aspect_ratio: 1/1
    tap_action:
      action: call-service
      service: scene.turn_on
      haptic: medium
      service_data:
        entity_id: scene.gezellig
    styles:
      card:
        - border-radius: 24px
        - background-color: var(--blue)
      icon:
        - color: var(--black)
  - type: etc.. (copy the custom button-card above to add more)

Light cards

These are also in their own grid card with 2 columns, but for clarity, I will only focus on the light card. The light card is a combination of the custom button-card and the my-slider-v2. The light cards have three statuses: on, off, and unavailable. Hence, value and if statements are used to check if the lamp is available and then whether it is on or off. Clicking on the cards turns the lamp on or off, and a hold action opens the more-info modal screen.

type: custom:button-card
name: Studeerkamer
icon: mdi:track-light
entity: light.studeerkamer_groep
tap_action:
  action: toggle
  haptic: medium
hold_action:
  action: more-info
  haptic: medium
custom_fields:
  slider:
    card:
      type: custom:my-slider-v2
      entity: light.studeerkamer_groep
      colorMode: brightness
      styles:
        container:
          - background: none
          - border-radius: 100px
          - overflow: visible
        card:
          - height: 16px
          - padding: 0 8px
          - background: |
              [[[
                if (entity.state == "on") return "linear-gradient(90deg, rgba(255,255,255, 0.3) 0%, rgba(255,255,255, 1) 100%)";
                else return "var(--contrast4)";
              ]]]
        track:
          - overflow: visible
          - background: none
        progress:
          - background: none
        thumb:
          - background: |
              [[[
                if (entity.state == "on") return "var(--black)";
                if (entity.state == "off") return "var(--contrast20)";
                else return "var(--contrast8)";
              ]]]
          - top: 2px
          - right: '-6px'
          - height: 12px
          - width: 12px
          - border-radius: 100px
styles:
  grid:
    - grid-template-areas: '"i" "n" "slider"'
    - grid-template-columns: 1fr
    - grid-template-rows: 1fr min-content min-content
  card:
    - font-family: In case of a custom font, otherwise you can remove this line
    - background: var(--contrast2)
    - padding: 16px
    - '--mdc-ripple-press-opacity': 0
  img_cell:
    - justify-self: start
    - width: 24px
  icon:
    - width: 24px
    - height: 24px
    - color: var(--contrast8)
  name:
    - justify-self: start
    - font-size: 14px
    - margin: 4px 0 12px 0
    - color: var(--contrast8)
state:
  - value: 'on'
    styles:
      card:
        - background: var(--yellow)
      icon:
        - color: var(--black)
      name:
        - color: var(--black)
  - value: 'off'
    styles:
      icon:
        - color: var(--contrast20)
      name:
        - color: var(--contrast20)

Colored light cards

Thanks to @StickyClient we also have a light card variant for colored light bulbs!

type: custom:button-card
name: Desk
icon: '[[[ return entity.attributes.icon ]]]'
entity: light.desk
tap_action:
  action: toggle
  haptic: medium
hold_action:
  action: more-info
  haptic: medium
custom_fields:
  slider:
    card:
      type: custom:my-slider-v2
      entity: "[[[ return entity.entity_id ]]]"
      colorMode: brightness
      styles:
        container:
          - background: none
          - border-radius: 100px
          - overflow: visible
        card:
          - height: 16px
          - padding: 0 8px
          - background: |
              [[[
                if (entity.state == "on") return "linear-gradient(90deg, rgba(255,255,255, 0.3) 0%, rgba(255,255,255, 1) 100%)";
                else return "var(--contrast4)";
              ]]]
        track:
          - overflow: visible
          - background: none
        progress:
          - background: none
        thumb:
          - background: |
              [[[
                if (entity.state == "on") return "var(--black)";
                if (entity.state == "off") return "var(--contrast20)";
                else return "var(--contrast8)";
              ]]]
          - top: 2px
          - right: '-8px'
          - height: 12px
          - width: 12px
          - border-radius: 10px
styles:
  grid:
    - grid-template-areas: '"i" "n" "slider"'
    - grid-template-columns: 1fr
    - grid-template-rows: 1fr min-content min-content
  card:
    - font-family: In case of a custom font, otherwise you can remove this line
    - background: var(--contrast2)
    - padding: 16px
    - '--mdc-ripple-press-opacity': 0
  img_cell:
    - justify-self: start
    - width: 24px
  icon:
    - width: 24px
    - height: 24px
    - color: var(--contrast8)
  name:
    - justify-self: start
    - font-size: 14px
    - margin: 4px 0 12px 0
    - color: var(--contrast8)
state:
  - value: 'on'
    styles:
      card:
        - background: |
              [[[
                  var color = entity.attributes?.rgb_color;
                  if (entity.state != "on"){
                    return 'var(--contrast20)';
                  }
                  else if (color){
                    return 'rgba(' + color + ')'
                  }
                  else{
                    return 'var(--yellow)'
                  }
              ]]]
      icon:
        - color: var(--black)
      name:
        - color: var(--black)
  - value: 'off'
    styles:
      icon:
        - color: var(--contrast20)
      name:
        - color: var(--contrast20)

Brightness and temperature sliders

This is specific to my scenario, but the top four lights are all in the living room. Instead of adjusting the brightness for each lamp every time, I have created a general slider for the brightness and temperature that only adjusts the values of the active living room lights. This can be done using a dynamic group that only contains the active lamps. If you want to know more about this, check out this topic. But let’s take a look at the cards themselves. The slider cards are also a combination of the custom button-card and the my-slider-v2 card.

type: custom:button-card
name: Helderheid
custom_fields:
  slider:
    card:
      type: custom:my-slider-v2
      entity: light.actieve_woonkamer_lampen
      colorMode: brightness
      styles:
        container:
          - border-radius: 100px
          - overflow: visible
          - background: none
        card:
          - height: 40px
          - padding: 0 20px
          - background: var(--brightness)
        track:
          - overflow: visible
          - background: none
        progress:
          - background: none
        thumb:
          - background: var(--black)
          - top: 2px
          - right: '-18px'
          - height: 36px
          - width: 36px
          - border-radius: 100px
styles:
  grid:
    - grid-template-areas: '"n" "slider"'
    - grid-template-columns: 1fr
    - grid-template-rows: 1fr min-content min-content
  card:
    - font-family: In case of a custom font, otherwise you can remove this line
    - background: var(--brightness-tint)
    - padding: 16px
    - '--mdc-ripple-press-opacity': 0
  name:
    - justify-self: start
    - font-size: 14px
    - margin: 4px 0 12px 0
    - color: var(--contrast20)

Home screen lay-out

As you could already see, my ‘Home’ screen consists of:

  • Title card
  • Graph card
  • Scene buttons
  • Grid of lights in the living room
  • Sliders of the living room lights
  • Grid of other lights around our apartment

However, the layout is still a point to dive deeper into. The brightness and temperature sliders are linked to the top grid of four lights by means of an overarching grid card. This ensures that there is a minimal margin between the slider and the top grid and a large margin between the slider and the bottom grid. To make it a bit clearer, I have visualized the structure.

83 Likes

Vacuum map card

The vacuum screen starts with a title card with badge that we have discussed before. Below that is the vacuum map. Essentially, this is the xiaomi vacuum map card (by PiotrMachowski) that uses the xiaomi cloud map extractor (also by PiotrMachowski). However, we have slightly styled the card to fit in with the rest of the design. Unfortunately, I was not able to use the same card-mod method as the graph card because the card-mod class is not picked up by the xiaomi vacuum map card. So we style it in the code of the card itself.

type: custom:xiaomi-vacuum-map-card
language: nl
icons: []
tiles: []
map_source:
  camera: camera.xiaomi_cloud_map_extractor
calibration_source:
  camera: true
entity: vacuum.roborock_s5_max
vacuum_platform: default
map_locked: true
two_finger_pan: false
map_modes:
  - template: vacuum_clean_zone
  - template: vacuum_goto
card_mod:
  style:
    ha-button-menu:
      $:
        mwc-menu:
          $:
            mwc-menu-surface:
              $: >
                /* Temporary fix for github issue 481:
                https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card/issues/481
                */ div {
                  left: 0 !important;
                  top: 0 !important;
                  position: absolute !important;
                }
    .: |
      ha-card {
        background: none !important;
        box-shadow: none !important;
        border-radius: 0px !important;
        overflow: visible !important;
        --map-card-internal-primary-color: var(--blue) !important;
        --map-card-internal-secondary-color: var(--contrast2) !important;
        --map-card-internal-primary-text-color: var(--black) !important;
        --map-card-internal-secondary-text-color: var(--contrast20) !important;
        --map-card-internal-manual-point-line-color: var(--contrast20) !important;
        --map-card-internal-manual-point-fill-color: var(--contrast20) !important;
        --map-card-internal-manual-rectangle-description-color: var(--contrast20) !important;
        --map-card-internal-manual-rectangle-line-color: none !important;
        --map-card-internal-manual-rectangle-line-color-selected: none !important;
        --map-card-internal-manual-rectangle-fill-color: rgba(var(--blue-rgb),0.4) !important;
        --map-card-internal-manual-rectangle-fill-color-selected: rgba(var(--blue-rgb),0.3) !important;        
        
        --map-card-internal-manual-rectangle-delete-circle-fill-color: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-delete-circle-line-color: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-delete-icon-color: var(--contrast20) !important;
        --map-card-internal-manual-rectangle-delete-circle-fill-color-selected: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-delete-circle-line-color-selected: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-delete-icon-color-selected: var(--contrast20) !important;

        --map-card-internal-manual-rectangle-resize-circle-fill-color: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-resize-circle-line-color: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-resize-icon-color: var(--contrast20) !important;
        --map-card-internal-manual-rectangle-resize-circle-fill-color-selected: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-resize-circle-line-color-selected: var(--contrast7) !important;
        --map-card-internal-manual-rectangle-resize-icon-color-selected: var(--contrast20) !important;
      }
      .modes-dropdown-menu-button ha-icon {
        color: var(--black) !important;
      }
      .modes-dropdown-menu-button .modes-dropdown-menu-button-text {
        display: none;
      }
      .map-wrapper {
        border-radius: 24px !important;
        overflow: hidden;
      }
      .controls-wrapper {
        margin-right: 0 !important;
        margin-left: 0 !important;
        margin-bottom: 0 !important;
      }
      .controls-wrapper .map-controls-wrapper {
        margin: 0 !important;
      }
      mwc-list-item {
        background: var(--contrast2) !important;
      }

Vacuum zone cards

These buttons are linked to the robot vacuum. The top six buttons represent rooms and zones that can be vacuumed. By selecting one or more of these, the robot vacuum will only vacuum those areas. We can also view the last time each room/zone was vacuumed. The green button can be used to start the robot vacuum, and if no rooms/zones are selected, the robot vacuum will vacuum the entire home. The ‘number of repetitions’ button speaks for itself and will determine how many times the robot vacuum will vacuum the areas. Finally, we have the ‘Empty vacuum’ button, which can be used to manually send the robot vacuum to the trash can. However, this also happens automatically when it is full, so we rarely use this.

In case you would like to learn more about the triggered vacuum script and how it vacuums just the selected areas: 🟣 Rounded - Dashboard guide - #25 by LE0N

6 zone cards

Here we also highlight one of the six buttons. In the code I use two helpers. A toggle for selecting and deselecting the area and a date helper for keeping track of the last cleaned date.

type: custom:button-card
icon: mdi:sofa-single
entity: input_boolean.woonkamer_robotstofzuiger_selecteren_voor_stofzuigen
name: Woonkamer
label: '[[[return states["sensor.robotstofzuiger_laatst_actief_woonkamer"].state ]]]'
show_label: true
tap_action:
  action: toggle
  haptic: medium
state:
  - value: 'on'
    styles:
      card:
        - background-color: var(--yellow)
        - box-shadow: none
      icon:
        - color: var(--black)
      name:
        - color: var(--black)
      label:
        - color: var(--black)
        - opacity: '0.5'
  - value: 'off'
    styles:
      card:
        - background: var(--contrast2)
        - box-shadow: none
      icon:
        - width: 24px
        - color: var(--contrast20)
      name:
        - color: var(--contrast20)
      label:
        - color: var(--contrast9)
styles:
  icon:
    - width: 24px
  img_cell:
    - justify-content: flex-start
    - margin-top: '-4px'
  name:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - font-size: 12px
    - margin-bottom: 0px
  card:
    - height: 84px
    - border-radius: 24px
    - padding: 12px 0 12px 14px
    - box-sizing: border-box
    - '--mdc-ripple-press-opacity': 0
  label:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - font-size: 12px

Start vacuum card

This button calls a script when pressed. The script determines whether zones or the entire house should be vacuumed. Depending on whether zones are selected or not, the name and icon will change.

type: custom:button-card
entity: input_boolean.algemeen_robotstofzuiger_zones_selected
icon: mdi:robot-vacuum
name: Zones
label: Stofzuigen
show_label: true
tap_action:
  action: call-service
  service: script.selectie_stofzuigen
  haptic: success
state:
  - value: 'off'
    name: Huis
    icon: mdi:home
styles:
  icon:
    - width: 24px
    - color: var(--black)
  img_cell:
    - justify-content: flex-start
    - margin-top: '-4px'
  name:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - color: var(--black)
    - font-size: 12px
    - margin-bottom: 0px
  card:
    - height: 84px
    - background-color: var(--green)
    - box-shadow: none
    - border-radius: 24px
    - padding: 12px 0 12px 14px
    - z-index: 1
  label:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - color: var(--black)
    - font-size: 12px

Number of repetitions card

This button allow the user to loop over 1, 2, or 3 repetitions of vacuuming the selected areas. Pressing the buttons will trigger a script that determines whether the number of repetitions should be increased by 1 or reset back to 1 if it is currently set to 3.

type: custom:button-card
icon: mdi:numeric-1-box
entity: input_number.algemeen_robotstofzuiger_zone_herhalingen
name: Aantal
label: herhalingen
show_label: true
state:
  - value: '1.0'
    icon: mdi:numeric-1-box
  - value: '2.0'
    icon: mdi:numeric-2-box
  - value: '3.0'
    icon: mdi:numeric-3-box
tap_action:
  action: call-service
  service: script.robotstofzuiger_herhalingen
  haptic: medium
styles:
  icon:
    - width: 24px
    - color: var(--contrast1)
  img_cell:
    - justify-content: flex-start
    - margin-top: '-4px'
  name:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - color: var(--contrast1)
    - font-size: 12px
    - margin-bottom: 0px
  card:
    - height: 84px
    - background-color: var(--contrast20)
    - box-shadow: none
    - border-radius: 24px
    - padding: 12px 0 12px 14px
    - z-index: 1
  label:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - color: var(--contrast1)
    - font-size: 12px

Empty vacuum card

As mentioned above, this button sends the robot vacuum to the trash can. Additionally, the button also displays how many cubic meters the robot vacuum has already vacuumed. Based on this, it is determined whether the robot vacuum should automatically go to the trash can after vacuuming.

type: custom:button-card
icon: mdi:broom
name: Leeg maken
label: >-
  [[[return states["sensor.robotstofzuiger_meters_schoongemaakt"].state + "m²
  gezogen"]]]
show_label: true
tap_action:
  action: call-service
  service: script.stofzuiger_legen
  haptic: success
styles:
  icon:
    - width: 24px
    - color: var(--contrast1)
  img_cell:
    - justify-content: flex-start
    - margin-top: '-4px'
  name:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - color: var(--contrast1)
    - font-size: 12px
    - margin-bottom: 0px
  card:
    - height: 84px
    - background-color: var(--contrast20)
    - box-shadow: none
    - border-radius: 24px
    - padding: 12px 0 12px 14px
    - z-index: 1
  label:
    - font-family: In case of a custom font, otherwise you can remove this line
    - justify-self: start
    - color: var(--contrast11)
    - font-size: 12px

Vacuum maintenance cards

These four cards display the hours remaining before we need to clean specific sensors or brushes of the robot vacuum. Pressing a card triggers a pop-up that asks us to confirm whether we want to reset the hours.

type: custom:button-card
name: >-
  [[[return
  Math.round(states["sensor.roborock_vacuum_s5e_main_brush_left"].state / 60 /
  60)]]]
styles:
  grid:
    - grid-template-areas: '"n eenheid" "l l"'
    - grid-template-columns: min-content min-content
  name:
    - font-size: 32px
    - color: var(--contrast20)
  card:
    - height: 80px
    - border-radius: 24px
    - padding: 30px 0 6px 16px
    - box-sizing: border-box
    - background: var(--contrast2)
    - box-shadow: none
  label:
    - justify-self: start
    - font-size: 12px
    - color: var(--contrast20)
    - margin-top: '-2px'
  custom_fields:
    eenheid:
      - font-size: 12px
      - color: var(--contrast9)
      - margin-bottom: 6px
      - padding-left: 2px
label: Hoofdborstel
custom_fields:
  eenheid: ' uur'
show_label: true
tap_action:
  action: call-service
  haptic: medium
  service: script.robotstofzuiger_uren_resetten_hoofdborstel
confirmation:
  text: Onderhouds uren van de hoofdborstel resetten?

More information button

This one is simple, just a basic custom button-card that opens the more-info modal window of the robot vacuum. Here we can, for example, adjust the fan speed if desired, although we rarely make use of this feature. As such, it doesn’t get a particularly prominent position on the screen.

type: custom:button-card
name: Meer info
entity: vacuum.roborock_s5_max
show_icon: false
styles:
  card:
    - background: none
    - border-radius: 24px
    - color: var(--contrast20)
    - margin-top: 8px
    - padding: 18px 0px
    - font-size: 16px
    - border: 2px solid var(--contrast5)
tap_action:
  action: more-info
  haptic: medium

Television source

This card displays the active TV app, such as Netflix, YouTube, NPO, Videoland, and more. Its primary purpose is to provide a quick overview of what’s playing. I’ve chosen not to show the specific movie/series/video title, as it doesn’t interest me much. While the card isn’t highly functional, it’s visually pleasing. I built the card using a custom button-card and a template sensor that tracks which app is active on the TV. Based on this, an image with the corresponding logo is displayed. I saved the image in the ‘www’ folder in the root directory.

type: custom:button-card
entity: sensor.televisie_media
name: '[[[return states["sensor.televisie_media"].state]]]'
show_entity_picture: true
styles:
  grid:
    - grid-template-areas: '"i gutter n"'
    - grid-template-columns: min-content 24px 1fr
  card:
    - font-family: In case of a custom font, otherwise you can remove this line
    - background: var(--contrast2)
    - padding: 24px
    - '--mdc-ripple-press-opacity': 0
  icon:
    - width: 70px
    - height: 70px
    - border-radius: 16px
    - border: 5px solid var(--contrast4)
  name:
    - font-size: 16px
    - color: var(--contrast20)
    - width: 100%
    - text-align: left
state:
  - value: GoogleTV
    entity_picture: /local/googletv.png
    styles:
      card:
        - background: var(--blue-tint)
      icon:
        - border: 5px solid rgba(66,133,244,0.5)
  - value: Twitch
    entity_picture: /local/twitch.png
    styles:
      card:
        - background: rgba(169,112,255,var(--color-tint))
      icon:
        - border: 5px solid rgba(169,112,255,0.5)
  - value: Spotify
    entity_picture: /local/spotify.png
    styles:
      card:
        - background: rgba(101,211,110,var(--color-tint))
      icon:
        - border: 5px solid rgba(101,211,110,0.5)
  - value: Netflix
    entity_picture: /local/netflix.png
    styles:
      card:
        - background: rgba(229,9,20,var(--color-tint))
      icon:
        - border: 5px solid rgba(229,9,20,0.5)
  - value: YouTube
    entity_picture: /local/youtube.png
    styles:
      card:
        - background: rgba(255,0,0,var(--color-tint))
      icon:
        - border: 5px solid rgba(255,0,0,0.5)
  - value: NPO Start
    entity_picture: /local/npo.png
    styles:
      card:
        - background: rgba(255,109,0,var(--color-tint))
      icon:
        - border: 5px solid rgba(255,109,0,0.5)
  - value: Plex
    entity_picture: /local/plex.png
    styles:
      card:
        - background: rgba(229,160,13,var(--color-tint))
      icon:
        - border: 5px solid rgba(229,160,13,0.5)
  - value: Ziggo
    entity_picture: /local/ziggo.png
    styles:
      card:
        - background: rgba(244,140,0,var(--color-tint))
      icon:
        - border: 5px solid rgba(244,140,0,0.5)
  - value: Videoland
    entity_picture: /local/videoland.png
    styles:
      card:
        - background: rgba(255,55,71,var(--color-tint))
      icon:
        - border: 5px solid rgba(255,55,71,0.5)
  - value: Aan het casten
    icon: mdi:cast
    styles:
      card:
        - background: var(--blue-tint)
      icon:
        - border: 5px solid rgba(177,197,255,0.5)
        - color: var(--blue)
        - width: 24px
        - height: 24px
        - padding: 23px
  - value: Televisie staat uit
    icon: mdi:television-off
    styles:
      card:
        - background: var(--contrast2)
      icon:
        - border: 5px solid var(--contrast4)
        - color: var(--contrast9)
        - width: 24px
        - height: 24px
        - padding: 23px

Television control cards

These buttons represent the most frequently used functions of our TV remote. The layout is partially inspired by the GoogleTV remote. Each button is a custom button-card within a grid card, with its own call-service or script action. The buttons’ appearance changes depending on the TV’s status, showing whether they are active or inactive. I will show the three different types of buttons below.

Action button

The most used button, with a call-service as tap action and a simple icon color change based on the TV state.

type: custom:button-card
icon: mdi:home-variant
aspect_ratio: 1/1
entity: media_player.sony_bravia_tv
show_name: false
tap_action:
  action: call-service
  haptic: medium
  service: remote.send_command
  service_data:
    entity_id: remote.sony_bravia_tv
    command: Home
styles:
  card:
    - border-radius: 24px
    - background-color: var(--contrast2)
  icon:
    - width: 32px
    - color: var(--contrast8)
state:
  - value: 'on'
    styles:
      icon:
        - color: var(--contrast20)

Power button

Also pretty straightforward but uses a toggle function for the tap action.

type: custom:button-card
icontype: custom:button-card
icon: mdi:power
entity: media_player.sony_bravia_tv
show_name: false
aspect_ratio: 1/1
tap_action:
  action: call-service
  haptic: success
  service: media_player.toggle
  service_data:
    entity_id: media_player.sony_bravia_tv
styles:
  card:
    - border-radius: 24px
    - background: var(--green)
  icon:
    - width: 32px
    - color: var(--black)
state:
  - value: 'on'
    styles:
      card:
        - background: var(--red)

Pause button

This is a somewhat unusual case where we use both the ‘state’ rule and if statements. This is because the ‘state’ rule only works with a single specified entity. If you want to use the states of multiple entities, it’s simpler to use if statements.

Furthermore, the pause button has three states: inactive (when the TV is off OR on but nothing is playing), paused, or played. Personally, I prefer to treat the “least concrete” state as the default and apply changes based on clear state changes. So in this case, the default style is for the inactive state. And if the state is ‘on’ or ‘off’, I modify the style accordingly.

type: custom:button-card
aspect_ratio: 1/1
show_name: false
tap_action:
  action: call-service
  haptic: medium
  service: media_player.media_play_pause
  service_data:
    entity_id: media_player.sony_bravia_tv
styles:
  icon:
    - width: 32px
    - color: var(--contrast8)
  card:
    - border-radius: 24px
    - background-color: var(--contrast2)
icon: mdi:pause
entity: media_player.googletv
state:
  - value: paused
    icon: mdi:play
    styles:
      card:
        - background-color: |
            [[[
              if (states['media_player.sony_bravia_tv'].state == 'off')
               return "var(--contrast2)";
              if (states['sensor.televisie_media'].state == 'GoogleTV')
                return "var(--contrast2)";
              return "var(--green)";
            ]]]
      icon:
        - color: |
            [[[
              if (states['media_player.sony_bravia_tv'].state == 'off')
               return "var(--contrast8)";
              if (states['sensor.televisie_media'].state == 'GoogleTV')
                return "var(--contrast8)";
              return "var(--black)";
            ]]]
  - value: playing
    styles:
      card:
        - background-color: |
            [[[
              if (states['sensor.televisie_media'].state == 'GoogleTV')
                return "var(--contrast2)";
              return "var(--yellow)";
            ]]]
      icon:
        - color: |
            [[[
              if (states['sensor.televisie_media'].state == 'GoogleTV')
                return "var(--contrast8)";
              return "var(--black)";
            ]]]

Speaker artist card

Similar to the ‘Television source’ card, but more functional. Due to the use of song covers as thumbnails and a blurred background, we need to use two custom button-cards here. One as a container and the other for the content.

type: custom:button-card
entity: media_player.sonos
show_entity_picture: true
show_name: false
tap_action:
  action: more-info
styles:
  grid:
    - grid-template-areas: '"info"'
    - grid-template-columns: 1fr
    - grid-template-rows: min-content
  card:
    - background: none
    - padding: 0
    - position: relative
    - '--mdc-ripple-press-opacity': 0
  img_cell:
    - position: absolute
  icon:
    - width: 150%
    - opacity: var(--color-tint)
    - '-webkit-filter': blur(20px)
    - '-moz-filter': blur(20px)
    - '-o-filter': blur(20px)
    - '-ms-filter': blur(20px)
    - filter: blur(20px)
custom_fields:
  info:
    card:
      type: custom:button-card
      entity: media_player.sonos
      show_entity_picture: true
      name: |
        [[[
          if (states['media_player.sonos'].attributes.media_title)
            return states['media_player.sonos'].attributes.media_title;
          else
            return "Nothing is playing";
        ]]]
      label: |
        [[[
          if (states['media_player.sonos'].attributes.media_artist)
            return states['media_player.sonos'].attributes.media_artist;
          else
            return "";
        ]]]
      show_label: true
      show_icon: true
      styles:
        grid:
          - grid-template-areas: '"i gutter n" "i gutter l"'
          - grid-template-columns: min-content 24px 1fr
          - grid-template-rows: min-content
        card:
          - font-family: hk nova medium
          - background: none
          - border-radius: 0
          - background: none
          - padding: 24px
          - '--mdc-ripple-press-opacity': 0
        img_cell:
          - height: 80px
          - width: 80px
          - border-radius: 16px
        icon:
          - height: 100%
          - width: 100%
        name:
          - font-size: 16px
          - color: var(--contrast20)
          - width: 100%
          - text-align: left
          - align-self: end
        label:
          - font-size: 12px
          - color: var(--contrast20)
          - opacity: 0.5
          - width: 100%
          - text-align: left
          - align-self: start
        custom_fields:
          image:
            - '--mdc-ripple-press-opacity': 0.5

Speaker control cards

Also similar to the TV variants, only in this case a two columns grid because it worked better in terms of layout. And sometimes it’s fun to play with the sizes, where we can make the play/pause button a bit larger. Below, I will highlight the play/pause button and one of the 6 standard buttons, but this won’t be anything we haven’t discussed before.

Action button

type: custom:button-card
icon: mdi:volume-plus
name: Volume omhoog
tap_action:
  action: call-service
  haptic: medium
  service: media_player.volume_up
  service_data:
    entity_id: media_player.sonos
styles:
  icon:
    - width: 24px
    - color: var(--contrast20)
  img_cell:
    - justify-content: flex-start
    - margin-top: 0px
  name:
    - justify-self: start
    - font-size: 14px
    - margin-top: 0px
    - color: var(--contrast20)
  card:
    - height: 84px
    - border-radius: 24px
    - padding: 8px 0px 16px 20px
    - background-color: var(--contrast2)
  grid:
    - grid-template-areas: '"i" "n"'

Play / pause button

Our Sonos doesn’t have a power-off state, it either plays music or not. This means that we only give the play/pause button two states.

type: custom:button-card
icon: mdi:play
entity: media_player.sonos
name: Afspelen
tap_action:
  action: call-service
  haptic: medium
  service: script.sonos_play_pause
styles:
  icon:
    - width: 24px
    - color: var(--black)
  img_cell:
    - justify-content: flex-start
    - margin-top: 0px
  name:
    - justify-self: start
    - font-size: 14px
    - margin-top: 0px
    - color: var(--black)
  card:
    - height: 184px
    - border-radius: 24px
    - padding: 108px 0px 16px 20px
    - background-color: var(--green)
  grid:
    - grid-template-areas: '"i" "n"'
state:
  - value: playing
    name: Pauzeren
    icon: mdi:pause
    styles:
      card:
        - background-color: var(--yellow)

And to briefly touch on the layout, the controls make use of multiple nested grid cards, similar to the light cards in combination with the slider cards.

That’s it! :tada:

It goes without saying that you will need to make some adjustments to the dashboard to fit your specific scenarios, but I hope this is a good starting point. As you may have noticed, I prefer using the custom button-card. In essence, it acts as a mini container that allows for endless placement and styling possibilities. A big shout-out to RomRider for creating such a versatile card! I hope you found this guide useful and I’m excited to see the dashboards you come up with!

Complete Rounded.yaml file

:bangbang: This is including the bottom bar from the ‘Beta stuff’ section below

View here: 🟣 Rounded - Dashboard guide - #23 by LE0N

23 Likes

Beta stuff

In addition to stable cards, I sometimes experiment with creating new cards, which may still contain small errors or may not work at all. One example of this is the bottom tab bar, which I have included below for potential use, but please be aware that there may be complications.

Bottom tab bar - Updated for HA version 2023.4

Personally, I find a bottom tab bar to be much more efficient than the current navigation bar at the top of the screen. Therefore, I used card-mod to make some adjustments and fix the navigation bar at the bottom. However, I have listed this under beta stuff because it may be difficult to use in edit mode and it changes the navigation bar in all dashboards. Ideally, I would only lower the navigation bar for specific screens. Let’s start by creating two new custom variables in the light and dark modes.

For dark mode:

	# Contrast RGB variables
	contrast1-RGB: 0,0,0

For light mode:

	# Contrast RGB variables
	contrast1-RGB: 255,255,255

Here we also adhere to the rules established beforehand, contrast1 is the base color, so white in light mode and black in dark mode.

Now we need to add some card-mod rules. This code is relatively simple as we are not yet using the card-mod-root rule. So you can copy all the code and paste it at the bottom of Rounded.yaml.

  card-mod-root-yaml: |

    .: |

      /* ___________ Bottom tabbar ___________  */

      #view {
        margin-top: 0 !important;
        height: calc(100vh - 80px - env(safe-area-inset-top)) !important;
      }
      .header {
        top: auto !important;
        bottom: 0;
        background-color: rgba(var(--contrast1-RGB),0.6) !important;
      }
      :host([scrolled]) .header {
        box-shadow: none !important;
      }
      .toolbar {
        height: 80px !important;
        padding: 4px 4px 16px 4px !important;
      }
      paper-tab {
        color: var(--contrast10);
        border-radius: 16px;
      }
      paper-tab.iron-selected {
        color: var(--contrast20);
      }
      ha-menu-button, ha-button-menu {
        color: var(--contrast10);
      }

Assuming everything went smoothly, you should now find the navigation bar at the bottom of your screen. While I did my best to fix potential issues, please do let me know if you run into any problems.

20 Likes

Wow, this is amazing! Thanks for sharing!!

1 Like

that´s a good looking setup, great work you did there!

trying to copy your navigation bar into my theme right now, since it´s way better then mine.
however i´m running into some problems.

i removed the ha-menu-button and ha-button-menu using this:

card-mod-root: |
    ha-menu-button, ha-button-menu {
      display: none !important;
    }

now the spacing between the ha-tabs(at least i guess so) is kinda bad

i´m trying to get it spaced like this:

maybe someone has an idea how i could get it spaced.

EDIT:

got it, it´s

    paper-tab {
      padding: 0 20px;
    }
1 Like

Nice work! Mind sharing your whole yaml files?

Thanks for sharing @LE0N. I really like the style and have just been playing around with it a bit. Unfortunately, I find that a lot of display space is wasted above the title card. Does anyone else have this problem?

As @freibiergesicht noted, it would be cool if you could also make your original .yaml files available via a repo. That would make some things a little easier :wink:
Thank you very much for everything!

Thanks a lot for this. This is (in my opinion) one of the most beautiful layouts Ive ever seen.

1 Like

Thanks all for the kind words!

@freibiergesicht @CM000n will make sure to share the complete yaml file this weekend and possibly even put my whole setup including config on github (have to dive into that a bit deeper).

@CM000n As for the big space on top of your dashboard, I see you’ve implemented the bottom bar, but with that you also have to adjust the top and bottom spacing of the whole dashboard. Make sure this code is working correctly:

  card-mod-view-yaml: |

    hui-masonry-view:
      $: |

        /* Swipe-card full width on mobile */

        @media screen and (max-width: 599px) {
          #columns .column swipe-card {
            margin-left: -4px;
            margin-right: -4px;
          }
        }

		/*  Making sure the 'Add card' FAB can be seen in edit mode  */
        
        ha-fab {
          margin-bottom: 80px;
        }

    .: |

      /*  Correct spaces top and bottom for bottom tabbar  */

      hui-view {
        padding-bottom: 80px;
        margin-top: -80px;
      }

And specifically the last part
/* Correct spaces top and bottom for bottom tabbar */

What I sometimes do is add a background: red; statement to see if the code has any effect on the dashboard. Sometimes the indentation can be quite tricky.

2 Likes

It was indeed because of wrong indentation :smiley:
Thank you very much!

1 Like

Amazing, beautiful, clean and clear.
Well done!

Hope to see more cards from this design
Like covers media player and entities

Thank you very much for sharing :pray:

This is so awesome and I’m loving your instructions. Pretty easy to follow. But one question from a noob like me. I copied your light cards, but I have quite a few colored ones around my home. How can I change the code so that the button shows the color that the light currently is set to?

Oh and the conditional brightness is so simple, yet so genius. Always missed something like that but never knew how to do it.

What icons are you using, and how did you do it? Dont look like icons from mdi :grin:

they are fontawesome icons, using this integration from HACS

1 Like

Wow! what a great looking dashboard. Man the attention to detail really shows in your results. Is there any requirement to have HACs installed to do this ?

This will work only on screens the same size as your phone since you are using pixels. Instead you could use a relative unit, that will maintain the same spacing independently of the screen, for example: padding: 0 1.5rem.

Or even better use flexbox to let CSS automatically decide the spacing.

1 Like

good idea, will give it a try. thanks!

Hi @Loony1, great question. Unfortunately I don’t own a colored light bulb myself so I can’t really test that one. My guess would be to extract the color values of the bulb and translate those to hex / rgb color codes which can be used by the background-color property of the light card.

@Stevengerrard08 @mase Actually I’m using the default mdi icons by Home Assistant in my dashboard, some of the icons may appear different on the screenshots because I designed them originally with iOS icons but I haven’t changed those yet.

@AJHomeAsst, good question. In theory HACS isn’t required BUT it makes installing and maintaining all the custom cards a lot easier so I would definitely advice to install HACS beforehand. It’s a great add-on!

1 Like

@LE0N Thanks for sharing.

Can you post the entire Rounded.yaml file?
I could not get to work, maybe I am not familliar with card mod inside the theme file and I am struggling here :confused:

2 Likes

This is really great! Thanks for the inspiration!

Is it possible that you could post the scripts that you are using for your vacuum? Selecting multiple zones and pressing start sounds really handy and cleaver.