🔹 Layout-card - Take control of where your cards end up

With this I get them to stay within the screen, but not the offset on the rows.
Now every button is aligned, but I want some of them to occupy more rows in the grid.

play with the card settings

        styles:
          card:
            - margin-top: -40px
            - padding: 8px
            - height: 80%
            - border-radius: 36px 36px 6px 36px
1 Like

Thank you for the help!
That did the trick for now :slight_smile:

I have a horizontal layout card where each column is rather “min width” to fit the content inside. The problem I’m facing is that the content is aligned to the center of the layout-card HTML object.

      - type: custom:layout-card
        layout_type: custom:horizontal-layout
        layout:
          max_width: initial
          place-items: start
          card_margin: 10px
        cards: ...

This is generated:

I need to position the elements to the start (left) of the horizontal-layout HTML object, with the CSS object place-items: start.

There are 2 options:

  • To add the place-items: start to the horizontal-layout element
  • Or add the justify-content: start to the #columns div, which has, by default in the card, the value set to center

I tried with card_mod even, but I can’t make it work, because it is not a ha-card. Any advice?

I love you mate.
If you ever come to Italy one day, send me a message and I’ll buy you a beer (or even two).

By(t)e

1 Like

Is it possible to set the with depended on the screen size of a device?

I tried with show, but can not get it to work.

For my older android i would like to have a smaller dashboard and for my newer/langer screen a bigger size.

(ps. examle shows twice the same width, ofc thats wrong/for test.)

But they have bothe the same size?

Here is what I’m using to experiment. You’ll have to upload the images to
/homeassistant/www/car.
card_margin if needed isn’t working. Known bug
this works:

    card_mod:
        style: |
           :host {
               --masonry-view-card-margin: 0px;
           }

This is the code for a complete view

views:
  - path: default_view
    title: Home
    type: panel
    cards:
      - type: custom:layout-card
        layout_type: custom:grid-layout
        layout:
          gap: 0
          padding: 0
          card_margin: 0
          grid-template-columns: repeat(6, 91.5px)
          grid-template-rows: auto
          grid-template-areas: |
            "plaats1 plaats2 plaats3 plaats4 plaats5 plaats6"
            "plaats7 plaats8 plaats9 plaats10 plaats11 plaats12"
          mediaquery:
            '(max-width: 415px)':
              gap: 0
              margin: 0
              padding: 0
              card_margin: 0px
              grid-template-columns: repeat(1, 100%)
              grid-template-rows: auto
              grid-template-areas: |
                "plaats1"
                "plaats2"
                "plaats3"
                "plaats4"
                "plaats5"
                "plaats6"
            '(max-width: 920px)':
              gap: 0
              margin: 0
              padding: 0
              card_margin: 0px
              grid-template-columns: repeat(3, 91.5px)
              grid-template-rows: auto
              grid-template-areas: |
                "plaats1 plaats2 plaats3"
                "plaats4 plaats5 plaats6"
                "plaats7 plaats8 plaats9"                
            '(max-width: 1400px)':
              gap: 0
              margin: 0
              padding: 0
              card_margin: 0px
              grid-template-columns: repeat(4, 91.5px)
              grid-template-rows: auto
              grid-template-areas: |
                "plaats1 plaats2 plaats3 plaats4"
                "plaats5 plaats6 plaats7 plaats8"
                "plaats9 plaats10 plaats11 plaats12"
            '(max-width: 2100px)':
              gap: 0
              margin: 0
              padding: 0
              card_margin: 0px
              grid-template-columns: repeat(6, 91.5px)
              grid-template-rows: auto
              grid-template-areas: >
                "plaats1 plaats2 plaats3 plaats4 plaats5 plaats6" "plaats7
                plaats8 plaats9 plaats10 plaats11 plaats12"                
        cards:
          - type: markdown
            content: |
              <div style="line-height: 1; margin: 0; padding: 0;">1</div>
            view_layout:
              grid-area: plaats1
            card_mod:
              style: |
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card {
                  height: 100% !important;
                  width: 100% !important;
                  border: 6px solid black !important;
                  display: flex !important;
                  justify-content: center !important;
                  align-items: center !important;
                  padding: 0 !important;
                  margin: 0 !important;
                  font-size: 36px !important;
                  font-weight: bold !important;
                  overflow: hidden !important;
                  card_margin: 0px
                }                
          - type: markdown
            content: |
              <div style="line-height: 1; margin: 0; padding: 0;">2</div>
            view_layout:
              grid-area: plaats2
            card_mod:
              style: |
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card {
                  height: 113.3px !important;
                  width: 91.5px !important;
                  border: 6px solid black !important;
                  display: flex !important;
                  justify-content: center !important;
                  align-items: center !important;
                  padding: 0 !important;
                  margin: 0 !important;
                  font-size: 36px !important;
                  font-weight: bold !important;
                  overflow: hidden !important;
                  card_margin: 0px
                }                
          - type: markdown
            content: |
              <div style="line-height: 1; margin: 0; padding: 0;">3</div>
            view_layout:
              grid-area: plaats3
            card_mod:
              style: |
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card {
                  height: 113.3px !important;
                  width: 91.5px !important;
                  border: 6px solid black !important;
                  display: flex !important;
                  justify-content: center !important;
                  align-items: center !important;
                  padding: 0 !important;
                  margin: 0 !important;
                  font-size: 36px !important;
                  font-weight: bold !important;
                  overflow: hidden !important;
                  card_margin: 0px
                }                
          - type: markdown
            content: |
              <div style="line-height: 1; margin: 0; padding: 0;">4</div>
            view_layout:
              grid-area: plaats4
            card_mod:
              style: |
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card {
                  height: 113.3px !important;
                  width: 91.5px !important;
                  border: 6px solid black !important;
                  display: flex !important;
                  justify-content: center !important;
                  align-items: center !important;
                  padding: 0 !important;
                  margin: 0 !important;
                  font-size: 36px !important;
                  font-weight: bold !important;
                  overflow: hidden !important;
                  card_margin: 0px
                }                
          - type: markdown
            content: |
              <div style="line-height: 1; margin: 0; padding: 0;">5</div>
            view_layout:
              grid-area: plaats5
            card_mod:
              style: |
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card {
                  height: 113.3px !important;
                  width: 91.5px !important;
                  border: 6px solid black !important;
                  display: flex !important;
                  justify-content: center !important;
                  align-items: center !important;
                  padding: 0 !important;
                  margin: 0 !important;
                  font-size: 36px !important;
                  font-weight: bold !important;
                  overflow: hidden !important;
                  card_margin: 0px
                }                
          - type: markdown
            content: |
              <div style="line-height: 1; margin: 0; padding: 0;">6</div>
            view_layout:
              grid-area: plaats6
            card_mod:
              style: |
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card {
                  height: 113.3px !important;
                  width: 91.5px !important;
                  border: 6px solid black !important;
                  display: flex !important;
                  justify-content: center !important;
                  align-items: center !important;
                  padding: 0 !important;
                  margin: 0 !important;
                  font-size: 36px !important;
                  font-weight: bold !important;
                  overflow: hidden !important;
                  card_margin: 0px
                }                
          - type: picture
            image: /local/car/aimg7.png
            view_layout:
              grid-area: plaats7
            card_mod:
              style: |
                ha-card {
                  width: 91.5px;
                  height: 113.3px;
                  padding: 0;
                  border: none;
                  border-radius: 0;
                }
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card img {
                  display: block; /* <-- ADD THIS LINE */
                  width: 100%;
                  height: 100%;
                  object-fit: fill;
                }
          - type: picture
            image: /local/car/aimg8.png
            view_layout:
              grid-area: plaats8
            card_mod:
              style: |
                ha-card {
                  width: 91.5px;
                  height: 113.3px;
                  padding: 0;
                  border: none;
                  border-radius: 0;
                }
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card img {
                  display: block; /* <-- ADD THIS LINE */
                  width: 100%;
                  height: 100%;
                  object-fit: fill;
                }
          - type: picture
            image: /local/car/aimg9.png
            view_layout:
              grid-area: plaats9
            card_mod:
              style: |
                ha-card {
                  width: 91.5px;
                  height: 113.3px;
                  padding: 0;
                  border: none;
                  border-radius: 0;
                }
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card img {
                  display: block; /* <-- ADD THIS LINE */
                  width: 100%;
                  height: 100%;
                  object-fit: fill;
                }
          - type: picture
            image: /local/car/aimg10.png
            view_layout:
              grid-area: plaats10
            card_mod:
              style: |
                ha-card {
                  width: 91.5px;
                  height: 113.3px;
                  padding: 0;
                  border: none;
                  border-radius: 0;
                }
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card img {
                  display: block; /* <-- ADD THIS LINE */
                  width: 100%;
                  height: 100%;
                  object-fit: fill;
                }
          - type: picture
            image: /local/car/aimg11.png
            view_layout:
              grid-area: plaats11
            card_mod:
              style: |
                ha-card {
                  width: 91.5px;
                  height: 113.3px;
                  padding: 0;
                  border: none;
                  border-radius: 0;
                }
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card img {
                  display: block; /* <-- ADD THIS LINE */
                  width: 100%;
                  height: 100%;
                  object-fit: fill;
                }
          - type: picture
            image: /local/car/aimg12.png
            view_layout:
              grid-area: plaats12
            card_mod:
              style: |
                ha-card {
                  width: 91.5px;
                  height: 113.3px;
                  padding: 0;
                  border: none;
                  border-radius: 0;
                }
                :host {
                  --masonry-view-card-margin: 0px;
                }
                ha-card img {
                  display: block; /* <-- ADD THIS LINE */
                  width: 100%;
                  height: 100%;
                  object-fit: fill;
                }

aimg7
aimg8
aimg9
aimg10
aimg11
aimg12

I’m trying to place a 2-button row under a 380px-wide status block. With unequal columns (e.g., 280px and 100px) using custom:layout-card, the left edge of the row shifts inward compared to the status block above. With equal columns the alignment is fine. This happens both with Tile and with Mushroom template cards.

The goal is simple: 380px container, same 4px L/R padding as the status block, and a 2-column row (wide “Start” + narrow “Off”) that aligns exactly left with the status block.

type: custom:layout-card
layout_type: grid
layout:
  grid-template-columns: 280px 100px
  grid-gap: 0
cards:
  - type: tile
    entity: switch.tapw_timeprogram_bms_forced_qube2
    name: Tapwater starten
    icon: mdi:hot-tub
    vertical: false
    tap_action:
      action: call-service
      service: switch.turn_on
      target:
        entity_id: switch.tapw_timeprogram_bms_forced_qube2
    card_mod:
      style:
        ha-tile-info$: |
          .secondary { display: none !important; }
  - type: tile
    entity: switch.tapw_timeprogram_bms_forced_qube2
    name: Uit
    icon: mdi:stop
    vertical: false
    tap_action:
      action: call-service
      service: switch.turn_off
      target:
        entity_id: switch.tapw_timeprogram_bms_forced_qube2
    card_mod:
      style:
        ha-tile-info$: |
          .secondary { display: none !important; }

Play with negative margins in layout block:

layout:
  margin: "-4px -4px -4px -4px"
  grid-template-columns: 280px 100px
  grid-gap: 0

Can you share the entire card code to include the top card?

As mention, a negative left margin may solve your issue

type: custom:layout-card
layout_type: grid
layout:
  grid-template-columns: 40% 60%
  margin: 0px 0px 0px -14px
cards:
  - type: tile
    entity: switch.xxxx
    name: Tapwater starten
    icon: mdi:hot-tub
  - type: tile
    entity: switch.xxxxxx
    name: Tapwater starten
    icon: mdi:hot-tub
  • margin: 1px 2px 3px 4px
    • top margin is 1px
    • right margin is 2px
    • bottom margin is 3px
    • left margin is 4px

Big thanks.
I started with margin. left -4px did the trick for the left side. Unfortunately, the right side started to shift left which I could not solve with margin option.
grid-template-columns: did the trick, I increased the second column by 8px. Although the total value exceeds the 380, the two items are now aligned.

  layout:
    margin: 0px 0px 0px -4px
    grid-template-columns: 280px 108px
    grid-gap: 0

1 Like

Glad it worked out and happy to assist!

This happened because you specified fixed widths in pixels. This could be prevented by:

grid-template-columns: 280px auto

Or

grid-template-columns: 70% 30%

Tried both option and with both options there is a small indent on the right side.
I might have understood if I had to add 4px (so 104px), but I don’t understand is why I have to add 8px if width is 380px, margin -4. Might be that the status block is 384?
Code status block

type: custom:mod-card
card_mod:
  style: |
    ha-card { width: 380px; margin: 0; }
card:
  type: vertical-stack
  cards:
    - type: custom:button-card
      name: Warm water
      show_state: false
      show_icon: false
      tap_action:
        action: navigate
        navigation_path: /huis/0
      styles:
        grid:
          - grid-template-areas: "'home n gear' 'info info info'"
          - grid-template-columns: 40px 1fr 40px
          - grid-template-rows: auto 1fr
        card:
          - padding: 4px
          - height: 180px
          - width: 380px
          - overflow: hidden
        name:
          - font-size: 14px
          - font-weight: bold
          - text-align: left
          - padding-bottom: 4px
        custom_fields:
          home:
            - display: flex
            - align-items: center
            - justify-content: center
          gear:
            - display: flex
            - align-items: center
            - justify-content: center
          info:
            - padding: 4px
      custom_fields:

I have created this status block. Intent is to go to home view, if you click on home icon and to settings view if you click on gear.

Enclosed code works great in my browser. If I click exactly on gear the settings view opens clicking somewhere else is going back to home.

Unfortunately, this is not working on the app. My guess is that the gear and home areas are overlapping and it’s luck that this works in browser.

I would like to create 3 areas (left, middle and right). Left and right slightly bigger than icon and rest is middle. In this case I will link left and middle both to the home view. Three columns will give me flexibility for the future.

Tried a lot but was not successful. Can someone provide a tip on how to define the 3 areas

Thanks.

type: custom:mod-card
card_mod:
  style: |
    ha-card { width: 380px; margin: 0; }
card:
  type: vertical-stack
  cards:
    - type: custom:button-card
      name: Warm water
      show_state: false
      show_icon: false
      tap_action:
        action: navigate
        navigation_path: /huis/0
      styles:
        grid:
          - grid-template-areas: "'home n gear' 'info info info'"
          - grid-template-columns: 40px 1fr 40px
          - grid-template-rows: auto 1fr
        card:
          - padding: 4px
          - height: 180px
          - width: 380px
          - overflow: hidden
        name:
          - font-size: 14px
          - font-weight: bold
          - text-align: left
          - padding-bottom: 4px
        custom_fields:
          home:
            - display: flex
            - align-items: center
            - justify-content: center
          gear:
            - display: flex
            - align-items: center
            - justify-content: center
          info:
            - padding: 4px
      custom_fields:
        home: >
          <ha-icon icon="mdi:home" style="color: var(--primary-color); width:
          38px; height: 38px;"></ha-icon>
        gear: |
          [[[
            // Alarm-badge op het tandwiel + eigen navigatie (geen bubbels)
            const anyAlarm =
              states['binary_sensor.glbal_qube2']?.state === 'on' ||
              states['binary_sensor.al_maxtime_antileg_active_qube2']?.state === 'on' ||
              states['binary_sensor.al_maxtime_dhw_active_qube2']?.state === 'on';
            const alarmDot = anyAlarm
              ? '<span style="position:absolute; top:-2px; right:-2px; width:10px; height:10px; background:var(--error-color); border-radius:50%; box-shadow:0 0 0 2px white;"></span>'
              : '';
            return `
              <div style="position:relative; width:38px; height:38px; display:flex; align-items:center; justify-content:center; cursor:pointer;"
                   onclick="event.stopPropagation(); window.history.pushState(null,'','/huis/wp2-instelling'); window.dispatchEvent(new Event('location-changed'));">
                <ha-icon icon="mdi:cog" style="width:22px; height:22px; color: var(--primary-text-color);"></ha-icon>
                ${alarmDot}
              </div>
            `;
          ]]]
        info: |
          [[[
            // ===== Helpers =====
            const safeNum = (eid,d=1)=>{const s=states[eid]?.state;if(!s||s==='unknown'||s==='unavailable')return null;const v=parseFloat(s);return isNaN(v)?null:parseFloat(v.toFixed(d));};
            const safeTxt = (eid)=>{const s=states[eid]?.state;return (s&&s!=='unknown'&&s!=='unavailable')?s:'';};

            // ===== Entities =====
            const statusCode = parseInt(states['sensor.unitstatus_qube2']?.state ?? 'NaN');  // 0,1,6,8,9,15,16,17,22
            const serviceTxt = safeTxt('sensor.qube_driewegklep_dhw_cv_status_2');           // 'CV' of 'SWW'
            const tDHWT      = safeNum('sensor.dhw_temp_qube2', 1);

            // Alarm => rood randje om de info-zone
            const anyAlarm =
              states['binary_sensor.glbal_qube2']?.state === 'on' ||
              states['binary_sensor.al_maxtime_antileg_active_qube2']?.state === 'on' ||
              states['binary_sensor.al_maxtime_dhw_active_qube2']?.state === 'on';
            const alarmStyle = anyAlarm ? 'box-shadow: inset 0 0 0 2px var(--error-color); border-radius: 8px;' : '';

            // ===== Kleur per status =====
            const colorByStatus = {
              0:'#000000', 1:'#1e88e5', 6:'#e53935',
              8:'#43a047', 9:'#43a047',
              15:'#9e9e9e', 16:'#9e9e9e',
              17:'#f9a825', 22:'#43a047'
            };
            const iconColor = colorByStatus[statusCode] ?? '#607d8b';

            // ===== Icoonkeuze volgens tabel =====
            const pickIcon = (code, service)=>{
              if ([0,1,6].includes(code)) return 'mdi:hot-tub'; // altijd SWW
              if (code === 22) return 'mdi:hot-tub';            // Heating DHW
              return (service === 'CV') ? 'mdi:radiator' : 'mdi:hot-tub';
            };
            const statusIcon = pickIcon(statusCode, serviceTxt);

            // ===== Gauge (klok) =====
            const SCALE_MIN = 20, SCALE_MAX = 70;
            const START_DEG = -120, END_DEG = 120;
            const CX = 60, CY = 60, R = 46;

            const clamp=(v,min,max)=>Math.max(min,Math.min(max,v));
            const mapTempToDeg=(T)=>{ if(T==null)return START_DEG; const p=(clamp(T,SCALE_MIN,SCALE_MAX)-SCALE_MIN)/(SCALE_MAX-SCALE_MIN); return START_DEG + p*(END_DEG-START_DEG); };
            const polarToXY=(cx,cy,r,deg)=>{ const rad=(deg-90)*Math.PI/180; return [cx+r*Math.cos(rad), cy+r*Math.sin(rad)]; };
            const arcPath=(cx,cy,r,d1,d2)=>{ const [x1,y1]=polarToXY(cx,cy,r,d1); const [x2,y2]=polarToXY(cx,cy,r,d2); const la=(Math.abs(d2-d1)>180)?1:0; const sw=d2>d1?1:0; return `M ${x1.toFixed(1)} ${y1.toFixed(1)} A ${r} ${r} 0 ${la} ${sw} ${x2.toFixed(1)} ${y2.toFixed(1)}`; };

            // Zones (vast)
            const A1s=START_DEG, A1e=mapTempToDeg(40); // geel
            const A2s=A1e,       A2e=mapTempToDeg(55); // groen
            const A3s=A2e,       A3e=mapTempToDeg(62); // donkergroen
            const A4s=A3e,       A4e=END_DEG;          // rood

            const needleDeg = mapTempToDeg(tDHWT ?? SCALE_MIN);
            const [nx, ny] = polarToXY(CX, CY, R-6, needleDeg);

            const C_Y='#f9a825', C_G='#43a047', C_DG='#1b5e20', C_R='#e53935', C_RING='#e0e0e0';

            // Linker status icoon (groot, verticaal gecentreerd)
            const colStatus = `
              <div style="flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; height:120px;">
                <ha-icon icon="${statusIcon}" style="color:${iconColor}; width:64px; height:64px;"></ha-icon>
              </div>
            `;

            // Gauge in het midden + temperatuur dichter tegen de gauge
            const dhwText = (tDHWT!=null) ? `${tDHWT.toFixed(1)} °C` : '...';
            const colDHW = `
              <div style="text-align:center; flex:1;">
                <svg width="120" height="120" viewBox="0 0 120 120">
                  <path d="${arcPath(CX,CY,R, START_DEG, END_DEG)}" stroke="${C_RING}" stroke-width="10" fill="none" />
                  <path d="${arcPath(CX,CY,R, A1s, A1e)}" stroke="${C_Y}" stroke-width="10" fill="none" />
                  <path d="${arcPath(CX,CY,R, A2s, A2e)}" stroke="${C_G}" stroke-width="10" fill="none" />
                  <path d="${arcPath(CX,CY,R, A3s, A3e)}" stroke="${C_DG}" stroke-width="10" fill="none" />
                  <path d="${arcPath(CX,CY,R, A4s, A4e)}" stroke="${C_R}" stroke-width="10" fill="none" />
                  ${[20,30,40,50,60,70].map(v=>{
                    const deg=mapTempToDeg(v); const [tx,ty]=polarToXY(CX,CY,R+8,deg);
                    return `<text x='${tx.toFixed(1)}' y='${ty.toFixed(1)}' text-anchor='middle' alignment-baseline='middle' font-size='10' fill='#555'>${v}</text>`;
                  }).join('')}
                  <circle cx="${CX}" cy="${CY}" r="4" fill="#555"/>
                  <line x1="${CX}" y1="${CY}" x2="${nx.toFixed(1)}" y2="${ny.toFixed(1)}" stroke="#111" stroke-width="3" stroke-linecap="round"/>
                </svg>
                <div style="margin-top:-16px; line-height:1; font-size:16px; font-weight:600;">${dhwText}</div>
              </div>
            `;

            // Wrapper met alarmstijl
            return `
              <div style="display:flex; align-items:center; justify-content:space-between; width:100%; ${alarmStyle}">
                <div style="display:flex; gap:8px; flex:1;">
                  ${colStatus}
                  ${colDHW}
                </div>
              </div>
            `;
          ]]]

Layout Card adds the following margin by default: 0px 4px 0px 4px
See under the heading Layouts here: GitHub - thomasloven/lovelace-layout-card: 🔹 Get more control over the placement of lovelace cards.

We can suppress this standard margin as follows:

margin: 0px -4px 0px -4px

That should make my suggestions for grid-template-columns work. You may need to readjust the margin values a little.

Tested both options with

margin: 0px -4px 0px -4px

And indeed they both give the expected result!.
Thanks

You’re welcome. I’m glad it works.

1 down, still 1 to go. I spend quite some hours on these topics, so your support is highly appreciated.
Any feedback regarding my second question (sorry for asking)

This is a somewhat more complex code and question. However, there are also experts here for custom:button-card. You might want to ask your question again here:

But one suggestion would be to “embed” additional custom:button cards nested as custom_fields. This allows you to assign different tap_actions to all three. This is explained here: