Person Cards - Show Off Yours

Here you go:

type: custom:button-card
styles:
  card:
    - padding: 8px
    - border-radius: 20px
  grid:
    - display: grid
    - grid-template-columns: auto 1fr auto
    - grid-template-rows: auto auto auto
    - grid-template-areas: |
        "picture entity battery"
        "picture state travel"
  custom_fields:
    picture:
      - grid-area: picture
      - justify-self: start
      - align-self: center
    entity:
      - grid-area: entity
      - justify-self: start
      - align-self: end
      - text-align: left
      - margin-left: 15px
    state:
      - grid-area: state
      - justify-self: start
      - align-self: start
      - text-align: left
      - margin-left: 15px
    battery:
      - grid-area: battery
      - justify-self: center
      - align-self: center
      - text-align: left
      - margin-left: 15px
    travel:
      - grid-area: travel
      - justify-self: center
      - align-self: center
      - text-align: left
      - margin-left: 15px
custom_fields:
  picture: |
    [[[
      let state = states['person.your_person_entity'].state;
      let borderColor = "gray";
      if (state === "home") {
        borderColor = "green";
      } else if (state === "not_home") {
        borderColor = "red";
      } else {
        borderColor = "orange";
      }

      return `<div style="position: relative; margin: 0; padding: 0; border: 3px solid ${borderColor}; border-radius: 50%; width: 40px; height: 40px; display: flex; justify-content: center; align-items: center;">
                <img src="${states['person.your_person_entity'].attributes.entity_picture}" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover; margin: 0; padding: 0;">
              </div>`;
    ]]]
  entity: |
    [[[
      return `<div style="font-size: 12px; font-weight: bold; margin: 0; padding: 0; text-transform: uppercase;">
                ${states['person.your_person_entity'].attributes.friendly_name}
              </div>`;
    ]]]
  state: |
    [[[
      let state = states['person.your_person_entity'].state;
      if (state === "home") {
        state = "Home";
      } else if (state === "not_home") {
        state = "Away";
      }
      return `<div style="font-size: 10px; color: var(--secondary-text-color); margin: 0; padding: 0;">
                ${state}
              </div>`;
    ]]]
  battery: |
    [[[
      let battery = states['sensor.your_battery_level_sensor'].state;
      let charging = states['sensor.your_battery_state_sensor'].state;
      let color = "green";
      if (battery < 10) {
        color = "red";
      } else if (battery < 30) {
        color = "orange";
      } else if (battery < 50) {
        color = "yellow";
      } else {
        color = "green";
      }

      return `<div style="position: relative; font-size: 14px; color: var(--secondary-text-color); margin: 0; padding: 0;">
                <svg width="30" height="30" viewBox="0 0 36 36" class="circular-chart">
                  <path class="circle-bg"
                    stroke="#eee"
                    stroke-width="2"
                    fill="none"
                    d="M18 2.0845
                      a 15.9155 15.9155 0 0 1 0 31.831
                      a 15.9155 15.9155 0 0 1 0 -31.831" />
                  <path class="circle"
                    stroke="${color}"
                    stroke-width="2"
                    stroke-dasharray="${battery}, 100"
                    fill="none"
                    d="M18 2.0845
                      a 15.9155 15.9155 0 0 1 0 31.831
                      a 15.9155 15.9155 0 0 1 0 -31.831" />
                  ${
                    charging === "Charging"
                      ? `<text x="18" y="24" class="percentage" style="fill:yellow; font-size: 16px; text-anchor: middle; animation: blink 2s infinite;">
                          ⚡
                        </text>`
                      : `<text x="18" y="17" class="percentage" style="fill:#fff; font-size: 10px; text-anchor: middle;">${battery}</text>
                         <text x="18" y="26" class="percentage" style="fill:#fff; font-size: 8px; text-anchor: middle;">%</text>`
                  }
                </svg>
                <style>
                  @keyframes blink {
                    0% { opacity: 1; }
                    50% { opacity: 0; }
                    100% { opacity: 1; }
                  }
                </style>
              </div>`;
    ]]]
  travel: |
    [[[
      let travel_time = states['sensor.your_travel_time_sensor'].state;
      let color = "green";
      let max_time = 60;
      let percentage = Math.min((travel_time / max_time) * 100, 100);
      if (travel_time >= 40) {
        color = "red";
      } else if (travel_time >= 20) {
        color = "yellow";
      } else {
        color = "green";
      }

      return `<div style="font-size: 14px; color: var(--secondary-text-color); margin: 0; padding: 0;">
                <svg width="30" height="30" viewBox="0 0 36 36" class="circular-chart">
                  <path class="circle-bg"
                    stroke="#eee"
                    stroke-width="2"
                    fill="none"
                    d="M18 2.0845
                      a 15.9155 15.9155 0 0 1 0 31.831
                      a 15.9155 15.9155 0 0 1 0 -31.831" />
                  <path class="circle"
                    stroke="${color}"
                    stroke-width="2"
                    stroke-dasharray="${percentage}, 100"
                    fill="none"
                    d="M18 2.0845
                      a 15.9155 15.9155 0 0 1 0 31.831
                      a 15.9155 15.9155 0 0 1 0 -31.831" />
                  <text x="18" y="17" class="percentage" style="fill:#fff; font-size: 10px; text-anchor: middle;">${travel_time}</text>
                  <text x="18" y="25" class="percentage" style="fill:#fff; font-size: 6px; text-anchor: middle;">min</text>
                </svg>
              </div>`;
    ]]]

3 Likes

Very impressive. :+1:

1 Like

apologies if I’m bringing this post back from the dead, but would you have the code for your peope cards? :smiley:

I’m particularly interested in being able to change the ringer mode remotely, as my wife is notorious for misplacing her phone with it in silent mode

I’ve had the same person card for the last few years. It works but I like to freshen up a bit. In button card, is it possible to have things like battery/distance clickable? I can with my current one, so clock distance and tells you the address a person is at

Cheers

Old but not dead yet. :smiley:

And yes, you use case was my use case aswell.

I don’t know if there’s an easier way but mine works as intended:

Here’s the code for the button:

type: custom:mushroom-template-card
primary: Ringer Mode
secondary: "{{ states('sensor.YOURMOBILE_ringer_mode') }}"
icon: "{{ state_attr('sensor.YOURMOBILE_ringer_mode', 'icon') }}"
entity: sensor.dennis_pixel_ringer_mode
icon_color: |-
  {% if is_state('sensor.YOURMOBILE_ringer_mode', 'normal') %}
    green
  {% elif is_state('sensor.YOURMOBILE_ringer_mode', 'vibrate') %}
    blue
  {% elif is_state('sensor.YOURMOBILE_ringer_mode', 'silent') %} 
    yellow 
  {% endif %}
hold_action:
  action: call-service
  service: script.toggle_ringer_mode
  target: {}
tap_action:
  action: none
double_tap_action:
  action: none
layout: horizontal
fill_container: false
multiline_secondary: false

And here’s code for the script I toggle with button:

alias: "[YOU] Toggle Ringer Mode "
sequence:
  - data:
      message: command_ringer_mode
      data:
        command: >-
          {% if is_state('sensor.YOURMOBILE_ringer_mode', 'normal')  %}
          vibrate  {% elif is_state('sensor.YOURMOBILE_ringer_mode',
          'vibrate') %} silent  {% elif
          is_state('sensor.YOURMOBILE_ringer_mode', 'silent') %} normal  {%
          endif %}
    action: notify.mobile_app_YOURMOBILE
mode: single
icon: mdi:phone-ring

And here you find the full documentation:

1 Like

Thanks mate - would you be able to share your YAML for one of the entire “people” cards as well?

Haven’t updated in a while but it’s still working.There you go:

type: horizontal-stack
cards:
  - type: custom:stack-in-card
    cards:
      - type: custom:mushroom-person-card
        entity: person.ME
        use_entity_picture: true
        layout: vertical
        hold_action:
          action: navigate
          navigation_path: ME
        hide_name: true
        hide_state: true
        fill_container: true
        card_mod:
          style: |
            ha-card {
              --ha-card-border-width: 0;
            }
      - type: custom:mushroom-chips-card
        chips:
          - type: template
            content: |-
              {% if is_state('person.ME', 'home') %}
                zuhause
              {% elif is_state('person.ME', 'not_home') %}
                unterwegs
              {% else %}
                {{ states('person.ME') }}
              {% endif %}
            card_mod:
              style: |
                ha-card {
                  --chip-box-shadow: 0px 0px;
                  --text-color: #a6a6a6;
                  --chip-font-size: 12px;
                  --chip-border-width: 0
                }
        alignment: center
  - type: custom:stack-in-card
    cards:
      - type: custom:mushroom-person-card
        entity: person.HER
        use_entity_picture: true
        layout: vertical
        hold_action:
          action: navigate
          navigation_path: HER
        hide_name: true
        hide_state: true
        fill_container: true
        card_mod:
          style: |
            ha-card {
              --ha-card-border-width: 0;
            }
      - type: custom:mushroom-chips-card
        chips:
          - type: template
            content: |-
              {% if is_state('person.HER', 'home') %}
                zuhause
              {% elif is_state('person.HER', 'not_home') %}
                unterwegs
              {% else %}
                {{ states('person.HER') }}
              {% endif %}
            card_mod:
              style: |
                ha-card {
                  --chip-box-shadow: 0px 0px;
                  --text-color: #a6a6a6;
                  --chip-font-size: 12px;
                  --chip-border-width: 0
                }
        alignment: center

And this is the target for my personal card:

  - theme: Google Theme
    title: ME
    path: ME
    icon: ''
    badges: []
    cards:
      - type: custom:mushroom-chips-card
        chips:
          - type: back
      - type: custom:mushroom-template-card
        secondary: >-
          {{ state_attr('sensor.ME_geocoded_location', 'thoroughfare')
          }}  {{ state_attr('sensor.ME_geocoded_location',
          'sub_thoroughfare') }}, {{
          state_attr('sensor.ME_geocoded_location', 'locality')
          }}     
        entity: sensor.ME_geocoded_location
        primary: Wo bin ich?
        icon: mdi:map-marker
        layout: horizontal
        multiline_secondary: false
        tap_action:
          action: more-info
        hold_action:
          action: navigate
          navigation_path: /map
      - type: custom:mushroom-title-card
        subtitle: Bedienung
      - type: gauge
        entity: sensor.ME_battery_level
        name: Akku
        theme: Google Theme
        severity:
          green: 75
          yellow: 25
          red: 0
        needle: true
      - type: horizontal-stack
        cards:
          - type: custom:mushroom-entity-card
            entity: binary_sensor.ME_is_charging
            name: Ladestatus
            icon_color: amber
      - type: custom:mushroom-template-card
        primary: Ringer Mode
        secondary: '{{ states(''sensor.ME_ringer_mode'') }}'
        icon: '{{ state_attr(''sensor.ME_ringer_mode'', ''icon'') }}'
        entity: sensor.ME_ringer_mode
        icon_color: |-
          {% if is_state('sensor.ME_ringer_mode', 'normal') %}
            green
          {% elif is_state('sensor.ME_ringer_mode', 'vibrate') %}
            blue
          {% elif is_state('sensor.ME_ringer_mode', 'silent') %} 
            yellow 
          {% endif %}
        hold_action:
          action: call-service
          service: script.toggle_ringer_mode
          target: {}
        tap_action:
          action: none
        double_tap_action:
          action: none
        layout: horizontal
        fill_container: false
        multiline_secondary: false
      - type: custom:mushroom-entity-card
        tap_action:
          action: call-service
          service: script.find_my_phone
          service_data: {}
          target: {}
        icon: mdi:cellphone-arrow-down-variant
        hide_icon: false
        entity: script.find_my_phone
        name: Wo ist mein Handy?
        icon_color: green
        layout: horizontal
        secondary_info: none
1 Like

many thanks - gives me something to work towards :smiley:

1 Like

I’ve been working on mine, with modifying some that I found here and there.

Most of it works great, but when I start spotify, I have to do a page refresh before it will start the marquee and change the picture to the headphones.

HA-Personcard-normal
HA-Personcard-headphones

type: custom:button-card
entity: person.<USERNAME>
device: <DEVICENAME>
variables:
  phone_battery_level_sensor: "[[[ return states[\"sensor.<DEVICENAME>_battery_level\"].state]]]"
  phone_battery_state_sensor: "[[[ return states[\"sensor.<DEVICENAME>_battery_state\"].state]]]"
  phone_wifi_sensor: "[[[ return states[\"sensor.<DEVICENAME>_wifi_connection\"].state]]]"
  proximity_sensor: "[[[ return states[\"sensor.<DEVICENAME>_geocoded_location\"].state]]]"
  avatar_normal: /local/<USER>-Smile.png
  avatar_headphones: /local/<USER>-Smile+headphones.png
  media_player_spotify: media_player.spotify_<SPOTIFYACCOUNT>
aspect_ratio: 2/1
name: Person
show_entity_picture: false
show_name: false
hold_action:
  action: none
state:
  - value: home
    styles:
      card:
        - background-color: "#202124"
      custom_fields:
        icon:
          - border-color: "#77c66e"
  - value: not_home
    styles:
      card:
        - background-color: "#202124"
      custom_fields:
        icon:
          - border-color: deepskyblue
  - value: Pizzeria
    styles:
      card:
        - background-color: "#202124"
      custom_fields:
        icon:
          - border-color: "#B83838"
styles:
  card:
    - background-color: "#202124"
    - border-radius: 5%
    - padding: 2% 5% 5% 5%
    - color: gray
    - font-size: 12px
    - text-shadow: 0px 0px 0px black
    - text-transform: capitalize
    - justify-self: center
    - align-content: center
  grid:
    - grid-template-areas: "\"icon status\" \"icon battery\" \"icon wifi\" \"icon proximity\""
    - grid-template-columns: 1.5fr 1fr
    - grid-template-rows: 1fr 1fr 1fr 1fr
    - align-content: center
  name:
    - font-size: 15px
    - align-self: middle
    - justify-self: start
  custom_fields:
    icon:
      - clip-path: circle()
      - width: 80%
      - pointer-events: none
      - display: grid
      - border: 5px solid
      - border-color: "#808080"
      - border-radius: 500px
      - margin: 0 0 0 0
      - opacity: 1
      - align-self: center
      - justify-self: center
    status:
      - justify-self: start
      - color: gray
    proximity:
      - align-self: middle
      - justify-self: start
      - color: gray
    wifi:
      - justify-self: start
      - color: gray
      - "--text-wifi-color-sensor": >-
          [[[ if (variables.phone_wifi_sensor == '<not connected>') return
          "#aaaaaa"; ]]]
    battery:
      - align-self: middle
      - justify-self: start
      - color: gray
      - "--text-color-sensor": >-
          [[[ if (variables.phone_battery_level_sensor < 35) return "#EF4F1A";
          ]]]
    media:
      - align-self: right
      - position: absolute
      - bottom: 4%
      - right: 4%
      - left: 4%
      - color: >-
          [[[if (states[variables.media_player_spotify].state == "playing")
          return "#000000"; else return "#ffffff00"; ]]]          
custom_fields:
  icon: >
    [[[ 
     if (states[variables.media_player_spotify].state =='playing') 
     {
    return entity === undefined ? null : `<img
    src=${variables.avatar_headphones} width="100%">`; } else { return entity
    === undefined ? null : `<img
    src="${states[entity.entity_id].attributes.entity_picture}" width="100%">`;
    } ]]]
  status: |
    [[[
      if (entity.state =='not_home') { 
      return `<ha-icon icon="mdi:home-export-outline"
        style="width: 20px; height: 20px; color: '#888888';">
        </ha-icon><span> On the way</span>`;
      } 
      if (entity.state =='home') { 
      return `<ha-icon 
        icon="mdi:home"
        style="width: 20px; height: 20px; color: 888888;">
        </ha-icon><span> ${entity.state}</span>`;
      } else { 
        if (entity.state =='[YOUR OTHER LOCATION]') { 
        return `<ha-icon icon="mdi:pizza"
          style="width: 20px; height: 20px; color: '#888888';">
          </ha-icon><span> ${entity.state}</span>`;
        }
        else{
        return `<ha-icon 
          icon="mdi:map-marker-radius"
          style="width: 20px; height: 20px; color: 888888;">
          </ha-icon><span> ${entity.state}</span>`;
        }
      }
    ]]]
  battery: |
    [[[
      if (variables.phone_battery_state_sensor =='charging') { 
        return `<ha-icon
        icon="mdi:battery-charging-medium"
        style="width: 20px; height: 20px; color: #888888;">
        </ha-icon> <span><span style="color: var(--text-color-sensor);">${variables.phone_battery_level_sensor}% Battery</span></span>`;
      } else {
        return `<ha-icon
        icon="mdi:battery-medium"
        style="width: 20px; height: 20px; color: #888888;">
        </ha-icon> <span><span style="color: var(--text-color-sensor);">${variables.phone_battery_level_sensor}% Battery</span></span>`;
      }
    ]]]
  wifi: |
    [[[
      if (variables.phone_wifi_sensor.state =='<not connected>') { 
        return `<ha-icon
        icon="mdi:wifi"
        style="width: 20px; height: 20px; color: var(--text-wifi-color-sensor);">
        </ha-icon> <span><span style="color: var(--text-wifi-color-sensor);">Disconnected</span></span>`; 
      } else {
        return `<ha-icon
        icon="mdi:wifi"
        style="width: 20px; height: 20px; color: #888888;">
        </ha-icon> <span><span style="color: var(--text-color-sensor);">${variables.phone_wifi_sensor}</span></span>`;
      }
    ]]]
  media: |
    [[[ 
     if (states[variables.media_player_spotify].state =='playing') { 
      return `<marquee> <ha-icon
        icon="mdi:music"
        style="width: 20px; height: 20px;"></ha-icon
        <span> ${states[variables.media_player_spotify].attributes.media_title}
        - ${states[variables.media_player_spotify].attributes.media_artist} </marquee>`
    } else {
      
    }
    ]]]

For some reason, when it changes the picture to the headphones one, it is a different size. Any suggestions?

probably a resolution thing, did you check the 2 pictures to be of exact same proportions?

as for the marquee

did you check the empty ‘else’ here? thats never a good thing.

you could try and set the triggers_update entity list to include that player entity explicitly

btw, marquee tag is long deprecated and support may vary among browser. advice is to move to Javascript translateX() see translateX() - CSS: Cascading Style Sheets | MDN

1 Like

@Mariusthvdb thanks for the info, I’ll take a look into that.