Help needed for positioning and sizing elements in Custom Button Card

alexa-weather

Hi all. I’m attempting to replicate this screen using the Custom Button Card. My hope is that I can write the card in a way that it resizes to fit any screen. I think this is possible by using percentages for values.

type: custom:button-card
variables:
  alarm_entity: sensor.living_room_echo_show_next_alarm
  var_weather_entity: weather.home
  use_24_hour_time: false
entity: none
show_state: false
show_icon: false
show_name: false
triggers_update: all
tap_action: none
hold_action: none
double_tap_action: none
styles:
  grid:
    - grid-template-areas: '"date date" "temperature detail"'
    - grid-template-rows: 1fr 15fr
    - grid-template-columns: 1fr fr
    - row-gap: .5rem
  card:
    - height: 100%
    - background-color: '#059bf1'
    - background-size: cover
    - border-radius: 0px
    - border: 2px solid red
    - overflow: hidden
  custom_fields:
    date:
      - justify-self: start
      - z-index: 2
    temperature:
      - justify-self: center
      - z-index: 2
    detail:
      - justify-self: start
      - z-index: 2
custom_fields:
  date:
    card:
      type: custom:button-card
      show_name: true
      name: |
        [[[    
          const options = { weekday: "long", month: "long", day: "numeric", year: 'numeric' };
          return new Date().toLocaleDateString([], options)
        ]]]
      styles:
        card:
          - height: 100%
          - padding: 0
          - background: none
          - border: 2px solid red
        name:
          - color: black
          - justify-self: center
          - align-self: center
          - font-size: 200%
  temperature:
    card:
      type: custom:button-card
      show_name: true
      name: |
        [[[
          var temperature = states[variables.var_weather_entity].attributes.temperature;
          return `${temperature}°`
        ]]]
      styles:
        card:
          - height: 100%
          - padding: 0
          - background: none
          - border: 2px solid red
        name:
          - color: black
          - justify-self: center
          - align-self: center
          - font-size: 500%
  detail:
    card:
      type: custom:button-card
      show_name: true
      name: |
        [[[
          const directions = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"]
          var apparent_temperature = states[variables.var_weather_entity].attributes.apparent_temperature;
          var wind_bearing = states[variables.var_weather_entity].attributes.wind_bearing;
          var wind_speed = states[variables.var_weather_entity].attributes.wind_speed;
          var wind_speed_unit = states[variables.var_weather_entity].attributes.wind_speed_unit;    
          var wind_bearing_compass = directions[parseInt( wind_bearing / 22.5)]
          var precipitation = states[variables.var_weather_entity].attributes.forecast[0].precipitation_probability

          return `<table align=left ><tr><td>Real Feel: <b>${apparent_temperature}°</b></td></tr>
                  <tr><td>Precipitation: <b>${precipitation}%</b></td></tr>
                  <tr><td>Wind: <b>${wind_bearing_compass} ${wind_speed} ${wind_speed_unit}</b></td></tr>
                  </table>`
        ]]]
      styles:
        card:
          - height: 100%
          - padding: 0
          - background: none
          - border: 2px solid red
          - align: relative
          - left: '-20%'
        name:
          - color: black
          - justify-self: center
          - align-self: center
          - font-size: 200%
          - text-align: left

This is my progress so far. I added a border to try and better understand the size and placement of these elements. Here’s of things I could use help understanding:

  • How do I make the view fit all of the available space? You’ll notice a vertical scroll bar on the right side
  • How can I best control the placement of the temperature and the detailed information elements? I am unsure how to place vertically or horizontally.
  • Why is the date not at the exact top corner of the card? I can’t see where I have any padding or other offsets that would account for the space at the top. I am fine with it’s placement but curious why it is not aligned to the corner of the containing card

Any advice is certainly appreciated. I am just learning CSS and the positioning and sizing methods are taking a while for me to comprehend.

Half of your problem is that you’re treating detail as a separate button card. I don’t have the code exactly, but you should focus your time on learning CSS placement in a grid. Use w3schools to learn the CSS, then apply it to the text. Keep in mind, you can output HTML directly from the custom field that contains that information without the additional custom button card.

You can even treat them as 3 separate grid.

E.g.

___________________
__________________|
|        |________|
|        |________|
|________|________|

type: custom:button-card
variables:
  alarm_entity: sensor.living_room_echo_show_next_alarm
  var_weather_entity: weather.home
  use_24_hour_time: false
entity: none
show_state: false
show_icon: false
show_name: false
triggers_update: all
tap_action: none
hold_action: none
double_tap_action: none
styles:
  grid:
    - grid-template-areas: |
        "date date"  
        "temperature detail"  
    - grid-template-rows: 1fr 15fr
    - grid-template-columns: 1fr fr
    - row-gap: .5rem
  card:
    - height: 100%
    - background-color: '#059bf1'
    - background-size: cover
    - border-radius: 0px
    - border: 2px solid red
    - overflow: hidden
  custom_fields:
    date:
      - justify-self: start
      - z-index: 2
      - font-size: 200%
    temperature:
      - justify-self: center
      - z-index: 2
    detail:
      - justify-self: center
      - z-index: 2
custom_fields:
  date: |
    [[[
          const options = { weekday: "long", month: "long", day: "numeric", year: 'numeric' };
          return new Date().toLocaleDateString([], options)
    ]]]
  temperature: |
    [[[
      var temperature = states[variables.var_weather_entity].attributes.temperature;
      return `${temperature}°`
    ]]]
  detail: |

    [[[
      const directions = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"]
      var apparent_temperature = states[variables.var_weather_entity].attributes.apparent_temperature;
      var wind_bearing = states[variables.var_weather_entity].attributes.wind_bearing;
      var wind_speed = states[variables.var_weather_entity].attributes.wind_speed;
      var wind_speed_unit = states[variables.var_weather_entity].attributes.wind_speed_unit;    
      var wind_bearing_compass = directions[parseInt( wind_bearing / 22.5)]
      var precipitation = states[variables.var_weather_entity].attributes.forecast[0].precipitation_probability

      return `<table align=left ><tr><td>Real Feel: <b>${apparent_temperature}°</b></td></tr>
              <tr><td>Precipitation: <b>${precipitation}%</b></td></tr>
              <tr><td>Wind: <b>${wind_bearing_compass} ${wind_speed} ${wind_speed_unit}</b></td></tr>
              </table>`
    ]]]

Thanks for the tips. I will check out w3 school and see what I can learn.

I have reworked things without the cards. I can see where that would simplify things but I still don’t understand how to move the detail section (highlighted yellow) over to the middle. I’ve tried justify-self start/center and no matter what it is stuck to what I guess is end. Any ideas?

most likely align-self: start

I’ve taken your advice and am looking through the align options. From the list of eight available as described at CSS Box Alignment Module Level 3 W3 page, the only one that moves the detail section is the align-self option. Unfortunately, this just moves the table vertically and not horizontally. Above is an example of align-self: start . ‘center’ puts it in the middle but stuck on the right side and ‘end’ puts it at the bottom also stuck to the right. I’m a loss on how to move it horizontally with ANY option.

For completeness, this is the code with that one change made. I’m also noticing that my table align left is not being honored either.

type: custom:button-card
variables:
  alarm_entity: sensor.living_room_echo_show_next_alarm
  var_weather_entity: weather.home
  use_24_hour_time: false
entity: none
show_state: false
show_icon: false
show_name: false
triggers_update: all
tap_action: none
hold_action: none
double_tap_action: none
styles:
  grid:
    - grid-template-areas: |
        "date date"  
        "temperature detail"  
    - grid-template-rows: 1fr 15fr
    - grid-template-columns: 1fr fr
    - row-gap: .5rem
  card:
    - height: 100%
    - background-color: '#059bf1'
    - background-size: cover
    - border-radius: 0px
    - border: 2px solid red
    - overflow: hidden
  custom_fields:
    date:
      - justify-self: start
      - z-index: 2
      - font-size: 200%
    temperature:
      - align-self: center
      - z-index: 2
    detail:
      - align-self: start
      - z-index: 2
custom_fields:
  date: |
    [[[
          const options = { weekday: "long", month: "long", day: "numeric", year: 'numeric' };
          return new Date().toLocaleDateString([], options)
    ]]]
  temperature: |
    [[[
      var temperature = states[variables.var_weather_entity].attributes.temperature;
      return `${temperature}°`
    ]]]
  detail: |

    [[[
      const directions = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"]
      var apparent_temperature = states[variables.var_weather_entity].attributes.apparent_temperature;
      var wind_bearing = states[variables.var_weather_entity].attributes.wind_bearing;
      var wind_speed = states[variables.var_weather_entity].attributes.wind_speed;
      var wind_speed_unit = states[variables.var_weather_entity].attributes.wind_speed_unit;    
      var wind_bearing_compass = directions[parseInt( wind_bearing / 22.5)]
      var precipitation = states[variables.var_weather_entity].attributes.forecast[0].precipitation_probability

      return `<table align=left ><tr><td>Real Feel: <b>${apparent_temperature}°</b></td></tr>
              <tr><td>Precipitation: <b>${precipitation}%</b></td></tr>
              <tr><td>Wind: <b>${wind_bearing_compass} ${wind_speed} ${wind_speed_unit}</b></td></tr>
              </table>`
    ]]]

try adding text-align: start then

FYI, you can use dev tools to play with things…

Thanks for the tip on dev tools. Unfortunately I’m not getting the ‘Inspect’ when I right click on the web page so I’m uncertain how to pull up specifics for the elements in the dev tools view. I think this may be because the whole card is a button so the right click is not being accepted?

I decided to give your other suggestion a try and break up the grid to have more individual elements instead of the HTML table I was using. I was able to get things to line up much better once I altered my grid-template-rows and grid-template-columns. Unfortunately I’m currently only using up a small portion of the card and I’d like to use the entire card. Further, it seems when I change the font size of the temperature element on the left it increases the space between the ‘Real Feel’ and the ‘Precipitation’ and ‘Wind’ elements on the right. My belief/hope was that all of these should act independently of each other. Is that not the case?

Here’s my updated code:

type: custom:button-card
variables:
  alarm_entity: sensor.living_room_echo_show_next_alarm
  var_weather_entity: weather.home
  use_24_hour_time: false
entity: none
show_state: false
show_icon: false
show_name: false
triggers_update: all
tap_action: none
hold_action: none
double_tap_action: none
styles:
  grid:
    - grid-template-areas: |
        "date date"  
        "temperature detail1"
        "temperature detail2"
        "temperature detail3"
    - grid-template-rows: 1fr 1fr
    - grid-template-columns: 1fr 1fr
    - row-gap: .5rem
  card:
    - height: 100%
    - background-color: '#059bf1'
    - background-size: cover
    - border-radius: 0px
    - border: 2px solid red
    - overflow: hidden
  custom_fields:
    date:
      - justify-self: start
      - z-index: 2
      - font-size: 200%
      - border: 2px solid red
    temperature:
      - align-self: center
      - justify-self: center
      - z-index: 2
      - border: 2px solid red
      - width: min-content
      - font-size: 100%
    detail1:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - border: 2px solid red
      - width: min-content
    detail2:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - border: 2px solid red
      - width: min-content
    detail3:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - border: 2px solid red
      - width: min-content
custom_fields:
  date: |
    [[[
          const options = { weekday: "long", month: "long", day: "numeric", year: 'numeric' };
          return new Date().toLocaleDateString([], options)
    ]]]
  temperature: |
    [[[
      var temperature = states[variables.var_weather_entity].attributes.temperature;
      return `${temperature}°`
    ]]]
  detail1: |
    [[[
      var apparent_temperature = states[variables.var_weather_entity].attributes.apparent_temperature;
      return `Real Feel: ${apparent_temperature}°`
    ]]]
  detail2: |
    [[[
      var precipitation = states[variables.var_weather_entity].attributes.forecast[0].precipitation_probability
      return `Precipitation: ${precipitation} %`
    ]]]
  detail3: |
    [[[
      const directions = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"]
      var wind_bearing = states[variables.var_weather_entity].attributes.wind_bearing;
      var wind_speed = states[variables.var_weather_entity].attributes.wind_speed;
      var wind_speed_unit = states[variables.var_weather_entity].attributes.wind_speed_unit;    
      var wind_bearing_compass = directions[parseInt( wind_bearing / 22.5)]
      return`Wind: ${wind_bearing_compass} ${wind_speed} ${wind_speed_unit}`
    ]]]    

I think I’m getting closer with each iteration but still the gird layout is not doing what I had hoped. I certainly appreciate your insight on this.

1 Like

Thanks. I got this part working now. Seems like a good tool to attempt and modify things without changing the card config.

I’m still unsure how I should be configuring the grid-template-rows/columns or how to fill the whole view. I will surely look through W3 for this type of stuff but thought that the forum would be a more focused place to find these type answers from folks who have had these ‘problems’ before me?


this is using min-content for the first row, and max-content for the first column. The rest is 1fr. Try and see if that solves some problems. Your grid areas look good

1 Like

also there is ofc padding on the card. 2rem in my case. But that is something you need to play with since you want your card to fill a bigger sreen.

Thanks for the help. I think that the changes you suggested are working out. I ended up using:

    - grid-template-rows: min-content 1fr
    - grid-template-columns: 1fr 1fr

Here’s the updated code:

type: custom:button-card
variables:
  alarm_entity: sensor.living_room_echo_show_next_alarm
  var_weather_entity: weather.home
  use_24_hour_time: false
entity: none
show_state: false
show_icon: false
show_name: false
triggers_update: all
tap_action: none
hold_action: none
double_tap_action: none
styles:
  grid:
    - grid-template-areas: |
        "date date"  
        "temperature detail1"
        "temperature detail2"
        "temperature detail3"
    - grid-template-rows: min-content 1fr
    - grid-template-columns: 1fr 1fr
    - row-gap: .5rem
  card:
    - height: 100%
    - background-color: '#059bf1'
    - background-size: cover
    - border-radius: 0px
    - border: 2px solid red
    - overflow: hidden
    - color: black
    - font-family: '"Roboto", sans-serif'
  custom_fields:
    date:
      - justify-self: start
      - align-self: start
      - z-index: 2
      - font-size: 200%
      - width: max-content
    temperature:
      - align-self: center
      - justify-self: end
      - z-index: 2
      - border-right: 2px solid black
      - width: max-content
      - font-size: 600%
      - padding-right: 5%
    detail1:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - width: min-content
      - height: 100%
      - font-size: 200%
      - padding-left: 5%
    detail2:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - width: min-content
      - height: 100%
      - font-size: 200%
      - padding-left: 5%
    detail3:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - width: min-content
      - height: 100%
      - font-size: 200%
      - padding-left: 5%
custom_fields:
  date: |
    [[[
          const options = { weekday: "long", month: "long", day: "numeric", year: 'numeric' };
          return new Date().toLocaleDateString([], options)
    ]]]
  temperature: |
    [[[
      var temperature = states[variables.var_weather_entity].attributes.temperature;
      return `${temperature}°`
    ]]]
  detail1: |
    [[[
      var apparent_temperature = states[variables.var_weather_entity].attributes.apparent_temperature;
      return `Real Feel: <b>${apparent_temperature}°</b>`
    ]]]
  detail2: |
    [[[
      var precipitation = states[variables.var_weather_entity].attributes.forecast[0].precipitation_probability
      return `Precipitation: <b>${precipitation}%</b>`
    ]]]
  detail3: |
    [[[
      const directions = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"]
      var wind_bearing = states[variables.var_weather_entity].attributes.wind_bearing;
      var wind_speed = states[variables.var_weather_entity].attributes.wind_speed;
      var wind_speed_unit = states[variables.var_weather_entity].attributes.wind_speed_unit;    
      var wind_bearing_compass = directions[parseInt( wind_bearing / 22.5)]
      return`Wind: <b>${wind_bearing_compass} ${wind_speed} ${wind_speed_unit}</b>`
    ]]]    

So now I am pretty much where I want to be horizontally but would like to add a lot more space between the first row and the second and add some blank space at the bottom. Basically want the space from the bottom of the first row to the bottom of the card space filled with the blue background color and have the second row centered vertically in that space. Is there a way to define that the whole card be used?

You can use template even for the ( buildin ) “name”
I suggest you ditch the custom(date) and place the template under the (name)


name: |
  [[[
    return helpers.formatTime24h(entity.attributes.forecast[0].datetime)
  ]]]
custom_fields:

It’s the buildin “name” who takes a row in top, so currently you have to use margin-top: -x%, to move your date up
If you use name, it should not be necessary
05.02.2024_23.59.47_REC
06.02.2024_00.10.25_REC

or maybe it’s the default grid margins who cause it :thinking:
Im using this in my theme, maybe thats why :blush:

masonry-view-card-margin: “0px”

Yeah, i like to squeeze

1 Like

Thanks for the tip and that does make sense. I’ve made some decent progress. Thanks to a Kevin Powell YT video, I was able to fill the viewport using a min-height: 100 vdh

Now my date row is stuck on the second row. Maybe it was there all along, but I’m not sure how to move it. I’d think that the start/start I’m using would have put it near the top left corner but apparently not. Can someone take a look and let me know if they can spot how I can move this up vertically without having to use a margin? Surely I should be able to do this right?

type: custom:button-card
variables:
  alarm_entity: sensor.living_room_echo_show_next_alarm
  var_weather_entity: weather.home
  use_24_hour_time: false
entity: none
show_state: false
show_icon: false
show_name: false
triggers_update: all
tap_action: none
hold_action: none
double_tap_action: none
styles:
  grid:
    - grid-template-areas: |
        "date date"  
        "temperature detail1"
        "temperature detail2"
        "temperature detail3"
    - grid-template-rows: min-content 1fr
    - grid-template-columns: 1fr 1fr
    - row-gap: .5rem
  card:
    - min-height: 100dvh
    - background-color: '#059bf1'
    - background-size: cover
    - border-radius: 0px
    - border: 1px solid red
    - overflow: hidden
    - color: black
    - font-family: '"Roboto", sans-serif'
    - font-weight: 300
  custom_fields:
    date:
      - justify-self: start
      - align-self: start
      - z-index: 2
      - font-size: 200%
      - width: max-content
    temperature:
      - align-self: center
      - justify-self: end
      - z-index: 2
      - border-right: 2px solid black
      - width: max-content
      - font-size: 1000%
      - padding-right: 5%
    detail1:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - width: min-content
      - height: 100%
      - font-size: 300%
      - padding-left: 5%
    detail2:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - width: min-content
      - height: 100%
      - font-size: 300%
      - padding-left: 5%
    detail3:
      - align-self: start
      - justify-self: start
      - z-index: 2
      - width: min-content
      - height: 100%
      - font-size: 300%
      - padding-left: 5%
custom_fields:
  date: |
    [[[
          const options = { weekday: "long", month: "long", day: "numeric", year: 'numeric' };
          var date = new Date().toLocaleDateString([], options)
          return `<b style="font-weight:400;">${date}</b>`
          
    ]]]
  temperature: |
    [[[
      var temperature = states[variables.var_weather_entity].attributes.temperature;
      return `${temperature}°`
    ]]]
  detail1: |
    [[[
      var apparent_temperature = states[variables.var_weather_entity].attributes.apparent_temperature;
      return `Real Feel: <b style="font-weight:400;">${apparent_temperature}°</b>`
    ]]]
  detail2: |
    [[[
      var precipitation = states[variables.var_weather_entity].attributes.forecast[0].precipitation_probability
      return `Precipitation: <b style="font-weight:400;">${precipitation}%</b>`
    ]]]
  detail3: |
    [[[
      const directions = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"]
      var wind_bearing = states[variables.var_weather_entity].attributes.wind_bearing;
      var wind_speed = states[variables.var_weather_entity].attributes.wind_speed;
      var wind_speed_unit = states[variables.var_weather_entity].attributes.wind_speed_unit;    
      var wind_bearing_compass = directions[parseInt( wind_bearing / 22.5)]
      return`Wind: <b style="font-weight:400;">${wind_bearing_compass} ${wind_speed} ${wind_speed_unit}</b>`
    ]]]    

Beside using

styles:
          card:
            - padding: 0%

And as i mentioned ( and showed above ) … remove the date:-custom field
Place the template in name: ( as showed in pic above )
And under styles: you add

name:
        - font-weight: leighter
        - color: '#87bf50'
        - padding-top: 0px
        - padding-bottom: 0px

use you own style-settings for this container (field)

06.02.2024_15.20.35_REC

edit: also have you tried with -rows: 1fr 1fr ? … instead of

grid-template-rows: min-content 1fr

im not sure why you use min-content

min-content will make that row the height of the content. You don’t want that.

Post an image of the container and you’ll see what it’s doing.

e.g.

image

F12 → selection tool → select the grid like I did. if your row is thin (from the min-content), align-self will do nothing.

Thanks to you both. I took @boheme61 advice and changed grid-template-rows: 1fr 1fr and it did bring the date up a bit. I did not change to name as suggested. I do understand that that may ‘fix’ the current issue, but shouldn’t I still be able to move that element a different way instead?

@petro Does this show what you were requesting? I’m seeing that it appears to have inherited the justify-self start and align-self start and additionally is setting width to max-content which I guess is due to the min-content → 1fr change I mentioned above? I have a red border around the top level card. I still don’t understand why that date text is not much higher than it is. Please excuse my ignorance as I am obviously still learning and have a lot left to learn.

No, you’re not hovering on the grid it self. See how my image shows purple dotted lines?

EDIT: You may need to hover where the edge of the grid lies.

GitHub - custom-cards/button-card: ❇️ Lovelace button-card for home assistant.

Have a look at this picture, describing the layout, in above link to the doc

As You have not defined any img or icon , so by placing your “info” from the area(custom area), in the name (n) it should be at the most top, then change area “date” to n or name, in your grid template area …
another example
06.02.2024_16.12.52_REC

Also, you can force the first row to be i.e 15px ( instead of 1fr ) , but having the font-size to 200% makes no sense

Sorry. Your screenshot was small and I couldn’t see enough detail. How about this? I can now see that the date indeed is where I would expect to see it within it’s grid cell. So the real issue is that my grid is not being expanded across the whole containing card right?