Material You Theme and Utilities- A Fully Featured Implementation of Material Design 3 Expressive for Home Assistant

The width of the card is grid_options

type: custom:big-slider-card
name: Keukenlicht aanrecht
color: sandybrown
entity: light.hue_lightstrip
show_percentage: true
height: 88
bold_text: true
grid_options:
  columns: 6
1 Like

thanks a lot!

Hi. Right now, regardless of the entity’s state, the text on the card is always black. Shouldn’t the text change color when the entity is enabled? I did it via card_mod (see screenshot), but maybe this should be provided by the theme

This is unrelated to the theme, it’s how big slider card styles its text.

Hi everyone!

I’m excited to share with you a custom dashboard component I’ve been building that brings the look & feel of the Google Home app directly into your Home Assistant frontend!

With this component, you’ll get:

  • :jigsaw: Swipeable Google-style cards for Lights, Cameras, Climate, and Wi-Fi devices
  • :art: Material You-inspired design, with full light/dark theme support
  • :iphone: A clean, modern layout that feels like using the actual Google Home app
  • :repeat: Auto-counting and state detection of your devices
  • :hammer_and_wrench: Easy installation via CDN or manual file

:link: Full documentation and example usage here:

:point_right: Documentation

:point_right: Repository

1 Like

would this be related… setting badges_wrap to a view badge (with box-shadows) causes horizontal line//border of the section below · Issue #25691 · home-assistant/frontend · GitHub

I’ve logged in Frontend, but it was closed because I use card-mod…

Even though the issue lies in Core frontend imho. there’s an immediate response to shut issues down with a single mention of custom

would be great if (for the time being this being recognized as core bug) we could set a card-mod on the badges to not have that sharp cut-off. Or on the section itself.

Ive found this to make it less visible

  card-mod-view-yaml: |

    :first-child $:
      hui-view-header:
        $:
          hui-view-badges $: |
            .badges {
              padding-bottom: 16px;
            }

but, it also creates a big gap (of course…) so it is not nice either

if I do this:

  card-mod-view-yaml: |

    :first-child $:
      hui-view-header:
        $:
          hui-view-badges $: |
            .badges {
              mask-image: unset !important;
              overflow: visible !important;
            }

as you say, it does not work,

setting the same in Inspector does make it as one would hope:

(sorry for this off-topic in Material you theme. You are the first however to find this exact problem I noticed, and find a fix. Hope you can help me sort it out in my card-mod theme)

wait, its the wrong path:

  card-mod-view-yaml: |

    :first-child $:
      hui-view-header:
        $: |
          .badges {
            mask-image: unset !important;
            overflow: visible !important;
            }

seems to be the correct, but has unwanted effect of not scrolling but overflowing … :wink:

Welcome to the forum. It’s great that you are offering a custom component to the community.

However, I feel it’s a little disrespectful to the developer of this theme to post your announcement here.

Far better to start your own thread in the ‘Share your Projects’ thread, where I’m sure it will get a lot of attention. You’ll also be able to offer support for those who use it.

1 Like

Thank you so much for your feedback and you’re absolutely right.

I sincerely apologize for posting in the wrong place; it wasn’t my intention to be disrespectful to the original theme’s developer or the community.

I’ll move my announcement to the ‘Share your Projects’ section where it properly belongs, and I really appreciate you pointing that out.

Thanks again for the warm welcome and your guidance! :blush:

1 Like

That’s exactly it. It’s a shame that they’ve marked it as not planned since it is an issue with the design of scrolling badges not accounting for box shadows in custom themes. I’ll look into adding some style fixes within the companion module and sharing it with you if they do not fix it within the HA frontend.

1 Like

It would also be nice if you mentioned beecho01 and I in your gitbook and didn’t call it a theme since this is the theme, while your project is a custom card suite that implements the material design system variables provided by this theme and its companion module. You also used the wrong repository name here. You should also work on making this HACS compatible as HACS isn’t listed as an installation method in your installation guides.

Thank you very much for your message and you’re absolutely right on all points.

This project is still in beta, and I’m actively working on refining the code, improving the documentation, and clarifying the naming and attribution. My intention was never to misrepresent the amazing work done by you and @beecho01, and I sincerely apologize for not properly crediting you in the current version.

I’ll make sure to:

  • Correctly reference your theme and its companion module as the foundation.
  • Rename the project where appropriate (not calling it a theme, but a custom card suite that integrates your Material Design system).
  • Fix the repository name and GitBook references.
  • Add proper installation instructions including HACS compatibility as a priority.

Thanks again for pointing these things out and for all the work you’ve already done.
I truly appreciate it. :pray:

3 Likes

I applied the theme, and my switches look like squares. The navigation bar look awful, and profile picture as well.

I found clearing my browser cache and fully restarting the browser resolved this for me.

1 Like

Check your themes folder to make sure that you do not have the old Material Rounded theme installed. If so delete it.

1 Like

That’s the case. Thank you!

1 Like

I’ve had a go at creating the climate card found in the Google Home App, if its of interest to anyone.

type: custom:button-card
entity: climate.pro_breeze_5000_btu_smart_pac
show_icon: false
show_name: false
show_state: false
styles:
  card:
    - "--mdc-ripple-color": transparent
    - "-webkit-tap-highlight-color": transparent
    - border-radius: 30px
    - background-color: "#414246"
    - padding: 10px 15px
    - box-shadow: none
    - color: "#c3c3c3"
    - height: "[[[ return window.innerWidth < 420 ? '193px' : '205px' ]]]"
  grid:
    - grid-template-areas: |
        "header header header header header header header header"
        "down down temp temp temp temp up up"
        "status status status status status status status status"
    - grid-template-rows: auto auto auto
    - grid-template-columns: repeat(8, 1fr)
  custom_fields:
    header:
      - grid-area: header
      - align-self: stretch
      - justify-self: stretch
      - width: 100%
      - height: 100%
      - background: none
      - padding-bottom: 20px
    temp:
      - display: flex
      - align-self: center
      - justify-self: center
      - font-size: "[[[ return window.innerWidth < 420 ? '64px': '72px' ]]]"
      - font-weight: "450"
      - margin-top: "-15px"
    up:
      - justify-self: center
      - display: flex
      - align-items: center
      - justify-self: end
      - margin-top: "-15px"
    down:
      - justify-self: center
      - display: flex
      - align-items: center
      - justify-self: start
      - margin-top: "-15px"
    status:
      - justify-self: center
      - align-self: start
      - font-size: 15px
      - padding-top: 6px
      - padding-bottom: 12px
state:
  - value: unavailable
    styles:
      card:
        - background-color: "#2c2c2e"
        - color: "#717173"
      custom_fields:
        status:
          - display: none
        temp:
          - color: "#717173"
          - font-weight: "400"
  - value: cool
    styles:
      card:
        - background-color: "#5a3e34"
        - color: "#fedcca"
      custom_fields:
        temp:
          - color: "#fedcca"
        status:
          - color: "#E6C0B2"
  - value: heat
    styles:
      card:
        - background-color: "#5a3e34"
        - color: "#fedcca"
      custom_fields:
        temp:
          - color: "#fedcca"
        status:
          - color: "#E6C0B2"
  - value: "off"
    styles:
      card:
        - background-color: "#2c2c2e"
        - color: "#e3e3e5"
      custom_fields:
        temp:
          - color: "#e3e3e5"
          - font-weight: "400"
custom_fields:
  header:
    card:
      type: custom:button-card
      entity: climate.pro_breeze_5000_btu_smart_pac
      show_icon: false
      show_name: false
      show_state: false
      tap_action:
        action: more-info
        haptic: medium
      custom_fields:
        arrow: >
          [[[ return `<ha-icon icon="mdi:chevron-right" style="width: 20px;
          height: 20px;"></ha-icon>`; ]]]
        friendly_name: "[[[ return entity.attributes.friendly_name ]]]"
        symbol: |
          [[[ let icon = "mdi:help-circle-outline";
               let color = "var(--primary-text-color)";
               switch (entity.state) {
                 case "cool": icon = "mdi:snowflake"; color = "#fedcca"; break;
                 case "heat": icon = "mdi:fire"; break; color = "#fedcca"; break;
                 case "fan_only": icon = "mdi:fan"; break;
                 case "dry": icon = "mdi:water-percent"; break;
                 case "off": icon = "m3rf:power-settings-new"; break;
                 case "auto": icon = "mdi:autorenew"; break;
               }
               return `<ha-icon icon="${icon}" style="width: 20px; height: 20px; color: ${color};"></ha-icon>`; ]]]
      styles:
        grid:
          - grid-template-areas: |
              "symbol friendly_name arrow"
          - grid-template-columns: auto 1fr auto
        card:
          - display: flex
          - flex-direction: column
          - flex-grow: "1"
          - justify-content: center
          - align-content: center
          - background: none
          - padding: "0"
          - color: var(--primary-text-color)
          - height: 28px
        custom_fields:
          symbol:
            - grid-area: symbol
            - align-self: center
            - justify-self: start
            - height: 28px
            - margin-top: 4px
          friendly_name:
            - grid-area: friendly_name
            - font-size: 16px
            - font-weight: "500"
            - align-self: center
            - justify-self: start
            - text-align: center
            - padding-left: 12px
            - line-height: 28px
            - height: 28px
            - color: |
                [[[
                  let color = "var(--primary-text-color)"
                  switch (entity.state) {
                    case "cool": color = "#fedcca"; break;
                    case "heat": color = "#fedcca"; break;
                    case "fan_only": color = "var(--primary-text-color)"; break;
                    case "dry": color = "var(--primary-text-color)"; break;
                    case "off": color = "var(--primary-text-color)"; break;
                    case "auto": color = "var(--primary-text-color)"; break;
                  }
                  return `${color}`
                ]]]
          arrow:
            - margin-top: 4px
            - grid-area: arrow
            - justify-self: end
            - align-self: center
            - height: 28px
            - color: |
                [[[
                  let color = "white"
                  switch (entity.state) {
                    case "cool": color = "#fedcca"; break;
                    case "heat": color = "#fedcca"; break;
                    case "fan_only": color = "var(--primary-text-color)"; break;
                    case "dry": color = "var(--primary-text-color)"; break;
                    case "off": color = "var(--primary-text-color)"; break;
                    case "auto": color = "var(--primary-text-color)"; break;
                  }
                  return `${color}`
                ]]]
  temp: |
    [[[ if (entity.state === 'off') {
          return "Off";
        } else if (entity.state === 'fan' || entity.state === 'fan_only') {
          return "Fan";
        } else if (entity.state === 'unavailable' || entity.state === 'unknown') {
          return "Unavailable";
        } else {
          return Math.round(entity.attributes.temperature); 
        } ]]]
  status: |
    [[[ if (entity.state === 'unavailable' || entity.state === 'unknown') {
          return "";
        } else if (entity.state === 'off' || entity.state === 'fan' || entity.state === 'fan_only') {
          return `Indoor ${Math.round(entity.attributes.current_temperature)}`;
        } else {
          let action = (entity.state || 'Idle');
          return `${action.charAt(0).toUpperCase() + action.slice(1)} · ${Math.round(entity.attributes.current_temperature)}°`;
        } ]]]
  down:
    card:
      type: custom:button-card
      name: −
      entity: climate.pro_breeze_5000_btu_smart_pac
      show_name: true
      show_icon: false
      styles:
        card:
          - font-size: "[[[return window.innerWidth < 420 ? '28px' : '32px']]]"
          - background: "#4b332b"
          - border-radius: 36px
          - width: "[[[return window.innerWidth < 420 ? '65px' : '80px']]]"
          - height: "[[[return window.innerWidth < 420 ? '45px' : '55px']]]"
          - display: flex
          - align-items: center
          - justify-content: center
          - transition: background 0.2s ease
        name:
          - line-height: 32px
          - font-size: 32px
          - justify-self: center
          - align-self: center
          - text-align: center
          - font-family: Arial
          - font-weight: "300"
      state:
        - value: "off"
          styles:
            card:
              - display: none
        - value: unavailable
          styles:
            card:
              - display: none
        - value: fan_only
          styles:
            card:
              - display: none
        - value: dry
          styles:
            card:
              - display: none
        - value: heat
          styles:
            card:
              - display: none
        - value: cool
          styles:
            name:
              - color: "#fedcca"
      tap_action:
        action: call-service
        service: climate.set_temperature
        service_data:
          entity_id: climate.pro_breeze_5000_btu_smart_pac
          temperature: "[[[ return entity.attributes.temperature - 1 ]]]"
  up:
    card:
      type: custom:button-card
      name: +
      entity: climate.pro_breeze_5000_btu_smart_pac
      show_name: true
      show_icon: false
      styles:
        card:
          - font-size: "[[[return window.innerWidth < 420 ? '28px' : '32px']]]"
          - background: "#4b332b"
          - border-radius: 36px
          - width: "[[[return window.innerWidth < 420 ? '65px' : '80px']]]"
          - height: "[[[return window.innerWidth < 420 ? '45px' : '55px']]]"
          - display: flex
          - align-items: center
          - justify-content: center
          - transition: background 0.2s ease
        name:
          - line-height: 32px
          - font-size: 32px
          - justify-self: center
          - align-self: center
          - text-align: center
          - font-family: Arial
      state:
        - value: "off"
          styles:
            card:
              - display: none
        - value: unavailable
          styles:
            card:
              - display: none
        - value: fan_only
          styles:
            card:
              - display: none
        - value: dry
          styles:
            card:
              - display: none
        - value: heat
          styles:
            card:
              - display: none
        - value: cool
          styles:
            name:
              - color: "#fedcca"
      tap_action:
        action: call-service
        service: climate.set_temperature
        service_data:
          entity_id: climate.pro_breeze_5000_btu_smart_pac
          temperature: "[[[ return entity.attributes.temperature + 1 ]]]"
tap_action:
  action: none
hold_action:
  action: none

Cooling

Fan

Off

@Nerwyn I assume its okay to add this to the thread as it uses some theme variables directly.

2 Likes

Thanks for sharing your card — I’m familiar with it! :blush:

This project wouldn’t exist without the foundational work and inspiration from:

  • @Nerwyn – Creator of the Material You theme for Home Assistant
  • @beecho01 – Creator of the Material Symbol Icons for Home Assistant

If you’re interested in exploring the full set of Google-style components, you can find them here:
:link: Introducing the Google Components

Material Expressive

Material You Theme and Utilities have been updated to follow the new Material Expressive specifications! Using this theme and module Home Assistant can be 99% compliant with the latest Material Design 3 specification.

You’ll have access to even more custom color themes using the new 2025 color specifications and alternate platform modes. You can also derive source colors from images, change the default card type, and enable dynamic color harmonization for semantic colors to better match your custom theme.

3 Likes

Great work! :tada: The new updates look amazing and the flexibility with Material Expressive is really impressive.
If possible, it would be great to have an option to display the page name at the top like before, and also the possibility to use custom icons when clicking and selecting a tab in the navigation bar.

You can already use custom icon packs (like in the screenshots), that’s a Home Assistant feature. You can’t use separate icons for selected/unselected though, that would have to be implemented in Home Assistant.

I don’t know if I’ll be restoring the header title or adding an option for it any time soon. The header styles were changed to turn into floating buttons and in order to restore the header I’d have to modify those styles as well. Have you tried using Home Assistant’s built in page title option? I removed the header title in favor of that.