Adaptive Mushroom

Adaptive Mushroom: one dashboard for mobile/tablet and light/dark mode

A minimalistic Mushroom-based dashboard for Home Assistant. No more need to maintain multiple dashboards! With this setup, you can create a single minimalistic-looking dashboard for both mobile/desktop and light/dark mode.

Buy me a HA cloud

I wanted to be able to have a nice, modern mobile layout like in 7ahang’s design, while also making use of the full screen on tablet/desktop. Having both in one dashboard meant I could do quick edits on cards without having to copy them to another dashboard. After much trial and error, nitpicking and YAML syntax errors, I ended up with my current dashboard that you see below.

Screenshots and features

  • The amazing layout-card is used to make the layout adaptive for mobile/tablet/desktop.
  • The mobile layout features a bottom navigation bar inspired by Android material design; the tablet/desktop layout has an easily accessible menu on the left of the three columns.
  • Custom theme variables are used for light/dark mode support.
  • I mainly use Mushroom cards, but I also made several custom cards using for example button-card.

Installation

This is my personal setup uploaded as is, there’s no quick installation button. But you can start your own Adaptive Mushroom dashboard fairly easily.
To help you get started with the main layout framework and theme, I’ve made a step-by-step installation guide.

Questions, comments, suggestions

If you have any questions or comments, feel free to reply to this topic. If you use Adaptive Mushroom for your setup, please share your dashboard here! I’m always looking for new inspiration and ideas. Suggestions or enhancements are also appreciated, either here or on the Github.

24 Likes

My wall dashboard is based on your inspiration. I am changing a few things and adding stuff here and there. I will post it once I’ve reached a good version 1.0 of my dash xD

Thank you for sharing!

2 Likes

Nice to hear, curious to see the results!

@ArenaCloser lets continue here. For some reason the sticky footer doesn’t work with your code. I chanced it as followed which works. Please note I had the dash on a custom dashboard url prior. So both on lovelace as custom board it didn’t work.

      - type: custom:stack-in-card
        mode: horizontal
        cards:
          - type: custom:decluttering-card
            template: bottom-nav
            variables:
              - dashboard-name: lovelace #update accordingly to your dashboard
              - active-view: home
        style: |
          :host {
            position: sticky !important;
            bottom: 0px;
            margin-bottom: 0px !important;
            z-index: 4;
          }
          ha-card { 
            background: rgb(var(--cstm-rgb-bottom-nav));
            box-shadow: none;
            padding-bottom: 15px;
            margin: 0px -8px -15px;
            border-radius: 0px;
          }
        view_layout:
          grid-area: footer
          show:
            mediaquery: '(max-width: 400px)'

I don’t understand why it looks like this :frowning:

Strange to hear that it works if you leave out the card_mod line and instead directly use style: | (looks like the only difference to my code if I’m not mistaken). FYI for me it works both ways, so not sure what the culprit was.

It looks like you have a very wide screen. It causes the layout-cards below e.g. ‘Favoriti’ and ‘Illuminazione’ to use three-column grids instead of two columns. I haven’t figured out a way yet to make those layout-cards work properly on very wide screens. You can change them to a regular type: grid to fix that issue until I find a fix.

It was too big a screen problem. Now I can’t configure the weather. Where do you get buienalarm???

Buienalarm is Dutch, from a custom integration in HACS: GitHub - gieljnssns/buienalarm-sensor-homeassistant: Buienalarm custom_component for Home-Assistant

I solved it using my weather station and I’m trying to convert the code.

What is this part of code for?

{% set str = as_timestamp(state_attr('weather.ifiugg2_2', 'forecast')[[[index]]].datetime) | timestamp_custom("%a") %}

Alright, so onwards to today’s edit. Got this working fully on openweathermap incl. adjusting the time towards noon.

20/11: updated
Changed the rain sensor and added better icon depending on the possibility of MM snow fall.

@ArenaCloser please note you use two differnt statments for classifying rain. The amount of ml is not matching for weather_now / weather_day.

  weather_now_template:
    card:
      type: custom:mushroom-template-card
      primary: 'Now: {{states(''sensor.openweathermap_weather'')}}'
      secondary: '[[secondary]]'
      icon: mdi:weather-partly-cloudy
      entity: sensor.openweathermap_condition
      picture: >-
        {% set entity = states("sensor.openweathermap_condition") %} {% set
        cloud = states("sensor.openweathermap_cloud_coverage")|int %}  {% set
        rain =
        states("sensor.openweathermap_forecast_precipitation")|float
        %} {% if entity == "exceptional" %}
          {% set slug = "isolated_scattered_tstorms_day" %}
        {% elif entity == "sunny" %}
          {% set slug = "sunny" %}
        {% elif entity == "windy" %}
          {% set slug = "windy" %}
        {% elif entity == "fog" %}
          {% set slug = "haze_fog_dust_smoke" %}
        {% elif entity == "hail" %}
          {% set slug = "wintry_mix_rain_snow" %}
        {% elif entity == "lightning_rainy" or entity == "lightning" %}
          {% set slug = "strong_tstorms" %}
        {% elif entity == "snowy" %}
          {% if rain <= 1 %}
            {% set slug = "flurries" %}
          {% elif rain <= 2.5 %}
            {% set slug = "snow_showers" %}
          {% else %}
            {% set slug = "blizzard" %}
          {% endif %}
        {% elif entity == "snowy_rainy" %}
          {% set slug = "wintry_mix_rain_snow" %}
        {% elif entity == "partly_cloudy" %}
          {% set slug = "partly_cloudy" %}
        {% elif entity == "pouring" %}
          {% set slug = "heavy_rain" %}
        {% elif rain > 0.5 %}
          {% if rain <= 1.5 %}
            {% set slug = "drizzle" %}
          {% elif rain <= 5 %}
            {% set slug = "showers_rain" %}
          {% else %}
            {% set slug = "heavy_rain" %}
          {% endif %}
        {% elif states("sun.sun") == "below_horizon" %} 
          {% if cloud < 20 %}
          {% set slug = "clear_night" %}
          {% elif cloud < 40 %}
          {% set slug = "mostly_clear_night" %}
          {% elif cloud < 60 %}
          {% set slug = "partly_cloudy_night" %}
          {% elif cloud < 80 %}
          {% set slug = "mostly_cloudy_night" %}
          {% elif cloud <= 100 %}
          {% set slug = "cloudy" %}
          {% endif %}
        {% else %}  
          {% if cloud < 20 %}
          {% set slug = "sunny" %}
          {% elif cloud < 40 %}
          {% set slug = "mostly_sunny" %}
          {% elif cloud < 60 %}
          {% set slug = "partly_cloudy" %}
          {% elif cloud < 80 %}
          {% set slug = "mostly_cloudy_day" %}
          {% elif cloud <= 100 %}
          {% set slug = "cloudy" %}
          {% endif %}
        {% endif %}
        https://www.gstatic.com/images/icons/material/apps/weather/2x/{{slug}}_dark_color_96dp.png
      multiline_secondary: false
      icon_color: grey
      tap_action:
        action: navigate
        navigation_path: /[[dashboard-name]]/nieuws
      badge_icon: ''
      badge_color: ''
      card_mod:
        style: |
          ha-card {
            background: none;
            box-shadow: none;
             --primary-text-color: rgb(240,240,240);
             --secondary-text-color: rgb(220,220,220);
          }
  weather_day_template:
    card:
      type: vertical-stack
      cards:
        - type: custom:mushroom-template-card
          layout: vertical
          card_mod:
            style: |
              ha-card {
                box-shadow: none;
                background: none;
                margin-bottom: -18px;
              }
          primary: ''
          secondary: >-
            {{ as_timestamp(state_attr('weather.openweathermap',
            'forecast')[(([[index]] | int) - (((now().hour - 13 + 24) % 24) // 3) + 40) % 40].datetime) | timestamp_custom('%a %d') }}
        - type: custom:mushroom-template-card
          layout: vertical
          entity: weather.openweathermap
          tap_action:
            action: more-info
          icon: mdi:weather-partly-cloudy
          multiline_secondary: true
          primary: >-
            {{ state_attr('weather.openweathermap', 'forecast')[(([[index]] | int) - (((now().hour - 13 + 24) % 24) // 3) + 40) % 40].temperature }} °C
          secondary: >-
            {{ state_attr('weather.openweathermap', 'forecast')[(([[index]] | int) - (((now().hour - 13 + 24) % 24) // 3) + 40) % 40].wind_speed }} km/h
          card_mod:
            style: |
              ha-card {
                box-shadow: none;
                background: none;
              }
          picture: >-
            {% set cloud =
            state_attr('weather.openweathermap','forecast')[(([[index]] | int) - (((now().hour - 13 + 24) % 24) // 3) + 40) % 40].cloud_coverage | int %} {% set
            rain = state_attr('weather.openweathermap','forecast')[(([[index]] | int) - (((now().hour - 13 + 24) % 24) // 3) + 40) % 40].precipitation | float %}
            {% set entity =
            state_attr('weather.openweathermap','forecast')[(([[index]] | int) - (((now().hour - 13 + 24) % 24) // 3) + 40) % 40].condition %} {% if entity ==
            "exceptional" %}
              {% set slug = "isolated_scattered_tstorms_day" %}
            {% elif entity == "sunny" %}
              {% set slug = "sunny" %}
            {% elif entity == "windy" %}
              {% set slug = "windy" %}
            {% elif entity == "fog" %}
              {% set slug = "haze_fog_dust_smoke" %}
            {% elif entity == "hail" %}
              {% set slug = "wintry_mix_rain_snow" %}
            {% elif entity == "lightning_rainy" or entity == "lightning" %}
              {% set slug = "strong_tstorms" %}
            {% elif entity == "snowy" %}
              {% if rain <= 1 %}
                {% set slug = "flurries" %}
              {% elif rain <= 2.5 %}
                {% set slug = "snow_showers" %}
              {% else %}
                {% set slug = "blizzard" %}
              {% endif %}
            {% elif entity == "snowy_rainy" %}
              {% set slug = "wintry_mix_rain_snow" %}
            {% elif entity == "partly_cloudy" %}
              {% set slug = "partly_cloudy" %}
            {% elif entity == "pouring" %}
              {% set slug = "heavy_rain" %}
            {% elif rain > 0.5 %}
              {% if rain <= 1.5 %}
                {% set slug = "drizzle" %}
              {% elif rain <= 5 %}
                {% set slug = "showers_rain" %}
              {% else %}
                {% set slug = "heavy_rain" %}
              {% endif %}
            {% elif cloud < 20 %}
              {% set slug = "sunny" %}
            {% elif cloud < 40 %}
              {% set slug = "mostly_sunny" %}
            {% elif cloud < 60 %}
              {% set slug = "partly_cloudy" %}
            {% elif cloud < 80 %}
              {% set slug = "mostly_cloudy_day" %}
            {% elif cloud <= 100 %}
              {% set slug = "cloudy" %}
            {% endif %}
            https://www.gstatic.com/images/icons/material/apps/weather/2x/{{ slug
            }}_light_color_96dp.png

Card:

          - type: custom:mushroom-title-card
            title: Weather
          - type: custom:stack-in-card
            cards:
              - type: custom:decluttering-card
                template: weather_now_template
                variables:
                  - secondary: >-
                      {{states('sensor.openweathermap_temperature') | round(1)}}
                      °C
            card_mod:
              style: |
                ha-card {
                  height: 13em !important;
                  background: linear-gradient(to top, transparent, rgba(var(--frog-rgb-{{states('sensor.weather_frog_status')}})) 10%), url({{states('sensor.weather_frog_image_wide')}});
                  background-size: cover, 200% auto;
                  background-position: bottom left {% if is_state("sensor.weather_frog_location", "field") %}20%{% elif is_state("sensor.weather_frog_location", "hills") %}40%{% else %}10%{% endif %};
                  background-repeat: no-repeat;
                  background-blend-mode: saturation;
                }

Adjust the card:

          - type: custom:stack-in-card
            mode: horizontal
            cards:
              - type: custom:decluttering-card
                template: weather_day_template
                variables:
                  - index: 7
              - type: custom:decluttering-card
                template: weather_day_template
                variables:
                  - index: 15
              - type: custom:decluttering-card
                template: weather_day_template
                variables:
                  - index: 23
              - type: custom:decluttering-card
                template: weather_day_template
                variables:
                  - index: 31
              - type: custom:decluttering-card
                template: weather_day_template
                variables:
                  - index: 39

Adjust the configuration.yaml of @ArenaCloser and update one line:

          {% set rain = states("sensor.buienalarm_precipitation") | float %} 

to:

          {% set rain = states("sensor.openweathermap_forecast_precipitation") | float %} 
3 Likes

I see what you guys mean now. Indeed for the 5-day forecast card I use the Buienradar integration, because it provides a daily forecast for the next 5 days. If you have another integration, the code has to be adjusted for that a little.

I believe you can use OpenWeatherMap in daily mode, which would be an international alternative option for this card. I use hourly mode, so my sensors may be slightly different (I only get up to 48 hours of forecasts). But here’s some code to get started for OWM:

Decluttering template
  weather_owm_template:
    card:
      type: vertical-stack
      cards:
        - type: custom:mushroom-template-card
          layout: vertical
          entity: weather.openweathermap
          primary: ''
          secondary: >
            {% set dict = {'Mon': 'ma', 'Tue': 'di', 'Wed': 'wo', 'Thu': 'do',
            'Fri': 'vr', 'Sat': 'za', 'Sun': 'zo'} %}

            {% set str = as_timestamp(state_attr(entity,
            'forecast')[[[index]]].datetime) | timestamp_custom("%a") %}

            {{ dict[str] }}
          card_mod:
            style: |
              ha-card {
                box-shadow: none;
                background: none;
                margin-bottom: -18px;
              }
        - type: custom:mushroom-template-card
          layout: vertical
          entity: weather.openweathermap
          tap_action:
            action: more-info
          icon: mdi:weather-partly-cloudy
          picture: >-
            {% set condition = state_attr(entity,
            "forecast")[[[index]]].condition %}

            {% set cloud = state_attr(entity,
            "forecast")[[[index]]].cloud_coverage | int %}

            {% set rain = state_attr(entity,
            "forecast")[[[index]]].precipitation | float %}

            {% if condition == "exceptional" %}
              {% set slug = "isolated_scattered_tstorms_day" %}
            {% elif condition == "fog" %}
              {% set slug = "haze_fog_dust_smoke" %}
            {% elif condition == "hail" %}
              {% set slug = "wintry_mix_rain_snow" %}
            {% elif condition == "lightning" or condition == "lightning-rainy"%}
              {% set slug = "strong_tstorms" %} 
            {% elif condition == "snowy" %}
              {% set slug = "flurries" %}
            {% elif condition == "snowy-rainy" %}
              {% set slug = "wintry_mix_rain_snow" %}
            {% elif rain > 0 and rain <= 0.5 %}
              {% set slug = "drizzle" %}
            {% elif rain > 0.5 and rain <= 1.5 %}
              {% set slug = "showers_rain" %}
            {% elif rain > 1.5 %}
              {% set slug = "heavy_rain" %}
            {% elif states("sun.sun") == "below_horizon" %} 
              {% if cloud < 20 %}
              {% set slug = "clear_night" %}
              {% elif cloud < 40 %}
              {% set slug = "mostly_clear_night" %}
              {% elif cloud < 60 %}
              {% set slug = "partly_cloudy_night" %}
              {% elif cloud < 80 %}
              {% set slug = "mostly_cloudy_night" %}
              {% elif cloud <= 100 %}
              {% set slug = "cloudy" %}
              {% endif %}
            {% else %}  
              {% if cloud < 20 %}
              {% set slug = "sunny" %}
              {% elif cloud < 40 %}
              {% set slug = "mostly_sunny" %}
              {% elif cloud < 60 %}
              {% set slug = "partly_cloudy" %}
              {% elif cloud < 80 %}
              {% set slug = "mostly_cloudy_day" %}
              {% elif cloud <= 100 %}
              {% set slug = "cloudy" %}
              {% endif %}
            {% endif %}
            http://www.gstatic.com/images/icons/material/apps/weather/2x/{{slug}}_light_color_96dp.png
          primary: '{{ state_attr(entity, "forecast")[[[index]]].temperature }},'
          secondary: >-
            {{ as_timestamp(state_attr(entity, "forecast")[[[index]]].datetime)
            | timestamp_custom("%H:%M") }}
          multiline_secondary: true
          card_mod:
            style: |
              ha-card {
                box-shadow: none;
                background: none;
              }
Card
type: custom:stack-in-card
mode: horizontal
cards:
  - type: custom:decluttering-card
    template: weather_owm_template
    variables:
      - index: 0
  - type: custom:decluttering-card
    template: weather_owm_template
    variables:
      - index: 24
  - type: custom:decluttering-card
    template: weather_owm_template
    variables:
      - index: 47

Results in this:
Example

@mysob note that you were using decluttering template incorrectly; in order to pass on a variable it must be in between two square brackets, like [[index]]. And since the forecast attribute provides an array of forecasts, we need to use square brackets again, so it becomes state_attr(entity, 'forecast')[[[index]]].condition.

It is used to get the name of the day for each forecast section, using the datetime value of the forecast attribute of the weather sensor.

Thanks, I updated my code and works now. Only a matter optimising the code so it takes the temp/data is from mid day 3PM e.g… Alternatively this also gives the opportunity set / display the day low temp during night times.

Question about the media card. I cannot set the artwork as background. This is what it looks like right now. Is it possible to set the icons of the radio station as artwork?

afbeelding

1 Like

@mysob you did some great work there, code and cards look great!

I had been wondering how to calculate the noon hour, but you managed to work it out :clap:

One question: what are your OWM settings? Mine is set to onecall_hourly so I only get the next 48 hours. But it looks like you have data up to 5 days in the future.

If it’s alright with you, I will update the Github with your new code for better international support (or you can do a pull request if you prefer).

1 Like

I use this code in my Chromecast to set the card background:

  type: custom:mushroom-media-player-card
  ...
  card_mod:
    style: |
      ha-card {
        background-image: url( '{{ state_attr('media_player.chromecast_4k', "entity_picture") }}' );
        background-position: center;
        background-repeat: no-repeat;
        background-size: cover;
        position: relative;
        background-blend-mode: overlay;
        background-color: rgba(var(--rgb-card-background-color), 0.7);
      }

You’ll have to see if your media_player has an attribute that returns the current radio station.

  • If yes, you can use if-statements in the background-image: url(...) part to show the image of the current radio station.
  • If no, I’m not sure how you would achieve what you want, except maybe with an automation or input_select.
1 Like

I use hourly and I have 7 days of forecasts in Italy. With @mysob’s card I solved everything

1 Like

@ArenaCloser I’ve updated the code. It should now correctly present the 5 day ahead from 13h. Also changed the http in https, for correctly displaying the icons when using HA cloud.

Feel free to add it. I use the official OWM integration, nothing had been adjusted there. The standard plan includes the 5 day ahead:

1 Like

@Mattia2399 please note I did update the code. :slight_smile:

1 Like

Hi guys, I tried to simplify the person chip cards a bit. Not needing the two grid layouts depending home / not home. Two issues to resolve:

  1. Right now the float is being set inside the child instead of the chips > entity > card-mod
    This creates a spacing issue, couldn’t solve it so far. Anyone?

  2. check entity instead of peron.a / b inside chips > entity > card-mod
    Didn’t seem to work with the original either. Perhaps we need to use style instead here?

Resolving both would eliminate almost half of the original code and would ease the plug-&-play use of this board.

      - square: false
        columns: 1
        type: grid
        cards:
          - type: horizontal-stack
            cards:
              - type: custom:mushroom-chips-card
                chips:
                  - type: entity
                    entity: person.a
                    content_info: nam
                    use_entity_picture: true
                    card_mod:
                      style: |
                        ha-card {
                          {% if is_state('person.a', 'home') %}
                            --chip-background: rgba(var(--rgb-state-person-home), 0.4);
                          {% elif is_state('person.a', 'not_home') %}
                            --chip-background: rgba(var(--rgb-state-person-not-home), 0.4);
                          {% elif is_state('person.a', 'unknown') %}
                            --chip-background: rgba(var(--rgb-state-person-unknown), 0.4);
                          {% else %}
                            --chip-background: rgba(var(--rgb-state-person-zone), 0.4);
                          {% endif %} 
                          margin: 5px 8px;
                        }
                  - type: entity
                    entity: person.b
                    content_info: nam
                    use_entity_picture: true
                    card_mod:
                      style: |
                        ha-card {
                          {% if is_state('person.b', 'home') %}
                            --chip-background: rgba(var(--rgb-state-person-home), 0.4);
                          {% elif is_state('person.b', 'not_home') %}
                            --chip-background: rgba(var(--rgb-state-person-not-home), 0.4);
                          {% elif is_state('person.b', 'unknown') %}
                            --chip-background: rgba(var(--rgb-state-person-unknown), 0.4);
                          {% else %}
                            --chip-background: rgba(var(--rgb-state-person-zone), 0.4);
                          {% endif %} 
                          margin: 5px 8px;
                        }
                alignment: start
                card_mod:
                  style: |
                    ha-card { 
                      margin-right: -8px;
                      overflow: hidden; /* Clear float */
                    }
                    .chip-container {
                      display: block !important;
                      position: relative;
                    }
                    .type-custom-mushroom-chips-card .chip-container > mushroom-entity-chip:first-child {
                    {% if is_state('person.a', 'home') %}
                      float: left;
                    {% elif is_state('person.a', 'not_home') or is_state('person.a', 'unknown') %}
                      float: right;
                    {% else %}
                      float: right;
                    {% endif %}  
                    }
                    .type-custom-mushroom-chips-card .chip-container > mushroom-entity-chip:nth-child(2) {
                    {% if is_state('person.b', 'home') %}
                      float: left;
                    {% elif is_state('person.b', 'not_home') or is_state('person.b', 'unknown') %}
                      float: right;
                    {% else %}
                      float: right;
                    {% endif %}  
                    }
        view_layout:
          grid-area: people
1 Like

@mysob Later I will share the card for the people I use on the mobile-dashboard.

I was also wondering, since OpenWeatherNetwork provides hour-by-hour forecasts, why not create a clickable popup for each day so I can see all the forecasts for the day? Or the forecast every 3 hours?