DEV Community

Cover image for Crafting My Perfect Home Assistant Dashboard
Bruno Sabot
Bruno Sabot

Posted on • Originally published at brunosabot.dev

Crafting My Perfect Home Assistant Dashboard

Inspired by the beautiful and functional dashboards I've seen online, I embarked on a journey to personalize my own Home Assistant experience. This post dives into the plugins I used, my customization choices, and a final glimpse of the finished product.

Introducing the Plugin Powerhouse

Several plugins played a pivotal role in building my dream dashboard, each offering unique functionalities that elevated the user experience.

I meticulously handpicked a select few from the vast plugin library, prioritizing consistency and cohesiveness around the versatile Bubble Card, to craft this personalized experience.

Bubble Card

bubble-card.png

I recently stumbled upon the Bubble Card plugin, and let me tell you, it was a game-changer!

This new plugin quickly became the star of my dashboard makeover. Its cool, rounded design elements totally revamped the whole look and feel, making everything smooth and visually appealing.

To take things to the next level, I also started using the Bubble theme, created by the same awesome creator.

Together, they work like magic, creating a consistent and stunning dashboard that's more than just the sum of its parts.

It's like my dashboard went from info central to an interactive and visually engaging masterpiece!

Card Mod

Anyone who's ever tinkered with their dashboard to make it look snazzy probably knows about Card Mod – it's kind of a no-brainer.

This awesome plugin lets you, well, mod your cards by taking styles from other cards. Need to change the background color, for example? Card Mod is your new best friend.

It helps if you know a little CSS, but even without it, you can do some pretty cool stuff and make your dashboard look way less, well, boring.

Decluttering Card

Card Mod is so cool, I went a little overboard and ended up customizing practically everything! But all those tweaks scattered throughout my config started to feel like a bit of a pain to manage.

That's when I discovered the magic of Decluttering Card. This plugin lets me create card templates that I can reuse everywhere, shrinking my configuration file down to a much more manageable size.

Now, I can make a change in one place, and it ripples through my entire dashboard – a total win!

Config Template Card

Decluttering Card definitely helps with the templating issue, but it can't handle everything, especially when things get a little more dynamic.

Thankfully, the Config Template Card swoops in to save the day! This plugin lets you use JavaScript to make pretty much any property of your card dynamic, putting the finishing touches on your template mastery.

It's like the final ingredient in the recipe for perfect dashboard customization.

Lovelace Layout Card

Now I have the power to create the perfect cards, I just needed the final touch: a stellar layout.

Thankfully, Lovelace Layout Card swoops in to save the day! This plugin allows me to craft a perfectly responsive dashboard that flawlessly adapts to both my desktop and mobile, ensuring a seamless Home Assistant experience no matter the device I'm using.

Bringing it to Life: My Customization Journey

In the following sections, I'll delve into the specifics of how I utilized these plugins to realize my desired dashboard functionalities.

I'll provide a detailed explanation for each, empowering you to replicate or adapt these approaches to craft your own personalized Home Assistant experience.

Alarm

alarm.png

Keeping tabs on my home alarm system's status is crucial. To achieve this, I needed a clear and immediate visual indicator right on my dashboard.

The ideal card would dynamically change its background color based on the alarm's state: green for disabled, orange for armed, and red for triggered. Luckily, the style customization of Bubble Card allowed me to create this functionality perfectly.

Now, with just a glance, I can see the alarm's status and take appropriate action if needed.

Here is the decluttering-card template:

default: - name: '' - columns: 2 card: type: custom:bubble-card card_type: button button_type: state entity: '[[entity]]' name: '[[name]]' show_state: true icon: mdi:alarm-light columns: '[[columns]]' card_layout: large styles: | .bubble-button-card-container { background-color: ${state === 'disarmed' ? 'var(--success-color)' : state === 'triggered' ? 'var(--error-color)' : 'var(--warning-color)'}; } .bubble-icon { color: ${state === 'disarmed' ? 'var(--success-color)' : state === 'triggered' ? 'var(--error-color)' : 'var(--warning-color)'} !important; opacity: 0.6 !important; } 
Enter fullscreen mode Exit fullscreen mode

AQI

aqi.png

Monitoring air quality is a top priority for me, as I'm sensitive to pollution.

Having this data readily available on my dashboard helps me maintain my health, not only through automations with my air purifier but also by allowing me to check the current pollution level at a glance.

I envisioned a card that would display the Air Quality Index (AQI) using a color-coded system: green for good air quality (AQI below 50), orange for moderate air quality (AQI below 100), and red for poor air quality (AQI above 100).

This way, I could easily understand the air quality and take appropriate actions, if necessary.

Here is the decluttering-card template:

default: - name: '' card: type: 'custom:bubble-card' card_type: button button_type: state entity: '[[entity]]' name: '[[name]]' icon: mdi:leaf show_state: true columns: 2 card_layout: large styles: | .bubble-button-card-container { background-color: ${state < 50 ? 'var(--success-color)' : state < 100 ? 'var(--warning-color)' : 'var(--error-color)'}; } .bubble-icon { color: ${state < 50 ? 'var(--success-color)' : state < 100 ? 'var(--warning-color)' : 'var(--error-color)'} !important; opacity: 0.6 !important; } 
Enter fullscreen mode Exit fullscreen mode

Covers

cover.png

My cover cards demanded a presentation that was both information-rich and visually clear. To achieve this, I implemented a three-pronged approach:

  • Condensed Icon Spacing & Favorite Position Integration: I tightened the spacing between the cover icons, fostering a sense of cohesion for the linked actions they represent. Additionally, I incorporated a sub-button displaying the favorite position (set at 15% in my case) directly within the card itself.
  • Seamless Opening Percentage Display: The current opening percentage is crucial information, and I eliminated the need for a separate entity card by integrating it directly into the cover card.
  • Color-Coded Status Recognition: Finally, I implemented a color-coding system to provide instant status recognition. Blue signifies a fully open cover, while purple highlights the preferred 15% position. This visual language allows for quick comprehension and informed decision-making when interacting with my covers.

This combined approach effectively streamlined the cover card format, enhancing both information accessibility and user experience.

Here is the decluttering-card template:

default: - name: '' card: type: 'custom:bubble-card' card_type: cover entity: '[[entity]]' name: '[[name]]' icon_open: mdi:window-shutter-open icon_close: mdi:window-shutter show_state: true columns: 2 card_layout: large sub_button: - name: My icon: mdi:star show_background: false tap_action: action: call-service service: button.press target: entity_id: - '[[my]]' card_mod: style: | .bubble-cover-card-container { background-color: {% if state_attr(config.entity, 'current_position') == 15 %} var(--state-cover-active-color) {% elif state_attr(config.entity, 'current_position') > 0 %} var(--accent-color) {% else %} var(--background-color-2, var(--secondary-background-color)) {% endif %} !important; } .large .bubble-buttons { gap: 8px; } .bubble-button { background: transparent; width: 40px; height: 40px; } .bubble-icon { justify-content: center; align-items: center; } .large .bubble-sub-button-container { margin-right: 8px; } .bubble-sub-button { background: transparent; width: 40px; height: 40px; } .bubble-state::after { content: " - {{ state_attr(config.entity, 'current_position') }}%"; margin-left: 4px; } .bubble-icon-container { border-color: transparent; background-color: transparent; } .bubble-icon-container::after { content: ""; background-color: {% if state_attr(config.entity, 'current_position') == 15 %} var(--state-cover-active-color) {% elif state_attr(config.entity, 'current_position') > 0 %} var(--accent-color) {% else %} var(--background-color-2, var(--secondary-background-color)) {% endif %} !important; height: 25px; width: 25px; position: absolute; left: 50px; top: 25px; -webkit-mask-image: radial-gradient(circle at top right, transparent 0, transparent 25px, black 25.5px); } 
Enter fullscreen mode Exit fullscreen mode

Separator

separator.png

To keep my dashboard organized, I grouped things into sections.

Bubble Card's separator mode was perfect for adding section titles, but I wanted to give them a little makeover.

So, I played around with some CSS to make the font lighter and bigger, and tweaked the line color so it stands out even when the cards aren't in a popup.

Now the section titles are easy to read and add a nice touch to the whole thing!

Here is the decluttering-card template:

default: - icon: '' - name: '' card: type: custom:bubble-card card_type: separator name: '[[name]]' icon: '[[icon]]' card_layout: large styles: | .bubble-name { font-weight: 100; font-size: 20px; } .bubble-line { background-color: var(--background-color-2, var(--secondary-background-color)); opacity: 0.2; } 
Enter fullscreen mode Exit fullscreen mode

Thermostat

thermostat.png

Craving more from my smart thermostat display, I used custom-template-card to dynamically change the card's icon based on heating state.

Color cues joined the party: blue for off, green for achieved comfort, and orange for heating to reach temperature. I even incorporated the target temperature in this customized switch card.

Now, it's a one-stop shop for efficient and comfortable climate control!

default: - name: '' - offset: 1 card: type: custom:config-template-card variables: MODE: "states['[[entity]]'].state" entities: - "[[entity]]" card: type: custom:bubble-card card_type: button button_type: switch entity: '[[entity]]' name: '[[name]]' icon: "${MODE === 'heat' ? 'mdi:thermometer': 'mdi:thermometer-off'}" show_state: true columns: 2 card_layout: large card_mod: style: | .bubble-button-card-container { background-color: {% if is_state(config.entity, 'heat') %}{% if state_attr(config.entity, 'temperature') > state_attr(config.entity, 'current_temperature') %}var(--warning-color){% else %}var(--success-color){% endif %}{% else %}var(--info-color){% endif %} !important; } .bubble-icon { color: {% if is_state(config.entity, 'heat') %}{% if state_attr(config.entity, 'temperature') > state_attr(config.entity, 'current_temperature') %}var(--warning-color){% else %}var(--success-color){% endif %}{% else %}var(--info-color){% endif %} !important; opacity: 0.6 !important; } .bubble-state::after { content: " - {{ state_attr(config.entity, 'temperature') }} °C"; margin-left: 4px; } 
Enter fullscreen mode Exit fullscreen mode

Weather

weather.png

While Bubble Card forms the foundation, the true magic lies in the customization of my weather display. It leverages the power of sub-buttons to showcase a range of essential information: current conditions, daily highs and lows, wind speed, and the anticipated rain forecast.

But the key is the intuitive color-coding system I implemented:

  • Temperatures utilize a blue-to-red gradient, providing a quick glimpse of the expected highs and lows.
  • Wind speed follows a similar approach, transitioning from neutral to red as intensity increases.
  • Rain takes center stage with a prominent blue highlight whenever there's a chance of precipitation.

This visual language allows me to grasp the daily forecast at a glance, without getting bogged down in numbers. It's a testament to the power of visual communication and user experience design in action!

default: - name: '' card: type: custom:bubble-card card_type: button button_type: state entity: '[[entity]]' name: '[[name]]' show_state: true scrolling_effect: false card_layout: large-2-rows sub_button: - name: Min icon: mdi:thermometer-low entity: '[[entity]]' attribute: forecast[0].templow show_background: false show_attribute: true - name: Wind icon: mdi:weather-windy entity: '[[entity]]' attribute: wind_speed show_background: false show_attribute: true show_icon: true - name: Max icon: mdi:thermometer-high entity: '[[entity]]' attribute: forecast[0].temperature show_background: false show_attribute: true - name: Rain icon: mdi:weather-pouring entity: '[[entity]]' show_background: false show_attribute: true attribute: forecast[0].precipitation show_icon: true card_mod: style: | .bubble-icon-container { background: transparent; } .bubble-name { border-radius: 20px; background-color: {% if state_attr(config.entity, 'temperature') < 10 %} var(--info-color) {% elif state_attr(config.entity, 'temperature') > 40 %} var(--error-color) {% elif state_attr(config.entity, 'temperature') > 30 %} var(--warning-color) {% endif %} ; margin-left: -8px; padding: 0 8px; } .bubble-name::after { content: " - {{ state_attr(config.sub_button[0].entity, 'temperature') }}°C"; margin-left: 4px; } .bubble-sub-button-1 { background-color: {% if state_attr(config.sub_button[0].entity, 'forecast')[0].templow < 10 %} var(--info-color) {% elif state_attr(config.sub_button[0].entity, 'forecast')[0].templow > 40 %} var(--error-color) {% elif state_attr(config.sub_button[0].entity, 'forecast')[0].templow > 30 %} var(--warning-color) {% endif %} ; } .bubble-sub-button-1::before { content: "°C"; } .bubble-sub-button-2 { background-color: {% if state_attr(config.sub_button[0].entity, 'wind_speed') > 20 %} var(--warning-color) {% elif state_attr(config.sub_button[0].entity, 'wind_speed') > 50 %} var(--error-color) {% endif %} ; } .bubble-sub-button-2::before { content: "km/h"; margin-left: 2px; } .bubble-sub-button-3 { background-color: {% if state_attr(config.sub_button[2].entity, 'forecast')[0].temperature < 10 %} var(--info-color) {% elif state_attr(config.sub_button[2].entity, 'forecast')[0].temperature > 40 %} var(--error-color) {% elif state_attr(config.sub_button[2].entity, 'forecast')[0].temperature > 30 %} var(--warning-color) {% endif %} ; } .bubble-sub-button-3::before { content: "°C"; } .bubble-sub-button-4 { background-color: {% if state_attr(config.sub_button[3].entity, 'forecast')[0].precipitation > 0 %} var(--info-color) {% endif %} ; } .bubble-sub-button-4::before { content:" mm"; margin-left: 2px; } 
Enter fullscreen mode Exit fullscreen mode

Weather forecast

weather-forecast.png

While the current weather is important, I also crave a sneak peek at the next week's forecast.

To achieve this, I leveraged the same color-coded system from my weather card for a visually cohesive experience. Additionally, I cleverly formatted the current day using its attributes, while also extracting the high and low temperatures from the same source.

This way, I get a clear picture of the upcoming week's weather trends right on my dashboard.

default: - name: '' - offset: 1 card: type: custom:config-template-card variables: FORECAST: "states['[[entity]]'].attributes.forecast[[[offset]]].condition" entities: - "[[entity]]" card: type: custom:bubble-card card_type: button button_type: state entity: '[[entity]]' name: Forecast icon: "${FORECAST === 'partlycloudy' ? 'mdi:weather-partly-cloudy': 'mdi:weather-'+FORECAST}" columns: 2 card_layout: large show_state: true card_mod: style: .: | .bubble-name { color: transparent; white-space: nowrap; } .bubble-name:before { color: var(--primary-text-color); content: "{{ strptime(state_attr(config.entity, 'forecast')[[[offset]]].datetime, '%Y-%m-%dT%H:%M:%S%z').strftime("%A") }}"; } .bubble-state { color: transparent; white-space: nowrap; } .bubble-state:before { color: var(--primary-text-color); content: "{{ state_attr(config.entity, 'forecast')[[[offset]]].templow }} / {{ state_attr(config.entity, 'forecast')[[[offset]]].temperature }} {{ state_attr(config.entity, 'temperature_unit') }}"; } .bubble-button-card { background-color: {% if state_attr(config.entity, 'forecast')[[[offset]]].temperature < 10 %} var(--info-color) {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature > 40 %} var(--error-color) {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature > 30 %} var(--warning-color) {% endif %} !important; } .bubble-icon { color:  {% if state_attr(config.entity, 'forecast')[[[offset]]].temperature < 10 %} var(--info-color) {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature > 40 %} var(--error-color) {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature > 30 %} var(--warning-color) {% endif %} !important; opacity: 0.6 !important; } 
Enter fullscreen mode Exit fullscreen mode

Is that it?

While I've highlighted some key customizations, there are additional tweaks throughout my dashboard that refine the presentation further. These refinements build upon the techniques described here. If you're curious to delve deeper, feel free to reach out – I'm always happy to chat about Home Assistant!

For a complete picture, here's a quick reference of the custom cards I've utilized:

  • Alarm Status: bubble_alarm (presented earlier)
  • Air Quality Index: bubble_aqi (presented earlier)
  • Cover Cards: bubble_cover (presented earlier)
  • Section Separators: bubble_separator (presented earlier)
  • Temperature Display: bubble_temperature
  • Thermostat Control: bubble_thermostat (presented earlier)
  • Trash Collection: bubble_trash
  • Vacuum Status: bubble_vacuum
  • Weather Display: bubble_weather (presented earlier)
  • Météo-France Alerts: bubble_weather_alert
  • Future Forecast: bubble_weather_forcecast
  • Website Status: bubble_website

Top comments (0)