šŸ”¹ Layout-card - Take control of where your cards end up

Fist and for all, many thanks for bringing and everybody supporting custom:grid-layout.

I have arrived at the point, where I want to tweak the position of the items withing a grid area. As suggested in the documentation, I started reading up on CSS Grid. Here, I noticed the justify-self and align-self properties. These let the each item decide ā€œfor themselvesā€ where it should be placed withing the grid area.

justify-self Aligns a grid item inside a cell along the inline (row) axis. This value applies to the content inside a single grid item.

align-self Aligns a grid item inside a cell along the block (column) axis. This value applies to the content inside a single grid item.

I’ve made many attempts at CSS selectors using mod_card, but just can’t figure how to specify properties for the custom:grid-layout items / children / areas (or whatever the correct terminology is).

I reduced my example to the basic lovelace page shown below.

views:
  - type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      height: calc(100vh - 56px) # assuming a header
      place-items: center center
      grid-template-columns: 1fr
      grid-template-rows: repeat(3, 1fr)
      grid-template-areas: |
        "a"
        "b"
        "footer"
    cards:
      - type: markdown
        content: "area a"
        view_layout:
          grid-area: a
      - type: markdown
        content: "area b"
        view_layout:
          grid-area: b
      - type: markdown
        content: "area footer"
        view_layout:
          grid-area: footer
        card_mod:
          style: |
            ha-card {
              background: red;
            }

Here, I want to e.g. place the footer at the vertical and horizontal end of its grid area.

In comparison, button-card has a styles field that let you specify CSS properties for its grid items, as in:

views:
  - type: custom:button-card
    name: "area n"
    state_display: "area s"
    label: "area l"
    show_label: true
    show_state: true
    styles:
      card:
        - border: none
        - padding: 0
      grid:
        - padding: 0
        - height: calc(100vh - 56px)
        - grid-template-columns: 1fr
        - grid-template-rows: repeat(3, 1fr)
        - grid-template-areas: |
            "n"
            "s"
            "l"
      name:
        - justify-self: start # horizontal
        - align-self: start # vertical
      state:
        - justify-self: center
        - align-self: center
      label:
        - justify-self: end
        - align-self: end
        - background: red

Is a similar approach possible with custom:grid-layout? Is there a CSS selector that I just missed?

Thanks for your time and consideration,
/coert

Nearly there. Apply the styling to :host which applies to the markdown card itself, which is the host of the ha-card where card-mod is applied.

type: custom:layout-card
layout_type: custom:grid-layout
layout:
  height: calc(100vh - 56px)
  place-items: center center
  grid-template-columns: 1fr
  grid-template-rows: repeat(3, 1fr)
  grid-template-areas: |
    "a"
    "b"
    "footer"
cards:
  - type: markdown
    content: area a
    view_layout:
      grid-area: a
  - type: markdown
    content: area b
    view_layout:
      grid-area: b
  - type: markdown
    content: area footer
    view_layout:
      grid-area: footer
    card_mod:
      style: |
        :host {
          justify-self: flex-end;
          align-self: end;
        }
        ha-card {
          background: red;
        }

Brilliant, can’t believe I missed that. (I get a feeling that sometimes the network caching causes me miss things.)

Brings me to a follow-up, that is my real-life scenario:

How would it work when footer itself was a custom:grid-layout, as in

views:
  - type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      height: calc(100vh - 56px) # assuming a header
      place-items: center center
      grid-template-columns: 1fr
      grid-template-rows: repeat(3, 1fr)
      grid-template-areas: |
        "a"
        "b"
        "footer"
    cards:
      - type: markdown
        content: "area a"
        view_layout:
          grid-area: a

      - type: markdown
        content: "area b"
        view_layout:
          grid-area: b

      - type: custom:layout-card
        layout_type: custom:grid-layout
        view_layout:
          grid-area: footer
        layout_options:
          place-items: center stretch
          place-content: end end
          padding: 0
          margin: 0vw
          grid-gap: 2vw
          grid-template-columns: repeat(2, 1fr)
          grid-template-rows: min-content
        card_mod:
          style: |
            :host {
              justify-self: flex-end;
              align-self: end;
              background: red;
            }
        cards:
          - type: markdown
            content: "footer1"
          - type: markdown
            content: "footer2"

Here, there is no hui-card or ha-card in the DOM structure.

layout-card                  ⇐ the top level layout-card
 ↳ $ 
   ↳ grid-layout
     ↳ $
        ↳ #root              ⇐ this appears to be the top of the CSS grid
           ↳  layout-card    ⇐ apply justify-self here?
             ↳ $ 
               ↳ grid-layout 
                  ↳ $
                    ↳  #root"

That means the :host selector doesn’t work. I also tried the place-content.

thanks
/Coert

Easiest here is to use custom:mod-card

type: custom:layout-card
layout_type: custom:grid-layout
layout:
  height: calc(100vh - 56px)
  place-items: center center
  grid-template-columns: 1fr
  grid-template-rows: repeat(3, 1fr)
  grid-template-areas: |
    "a"
    "b"
    "footer"
cards:
  - type: markdown
    content: area a
    view_layout:
      grid-area: a
  - type: markdown
    content: area b
    view_layout:
      grid-area: b
  - type: custom:mod-card
    card:
      type: custom:layout-card
      layout_type: custom:grid-layout
      view_layout:
        grid-area: footer
      layout_options:
        place-items: center stretch
        place-content: end end
        padding: 0
        margin: 0vw
        grid-gap: 2vw
        grid-template-columns: repeat(2, 1fr)
        grid-template-rows: min-content
      cards:
        - type: markdown
          content: footer1
        - type: markdown
          content: footer2
    card_mod:
      style: |
        :host {
          justify-self: flex-end;
          align-self: end;
          background: red;
        }

You could also theme card-mod-view and then use yaml selectors to traverse each DOM tree via shadowRoot selectors, but this gets tricky and is hard to maintain.

Best would be for custom:layout-card to apply card-mod to itself. Perhaps in future but I am not sure that would happen too quickly.

A final thought is that in a Sections dashboard, card_mod v4 can now support sections grids and stack cards. As they all use flex layout you could probably get what you need with a sections dashboard and card_mod. I might see what I can replicate.

I had a play and posted an example in card_mod thread.

1 Like

Thanks for your help.
This makes a great difference to me.

/Coert

1 Like

Hey! So I’m trying to create a dashboard similar to the default Map dash, but with cards at the top to quickly see where a given person is. The issues I have is I can’t get the map card to dynamically resize itself based on the browser/device viewing the page, I’ve tried a bunch of stuff that didn’t work, and I’m rather inexperienced when it comes to CSS. Ideally the person cards would take up like 10-15% of the top of the page (or possibly bigger depending on if text is too small), and the map would fill the rest of the screen, without overflowing the screen so there are no scrollbars and 100% of the needed portion of the map fits on screen. This is the full yaml I’ve been working on

type: custom:grid-layout
layout:
  height: 100%
  grid-template-columns: 1fr 1fr 1fr
  grid-template-rows: auto
  grid-template-areas: |
    "h1 h2 h3"
    "m1 m1 m1"
cards:
  - type: custom:stack-in-card
    view_layout:
      grid-area: h2
    cards:
      - type: custom:button-card
        tap_action:
          action: more-info
        hold_action:
          action: none
        entity: person.person
        color: var(--state-icon-color)
        show_label: true
        name: Person
        size: 300%
        aspect_ratio: null
        styles:
          name:
            - font-size: 100%
          label:
            - font-size: 100%
            - opacity: 50%
          grid:
            - grid-template-areas: "\"n i\" \"l i\""
            - grid-template-columns: 75% 15%
        state:
          - value: unavailable
            label: Unknown
            icon: mdi:help
          - value: unknown
            label: Unknown
            icon: mdi:help
          - value: not_home
            label: Away
            icon: mdi:map-marker-radius
          - value: home
            label: Home
            icon: mdi:home
      - type: custom:button-card
        entity: sensor.person_geocode
        color: var(--state-icon-color)
        show_state: true
        show_icon: false
        show_name: false
        aspect_ratio: null
        styles:
          state:
            - overflow: visible
            - text-wrap: wrap
  - type: custom:stack-in-card
    view_layout:
      grid-area: h3
    cards:
      - type: custom:button-card
        tap_action:
          action: more-info
        hold_action:
          action: none
        entity: person.person
        color: var(--state-icon-color)
        show_label: true
        name: Person
        size: 300%
        aspect_ratio: null
        styles:
          name:
            - font-size: 100%
          label:
            - font-size: 100%
            - opacity: 50%
          grid:
            - grid-template-areas: "\"n i\" \"l i\""
            - grid-template-columns: 75% 15%
        state:
          - value: unavailable
            label: Unknown
            icon: mdi:help
          - value: unknown
            label: Unknown
            icon: mdi:help
          - value: not_home
            label: Away
            icon: mdi:map-marker-radius
          - value: home
            label: Home
            icon: mdi:home
      - type: custom:button-card
        entity: sensor.person_geocode
        color: var(--state-icon-color)
        show_state: true
        show_icon: false
        show_name: false
        aspect_ratio: null
        styles:
          state:
            - overflow: visible
            - text-wrap: wrap
  - type: custom:stack-in-card
    view_layout:
      grid-area: h1
    cards:
      - type: custom:button-card
        tap_action:
          action: more-info
        hold_action:
          action: none
        entity: person.person
        color: var(--state-icon-color)
        show_label: true
        name: Person
        size: 300%
        aspect_ratio: null
        styles:
          name:
            - font-size: 100%
          label:
            - font-size: 100%
            - opacity: 50%
          grid:
            - grid-template-areas: "\"n i\" \"l i\""
            - grid-template-columns: 75% 15%
        state:
          - value: unavailable
            label: Unknown
            icon: mdi:help
          - value: unknown
            label: Unknown
            icon: mdi:help
          - value: not_home
            label: Away
            icon: mdi:map-marker-radius
          - value: home
            label: Home
            icon: mdi:home
      - type: custom:button-card
        entity: sensor.person_geocode
        color: var(--state-icon-color)
        show_state: true
        show_icon: false
        show_name: false
        aspect_ratio: null
        styles:
          state:
            - overflow: visible
            - text-wrap: wrap
  - type: custom:auto-entities
    view_layout:
      grid-area: m1
    style:
      layout-card$: |
        .grid > * {
          margin: 0px !important;
        }
    filter:
      include:
        - options: {}
          domain: person
        - options: {}
          domain: zone
      exclude:
        - options: {}
          state: unknown
        - options: {}
          state: unavailable
    card:
      type: map
      height: 100%
      auto_fit: true
      theme_mode: auto
      hours_to_show: 24
icon: mdi:help
title: help
path: help

Could someone help me figure out why I can’t set a width for my browser mod/layout card popups? It keeps clipping the right column and it’s driving me crazy.

  • –popup-max-width: min(820px, 96vw) is set via browser_mod’s style property to control dialog width
  • custom:layout-card with custom:grid-layout creates a two-column layout inside (1fr 1.3fr)
  • The map column clips/overflows horizontally despite the fractional columns

Questions:

  1. Does --popup-max-width still work in browser_mod v2.8.2 to set the dialog width, or has it been renamed/changed?
  2. When custom:layout-card with custom:grid-layout is used inside a browser_mod popup (not a panel view), does it correctly constrain to the popup width? The docs warn it’s ā€œof limited use except in panel modeā€.

I am using the latest version of HA, browser-mod and layout-card.

  action: fire-dom-event
  browser_mod:
    service: browser_mod.popup
    data:
      title: Car
      style: |
        --popup-max-width: min(820px, 96vw);
        --primary-color: transparent;
      card_mod:
        style:
          layout-card$grid-layout$: |
            hui-vertical-stack-card {
              border-right: 1.5px solid rgba(0, 0, 0, 0.2);
              border-radius: 0;
              transition: none;
            }
      content:
        type: custom:layout-card
        layout_type: custom:grid-layout
        layout:
          margin: 0
          grid-template-columns: 1fr 1.3fr
          grid-template-rows: 1fr
          grid-template-areas: |
            "info map"
          mediaquery:
            "(max-width: 800px)":
              grid-template-columns: 1fr
              grid-template-rows: repeat(2, 1fr)
              grid-template-areas: |
                "info"
                "map"
        cards:
          - type: vertical-stack
            view_layout:
              grid-area: info
            cards: [...]
          - type: map
            view_layout:
              grid-area: map
            card_mod:
              style:
                .: |
                  ha-card {
                    height: 500px;
                  }
                ha-map$: |
                  #map {
                    height: 500px !important;
                  }

You should switch to the new mod. šŸ’” UI eXtension - Add CSS styles to (almost) any part of the Home Assistant UI - #123 by VietNgoc

1 Like

From which device/screen is the screenshot taken/do the problem occurs?

I’m not sure I understand the question, this is from my Home Assistant Green dashboard that I am building (based on matt8707/hass-config), running in my Chrome browser. Should I be using UI eXtension instead?

My question was related to the device and browser. I have similar problems (only) on iPad companion app currently. Without checking the deep details and if it is the same probkem, I only wanted to check this and give the advice to test if it is really a code (change) problem or a browser one.

From what I see just one change required. Use --popup-width on ha-dialog (Browser Mod provides for default --popup-width: var(--popup-min-width, 580px)

  action: fire-dom-event
  browser_mod:
    service: browser_mod.popup
    data:
      title: Car
      style: |
        ha-dialog {
          --popup-width: min(820px, 96vw);
          --primary-color: transparent;
        }

I’m not sure if someone here can help or not…

I’m using the layout-card in a vertical layout mode (not sure if that matters sine I also tried with horizontal layout and have the same results).

Everything works as expected in my layout until I add the built-in HA thermostat card.

Hare is the code for a working view (I’ve shortened some of the code for brevity but it won’t change the result):

title: HVAC
type: custom:layout-card
layout_type: custom:vertical-layout
layout:
  max_cols: 4
cards:

###  COLUMN 1 HEATER  ##############################################################

  # - type: thermostat
    # entity: climate.main_heat_thermostat  
    # features:
      # - type: "climate-hvac-modes"
        # hvac_modes:
          # #- auto
          # #- heat_cool
          # - heat
          # #- cool
          # #- dry
          # #- fan_only
          # - "off" 
  
  - type: custom:simple-thermostat
    entity: climate.main_heat_thermostat
    
  - type: history-graph
    title: Main Heat Thermostat Temp
    entities:
      - sensor.main_heat_thermostat_air_temperature
    hours_to_show: 24
    refresh: 600

  - type: history-graph
    title: Main Heat Status
    entities:
      - binary_sensor.main_heat_active
      - sensor.main_ac_fan_state
    hours_to_show: 24
    refresh: 600

  - type: history-graph
    title: Main Heat Cycle Time
    entities:
      - sensor.main_heat_run_cycle_duration
    hours_to_show: 24
    refresh: 600

  - type: history-graph
    title: Main Heat Run Time Percentage Last Hour
    entities:
      - sensor.main_heat_run_time_percentage_last_hour
    hours_to_show: 24
    refresh: 600
   
  - type: history-graph
    title: Main Heat Run Time Percentage Yesterday
    entities:
      - sensor.main_heat_run_time_percentage_yesterday
    hours_to_show: 24
    refresh: 600

  - type: custom:layout-break

### COLUMN 2 TEMPS ETC  ###################################################################

  
  - type: entities
    title: Main AC Filter
    show_header_toggle: false
    state_color: true
    entities:
      - binary_sensor.main_ac_filter_needs_changed
      - entity: sensor.hvac_fan_current_running_total_hrs
        type: "custom:secondaryinfo-entity-row"
        secondary_info: "Needs Changed at 300 hours"
      - type: 'custom:restriction-card'
        card:
          type: custom:binary-control-button-row
          entity: input_boolean.hvac_ac_filter_changed
          customTheme: true
          reverseButtons: true
          state_color: true
        row: true
  
  - type: custom:stack-in-card
    cards:
      - type: history-graph
        title: Indoor Temperatures
        entities:
          - sensor.livingroom_display_sensor_temperature
          - sensor.sunroom_aqara_temperature
        hours_to_show: 24
        refresh: 600

      - type: history-graph
        title: Outdoor Temperature
        entities:
          - sensor.auburn_kgwb_temperature_template
          - sensor.openweathermap_temperature
        hours_to_show: 24
        refresh: 600
        
      - type: history-graph
        title: Indoor Humidities
        entities:
          - sensor.big_room_humidity
          - sensor.livingroom_humidity
        hours_to_show: 24
        refresh: 600
        
  - type: history-graph
    title: Basement Temperature
    entities:
      - sensor.main_basement_temperature
      - sensor.dragon_room_temperature
    hours_to_show: 24
    refresh: 600

  - type: history-graph
    title: Basement Humidity
    entities:
      - sensor.main_basement_humidity
      - sensor.dragon_room_humidity
    hours_to_show: 24
    refresh: 600
  
  - type: history-graph
    title: Dehumidifier Status
    entities:
      - switch.humidity_control_plug
    hours_to_show: 24
    refresh: 600
        
  - type: custom:layout-break
  
###  COLUMN 3 AC  ###########################################################################
  
  - type: custom:simple-thermostat
    entity: climate.main_ac_thermostat
  
  - type: history-graph
    title: Main AC Thermostat Temp
    entities:
      - sensor.main_ac_thermostat_air_temperature
    hours_to_show: 24
    refresh: 600

  - type: history-graph
    title: Main AC Status
    entities:
      - binary_sensor.main_ac_cooling_active
      - binary_sensor.main_ac_compressor_running
    hours_to_show: 24
    refresh: 600

  - type: history-graph
    title: Main AC Duct Temps
    entities:
      # - sensor.smoker_sensor_test_ntc
      - sensor.main_ac_return_temperature
      - sensor.main_ac_coil_temperature
    hours_to_show: 24
    refresh: 600

Here is the result with the above code:

Here all I’ve changed is added the built-in thermostat card (uncommented the first part in the code above) I’ve had to put in two screenshots scrolled down because the built-in thermostat card takes up as much vertical space as the 7 cards to the right of it:

Does anybody have any idea how to prevent the thermostat card from stretching vertically?

You can put inside i.e a grid-layout-card, and set " height: Xpx; "
Or card-mod (ha-card { height }
Edit: or add direct on thermostat-card

 card_mod:
    style: |
      :host {
        height: 300px !important;
      }
1 Like

Thanks for that. it worked at 450px. I never thought that I should have to use card-mod for that

I still don’t know why it stretches like that in the first place tho.

1 Like

Because developers have long stopped using any other layout than the grid sections type :joy:

2 Likes

Yeah but unfortunately I can’t (easily…) use yaml for sections style views.

Yes I’m still a caveman. :laughing:

Beside the Fact that they constantly have ā€œfiddledā€ with the Dashboard Default/Layout Settings , since they started that Journey ā€œUnder Constructionā€ Warning Sign missing !, One can never tell what they will come up with / or Changed in coming releases.

1 Like

Hi there, hours that I am trying to get this work and no way … I think can be simple for you.
I have defined a grid (see code below) to fit on wall tablet (1340 x 715 px) … I do not want to scroll down, so limited to 715 px height.

I am displaying an history card on the left side … issue is If I make it take the full 715 px height, it is still for me not width enough, so I would like to keep the max height of 715 px, but ā€œstretchā€ the card so that it takes more space on the width.

Increase the height ? Not possible because then the width is indeed increasing proportionally, but the height will be more than 715 px which I do not want.

See the code below and the screenshot

See n here I have set the first column to 420 pixels, which is fine, because the height is just where it should be … but I would like to increase the width so something like 700 pixels instead of 420, to have the clock card starting at column 700 pixels.
But If I do that, the height of the history card will exceed the 715 pixels.
I would like to kee the same 715 pixels height, but increase (STRETCH) the card to the right …

kiosk_mode:
  admin_settings:
    hide_header: false
    hide_sidebar: false
  user_settings:
    - users:
        - alessio
        - flavia
        - inĆØs
      hide_sidebar: false
    - users:
        - wallpanel
      kiosk: true
views:
  - title: Home
    type: custom:grid-layout
    layout:
      grid-template-columns: 420px 500px 420px
      grid-template-rows: 65px 65px 65px 65px 65px 65px 65px 65px 65px 65px 65px
      grid-template-areas: |
        "r1-1 r1-1 r1-1"
        "r2-1 r2-2 r2-3"
        "r2-1 r2-2 r3-3"
        "r2-1 r2-2 r4-3"
        "r2-1 r2-2 r5-3"
        "r2-1 r2-2 r6-3"
        "r2-1 r2-2 r7-3"
        "r2-1 r2-2 r8-3"
        "r2-1 r2-2 r9-3"
        "r2-1 r2-2 r10-3"
        "r2-1 r2-2 r11-3"
        "r2-1 r2-2 r12-3"
    icon: mdi:home
    visible:
      - user: 6c0f22e61daf4f799222ab1f2942f2af
      - user: 1a22f3b4ca2f4c4c84db909d9ba20c60
    badges: []
    cards:
      - type: horizontal-stack
        cards:
          - type: custom:mushroom-chips-card
            chips:
              - type: entity
                entity: person.delecole
                tap_action:
                  action: navigate
                  navigation_path: /lenovo-m10-plus-home/0
                name: Home
                content_info: name
                icon: mdi:home
                use_entity_picture: false
                icon_color: blue
        view_layout:
          grid-area: r1-1
      - type: history-graph
        entities:
          - entity: sensor.s_battery
          - entity: lawn_mower.s
          - entity: sensor.s_rssi
          - entity: sensor.s_error
          - entity: binary_sensor.s_online
          - entity: switch.s_party_mode
        hours_to_show: 36
        card_mod:
          style: |
            ha-card {
              background: transparent;
              border: 0px;
            }
        view_layout:
          grid-area: r2-1



      - type: custom:clock-weather-card
        entity: weather.maison_2
        tap_action:
          action: navigate
          navigation_path: /lenovo-m10-plus-meteo/0
        card_mod:
          style: |
            ha-card {
              color: black;
              background: transparent;
              border: 0px;
            }
        view_layout:
          grid-area: r2-3