4 entités tassées dans une ligne de faible hauteur

Bonjour,

Mon problème

Je cherche à afficher côte à côte, sur une ligne de petite hauteur, 4 informations dans une section de largeur 12 (pour être lisible sur un téléphone) :

  • une icône : icône et couleur personnalisées par un template. Dans l'idéal, un clic dessus renvoie vers l'entité volet
  • un input_datetime : affiché sous la forme "24/07 07:32". La couleur du texte doit changer selon un template (date dépassée, switch activé ou non, ...). Je dois pouvoir changer la date et l'heure rapidement en cliquant dessus.
  • un input_boolean : qui bascule son état quand j'appuie dessus (NB : je préfère la forme du swithc, mais si c'est une icône qui change de forme et/ou de couleur cela peut convenir)
  • un input_select : sans son icône pour gagner de la place, qui affiche la dropdown list dès que l'on clique dessus (je ne veux pas qu'un clic affiche l'entité puis de nouveau cliquer pour choisir, je veux pouvoir choisir dès le premier clic).

J'ai réussi à faire ceci, stacké dans des vertical-stack-in-card, mais pas joli et trop haut :
Ligne actuelle

Et dans l'idéal, je créé une streamline_card pour faire 1 ligne, je les stacke dans une verticale stack-in-card par 4 ou 16 (je ne me suis pas encore décidé) car j'ai 64 lignes à mettre en prenant le moins de place possible (affichage smartphone) pour faire comme cette image :

Ma configuration

HA 2026.3.4, mais mise à jour vers la dernière version prévue prochainement si cela me bloque

J'ai pour le moment les modules HACS suivants, mais je n'ai pas réussi à faire ce que je voulais avec :

  • Mushroom template
  • card-mod (compliqué de trouver le code à mettre, et sur 64 lignes ça rame un peu)
  • vertical-stack-in-card + stack-in-card
  • streamline-card
  • multiple-entity-row (je l'ai rajoutée par rapport à mon premier message)
  • button-card (je ne maîtrise pas du tout...)
  • je suis ouvert à l'installation d'autres modules :+1:

Je sais écrire sans soucis les template (d'une mushroom-template card par exemple), les streamline card, faire quelques choses (avec difficulté tout de même) avec card-mod ... mais je n'arrive pas à tasser en largeur et réduire la hauteur comme je veux.

Merci d'avance :slight_smile:

Bonjour, je te conseil de regarder cette intégration qui fait exactement ça : custom:multiple-entity-row

Je l'utilise aussi, j'avais oublié de le mentionner.
J'avais déjà essayé, mais malheureusement multiple-entity-row ne me permet pas d'afficher directement la liste déroulante du input_select, le seul "truc" direct que l'on peut mettre est un toggle (ce qui m'est aussi pratique).
J'ai essayé de le mettre comme l'entité principale, ou comme une entité au milieu mais même résultat. Et je ne parviens pas à afficher un template dans la partie gauche à la place du nom de l'entité (mais je viens de voir que c'était limité à la version bêta que je n'ai pas, car il faut HA 2026.4, je vais devoir m'occuper de la mise à jour de HA...) :

Tu peux, je pense faire la même chose avec tes cartes natives et les mushroom card (mushroom-chips-card et mushroom-select-card). Tu mets le tout dans une horizontal-stack. Et après tu appliques un style avec uix.

image

Ceci dit je pense que tu peux peut-être juste appliquer uix a ton code fait pour la vertical-stack-in-card ça peut peut-être marcher.

Bonjour,
Peux-tu mettre le code d'une ligne que tu as déjà faite ?
Je peux voir avec button-card.

J'ai fait des cartes comme telle en button-card (100% button-card):

le plus chiant c'est un menu déroulant (dropdown) sous button-card, pas possible, mais passer par une carte entittes.

Il va falloir que je passe du temps à apprendre ces button-card, c'est complexe mais on peut visiblement tout faire !
Voici mon code pour 2 lignes (identiques):

type: entities
entities:
  - type: custom:multiple-entity-row
    entity: input_select.mode_simu_voletroulantwc_1
    name: false
    secondary_info: "{{ states('input_datetime.heure_simu_voletroulantwc_1') }}"
    entities:
      - entity: input_boolean.force_simu_voletroulantwc_1
        name: false
        toggle: true
    card_mod:
      style:
        hui-generic-entity-row:
          $: |
            state-badge {
              {% set entity = 'cover.voletroulantwc' %}
              {% if states(entity) == 'opening' or states(entity) == 'closing' %}
                color: orange;
              {% elif states(entity) == 'open' %}
                color: green;
              {% elif states(entity) == 'closed' %}
                color: red;
              {% else %}
                color: orange;
              {% endif %}
            }
            .info {
              {% if is_state('input_boolean.force_simu_voletroulantwc_1','on') %}
                color: var(--primary-color);
              {% endif %}
            }
          .: |
            .state.entity {
              {% if as_timestamp(now()) > as_timestamp(states(config.entity)) %}
                color: red;
              {% elif is_state('input_boolean.force_simu_voletroulantwc_1','on') %}
                color: var(--primary-color);
              {% endif %}
            }
  - type: custom:multiple-entity-row
    entity: input_select.mode_simu_voletroulantwc_1
    name: false
    secondary_info: "{{ states('input_datetime.heure_simu_voletroulantwc_1') }}"
    entities:
      - entity: input_boolean.force_simu_voletroulantwc_1
        name: false
        toggle: true
    card_mod:
      style:
        hui-generic-entity-row:
          $: |
            state-badge {
              {% set entity = 'cover.voletroulantwc' %}
              {% if states(entity) == 'opening' or states(entity) == 'closing' %}
                color: orange;
              {% elif states(entity) == 'open' %}
                color: green;
              {% elif states(entity) == 'closed' %}
                color: red;
              {% else %}
                color: orange;
              {% endif %}
            }
            .info {
              {% if is_state('input_boolean.force_simu_voletroulantwc_1','on') %}
                color: var(--primary-color);
              {% endif %}
            }
          .: |
            .state.entity {
              {% if as_timestamp(now()) > as_timestamp(states(config.entity)) %}
                color: red;
              {% elif is_state('input_boolean.force_simu_voletroulantwc_1','on') %}
                color: var(--primary-color);
              {% endif %}
            }

Je vais regarder comment formatter tout ça avec les mushroom cards (voici les solutions trouvées si ça peut aider quelqu'un) :

  • date au format jj/mm hh:mm --> trouvé en utilisant une mushroom-chip-template
  • input_boolean en forme de switch --> pareil, mushromm-chip-template
  • retirer le nom et l'icone de la carte select : ajouter
primary_info: none
secondary_info: none
icon_type: none

ce qui donne (mon icone est coupée à droite et en bas :thinking:) :
image

Je vais creuser aussi uix, je ne connais pas mais ça semble être un card-mod boosté ?

UIX est une personnalisation d'interface que tu peux (en théorie) appliquer à toutes tes cartes. Tu peux même créer des "style" que tu appels dans ton code yaml partout où tu veux. C'est de la personnalisation CSS. C'est du "standard". Ce que tu apprends sur UIX pourras être utilisé ailleurs.

Boutton card est une carte interactive. Tu peux faire des cartes complexes, ton code sera limité à la carte elle-même.

En termes de performance, chaque carte boutton card appelle son propre code, ce qui peut alourdir la charge. D'un autre côté si tu mets des UIX de style différent sur toutes tes cartes, c'est pareil. Par contre, si tu mets le même thème partout avec peu de modifications, c'est plus léger.

Je cherche à ne pas utiliser des cartes HACS dans une optique de maintenabilité et de cohérence entre les dashboard. J'ai les mushroom et ça me suffit.

Tu peux utiliser Claude IA (en gratuit, t'as de quoi faire) pour t'aider à faire une carte button-card.

@juu,
a tester, j'ai pas d'entité adéquate chez moi pour faire un test. dit moi ce qui va ou pas, pour adapter ,modifier la carte.
image

type: custom:button-card
entity: cover.voletroulantwc
show_name: false
show_state: false
show_label: false
show_icon: true
tap_action:
  action: none
hold_action:
  action: none

custom_fields:
  time: |
    [[[ return states['input_datetime.heure_simu_voletroulantwc_1'].state ]]]

  toggle: |
    [[[
      const on = states['input_boolean.force_simu_voletroulantwc_1'].state === 'on';
      return `<ha-switch ${on ? 'checked' : ''}
        onclick="event.stopPropagation();
                 hass.callService('input_boolean','toggle',{entity_id:'input_boolean.force_simu_voletroulantwc_1'})">
      </ha-switch>`;
    ]]]

  dropdown: |
    [[[
      if (!this._card) {
        this._card = document.createElement('hui-entities-card');
        this._card.setConfig({
          entities: [
            { entity: 'input_select.mode_simu_voletroulantwc_1', name: false }
          ],
          show_header_toggle: false,
          card_mod: {
            style: `
              ha-card { box-shadow:none; border:none; padding:0; margin:0; background:none; }
              state-badge { display:none; }
              hui-generic-entity-row { padding:0 !important; min-height:0 !important; }
            `
          }
        });
      }
      this._card.hass = hass;
      return this._card;
    ]]]

styles:
  card:
    - padding: 4px 10px
    - height: 48px
    - border-radius: 12px
    - display: flex
    - align-items: center
    - overflow: visible      

  grid:
    - grid-template-areas: '"i time toggle dropdown"'
    - grid-template-columns: 22px 62px 44px 1fr
    - grid-template-rows: 1fr
    - align-items: center
    - column-gap: 6px

  icon:
    - width: 20px
    - color: |
        [[[
          const s = states['cover.voletroulantwc'].state;
          if (['opening','closing'].includes(s)) return 'orange';
          if (s === 'open') return 'green';
          if (s === 'closed') return 'red';
          return 'orange';
        ]]]

  custom_fields:
    time:
      - font-size: 12px
      - justify-self: start
    toggle:
      - justify-self: center
    dropdown:
      - width: 100%
      - justify-self: stretch

Bizzare, ma date n'est pas formattée comme toi, et le dropdown list ne fonctionne pas sur l'ordi (je suis sous Chrome) ni le téléphone :
image

J'ai button-card 7.0.1

Et j'ai aussi besoin de personnaliser l'icone selon un template (si tu peux juste me faire un exemple simple, j'adapterai ensuite par moi-même) :

      {% if states('cover.voletroulantwc') == 'opening' %}
        mdi:arrow-up-box
      {% elif states('cover.voletroulantwc') == 'closing' %}
        mdi:arrow-down-box
      {% elif state_attr('cover.voletroulantwc', 'current_position') == 0 %}
        {% if state_attr('cover.voletroulantwc', 'current_position') == 100 - states('number.voletroulantwc_my_position')|int %}
          local:window-shutter-myposition
        {% else %}
          mdi:window-shutter
        {% endif %}
      {% elif state_attr('cover.voletroulantwc', 'current_position') > 0 and state_attr('cover.voletroulantwc', 'current_position') < 50 %}
        {% if state_attr('cover.voletroulantwc', 'current_position') == 100 - states('number.voletroulantwc_my_position')|int %}
          local:window-shutter-intermediate1-myposition
        {% else %}
          local:window-shutter-intermediate1
        {% endif %}
      {% elif state_attr('cover.voletroulantwc', 'current_position') >= 50 and state_attr('cover.voletroulantwc', 'current_position') < 100 %}
        {% if state_attr('cover.voletroulantwc', 'current_position') == 100 - states('number.voletroulantwc_my_position')|int %}
          local:window-shutter-intermediate2-myposition
        {% else %}
          local:window-shutter-intermediate2
        {% endif %}
      {% elif state_attr('cover.voletroulantwc', 'current_position') == 100 %}
        {% if state_attr('cover.voletroulantwc', 'current_position') == 100 - states('number.voletroulantwc_my_position')|int %}
          local:window-shutter-open-myposition
        {% else %}
          mdi:window-shutter-open
        {% endif %}
      {% else %}
        mdi:window-shutter-alert
      {% endif %}

J'aurai également aimé qu'un clic sur la date ouvre l'entité date ou me permette de la modifier facilement

(désolé pour cette longue liste)

l'image, est celle fournie par l'IA pour que j'aie un aperçu. :grin:
a tester :

type: custom:button-card
entity: cover.voletroulantwc
show_name: false
show_state: false
show_label: false
show_icon: true
tap_action:
  action: none
hold_action:
  action: none

custom_fields:
  time: |
    [[[
      const raw = states['input_datetime.heure_simu_voletroulantwc_1'].state;
      const d = new Date(raw.replace(' ', 'T'));
      const pad = (n) => String(n).padStart(2, '0');
      const jour = pad(d.getDate());
      const mois = pad(d.getMonth() + 1);
      const h = pad(d.getHours());
      const m = pad(d.getMinutes());
      const s = pad(d.getSeconds());
      return `${jour}/${mois} ${h}:${m}:${s}`;
    ]]]

  toggle: |
    [[[
      const on = states['input_boolean.force_simu_voletroulantwc_1'].state === 'on';
      return `<ha-switch ${on ? 'checked' : ''}
        onclick="event.stopPropagation();
                 hass.callService('input_boolean','toggle',{entity_id:'input_boolean.force_simu_voletroulantwc_1'})">
      </ha-switch>`;
    ]]]

  dropdown:
    card:
      type: entities
      show_header_toggle: false
      entities:
        - entity: input_select.mode_simu_voletroulantwc_1
          name: false
      card_mod:
        style: |
          ha-card { box-shadow:none; border:none; padding:0; margin:0; background:none; }
          state-badge { display:none; }
          hui-generic-entity-row { padding:0 !important; min-height:0 !important; }

styles:
  card:
    - padding: 4px 10px
    - height: 48px
    - border-radius: 12px
    - display: flex
    - align-items: center
    - overflow: visible

  grid:
    - grid-template-areas: '"i time toggle dropdown"'
    - grid-template-columns: 22px 92px 44px 1fr
    - grid-template-rows: 1fr
    - align-items: center
    - column-gap: 6px

  icon:
    - width: 20px
    - color: |
        [[[
          const s = states['cover.voletroulantwc'].state;
          if (['opening','closing'].includes(s)) return 'orange';
          if (s === 'open') return 'green';
          if (s === 'closed') return 'red';
          return 'orange';
        ]]]

  custom_fields:
    time:
      - font-size: 11px
      - justify-self: start
      - white-space: nowrap
    toggle:
      - justify-self: center
    dropdown:
      - width: 100%
      - justify-self: stretch

si tu veux changer la taille de la police pour la date, ajoute font-size

  custom_fields:
    time:
      - font-size: 11px
      - justify-self: start
      - white-space: nowrap

tu peux jouer sur la hauteur et le padding (marge intérieure, les valeurs sont pour vertical 4px et horizontal 10px) ici:

styles:
  card:
    - padding: 4px 10px
    - height: 48px

si besoin, un bon tuto pour button-card :

Hello,

custom:button-card c'est une dinguerie :smiley:

Il y a quoi dans l'input select? c'est pas plus simple de mettre plusieurs boutons côte à côte ? et ça prendra sans doute pas plus de place, sauf si il y a 50 trucs dans l'input :slight_smile:

cdt

Oui, pas bête des bouttons a la place du dropdown, ca ferais une carte 100% button-card. sans entities card et card_mod.

Re,

Oui tout à fait et le template on peut aussi le faire en button, du coup on peut dégager streamline :slight_smile: jsuis en plein dedans, et pour avoir testé les cardmod passés dans des streamilne, à forte dose, c'est plus réactif de passer par les template button de toute façon :slight_smile:

cdt

@juu , j'avais pas vu ta demande pour l'icone :

type: custom:button-card
entity: cover.voletroulantwc
show_name: false
show_state: false
show_label: false
show_icon: true
tap_action:
  action: none
hold_action:
  action: none

icon: |
  [[[
    const c = states['cover.voletroulantwc'];
    const pos = c.attributes.current_position;
    const myPos = 100 - parseInt(states['number.voletroulantwc_my_position'].state);

    if (c.state === 'opening') return 'mdi:arrow-up-box';
    if (c.state === 'closing') return 'mdi:arrow-down-box';

    if (pos === 0) {
      return pos === myPos ? 'local:window-shutter-myposition' : 'mdi:window-shutter';
    }
    if (pos > 0 && pos < 50) {
      return pos === myPos ? 'local:window-shutter-intermediate1-myposition' : 'local:window-shutter-intermediate1';
    }
    if (pos >= 50 && pos < 100) {
      return pos === myPos ? 'local:window-shutter-intermediate2-myposition' : 'local:window-shutter-intermediate2';
    }
    if (pos === 100) {
      return pos === myPos ? 'local:window-shutter-open-myposition' : 'mdi:window-shutter-open';
    }
    return 'mdi:window-shutter-alert';
  ]]]

custom_fields:
  time: |
    [[[
      const raw = states['input_datetime.heure_simu_voletroulantwc_1'].state;
      const d = new Date(raw.replace(' ', 'T'));
      const pad = (n) => String(n).padStart(2, '0');
      const jour = pad(d.getDate());
      const mois = pad(d.getMonth() + 1);
      const h = pad(d.getHours());
      const m = pad(d.getMinutes());
      const s = pad(d.getSeconds());
      return `${jour}/${mois} ${h}:${m}:${s}`;
    ]]]

  toggle: |
    [[[
      const on = states['input_boolean.force_simu_voletroulantwc_1'].state === 'on';
      return `<ha-switch ${on ? 'checked' : ''}
        onclick="event.stopPropagation();
                 hass.callService('input_boolean','toggle',{entity_id:'input_boolean.force_simu_voletroulantwc_1'})">
      </ha-switch>`;
    ]]]

  dropdown:
    card:
      type: entities
      show_header_toggle: false
      entities:
        - entity: input_select.mode_simu_voletroulantwc_1
          name: false
      card_mod:
        style: |
          ha-card { box-shadow:none; border:none; padding:0; margin:0; background:none; }
          state-badge { display:none; }
          hui-generic-entity-row { padding:0 !important; min-height:0 !important; }

styles:
  card:
    - padding: 4px 10px
    - height: 48px
    - border-radius: 12px
    - display: flex
    - align-items: center
    - overflow: visible

  grid:
    - grid-template-areas: '"i time toggle dropdown"'
    - grid-template-columns: 22px 92px 44px 1fr
    - grid-template-rows: 1fr
    - align-items: center
    - column-gap: 6px

  icon:
    - width: 20px

  custom_fields:
    time:
      - font-size: 11px
      - justify-self: start
      - white-space: nowrap
    toggle:
      - justify-self: center
    dropdown:
      - width: 100%
      - justify-self: stretch

je connais pas ce genre d'icone local:.

Voici ce que donne ton code :
image

Le problème de la carte entities pour le input_select est qu'elle est très haute, et on ne peut pas retirer le nom ni l'icône. Du coup je l'ai remplacée par une mushroom-select, c'est largement mieux.
Il reste quelques marges que je peaufinerai plus tard (retirer le blanc au-dessus du dropdown pour gagner de la place mais je n'y arrive pas tout de suite :laughing:).
J'ai vaguement trouvé comment personnaliser l'icône, il faut maintenant que j'intègre toutes mes possibilités mais je ne maîtrise pas trop ces template-là.
image

Je ne peux pas mettre de boutons : j'ai 7 choix (pour le moment) dans ma dropdown, et je veux que ces choix restent flexibles (ils sont paramétrés dans mon config.yaml donc rapidement changeables). Et toutes mes automatisations et scripts sont déjà écrits selon ce dropdown, donc je n'y touche plus maintenant :sweat_smile:

J'avais oublié que button gérait les template, j'en avais fait il y a longtemps... Je ferai ça au lieu de mettre des streamline-card.

Ça rend bien au final. Oui le code doit surement être peaufiné à ton goût et aux erreurs de l'iA .
Empiler dans un vertical-stack-in-card ou stack-in-card pour plusieurs volets doit être pas mal.

En diminuant le padding en vertical et horizontal, et en baissant la hauteur (height), tu dois pouvoir faire plus compact encore.
pour le padding avec une taille pas cotée.

/* haut | droit | bas | gauche */
padding: 2px 2px 2px 2px

Re,

C'est surtout la multiplication des cartes qui fait que ça devient compliqué. custom:button-card c'est certes copieux en code mais ça permet de quasi tout faire, je dis quasi parce que je n'en ai pas encore fait le tour :slight_smile:

cdt

C'est ce que je cherchais à faire (à quelques marges près) en m'arrachant les cheveux, ça rendra bien quand j'aurai peaufiné.

Les icones local: c'est juste une tuerie, c'est custom_icons qui permet de mettre toutes les icônes de iconify et si ça ne suffit pas on créer ses propres icônes :heart_eyes: et on les insère avec local:

J'avais édité mon message pour les icônes, normal que tu n'ai pas vu.
Par contre mettre une icône personnalisée fait bugger. Les 2 lignes ont exactement le même code, la 1ère ligne a une icône mdi et la 2ème ligne une icône local:. J'ai peut-être un problème lors de la création de mes icônes, je vais creuser, bien que mes icônes perso fonctionnent parfaitement ailleurs (mushroom-templates principalement).

Je viens de m'apercevoir que appuyer sur le toggle change l'icône du toggle, mais ne change pas l'état de l'entité input_boolean.force_simu_voletroulantwc_1 (et en plus cette entité finira dans le template)

type: custom:button-card
entity: cover.voletroulantwc
show_name: false
show_state: false
show_label: false
show_icon: true
tap_action:
  action: none
hold_action:
  action: none
icon: |
  [[[
    const c = states['cover.voletroulantwc'];
    const pos = c.attributes.current_position;
    const myPos = 100 - parseInt(states['number.voletroulantwc_my_position'].state);

    if (c.state === 'opening') return 'mdi:arrow-up-box';
    if (c.state === 'closing') return 'mdi:arrow-down-box';

    if (pos === 0) {
      return pos === myPos ? 'local:window-shutter-myposition' : 'mdi:window-shutter';
    }
    if (pos > 0 && pos < 50) {
      return pos === myPos ? 'local:window-shutter-intermediate1-myposition' : 'local:window-shutter-intermediate1';
    }
    if (pos >= 50 && pos < 100) {
      return pos === myPos ? 'local:window-shutter-intermediate2-myposition' : 'local:window-shutter-intermediate2';
    }
    if (pos === 100) {
      return pos === myPos ? 'local:window-shutter-open-myposition' : 'mdi:window-shutter-open';
    }
    return 'mdi:window-shutter-alert';
  ]]]
custom_fields:
  time: |
    [[[
      const raw = states['input_datetime.heure_simu_voletroulantwc_1'].state;
      const d = new Date(raw.replace(' ', 'T'));
      const pad = (n) => String(n).padStart(2, '0');
      const jour = pad(d.getDate());
      const mois = pad(d.getMonth() + 1);
      const h = pad(d.getHours());
      const m = pad(d.getMinutes());
      const s = pad(d.getSeconds());
      return `${jour}/${mois} ${h}:${m}:${s}`;
    ]]]
  toggle: |
    [[[
      const on = states['input_boolean.force_simu_voletroulantwc_1'].state === 'on';
      return `<ha-switch ${on ? 'checked' : ''}
        onclick="event.stopPropagation();
                 hass.callService('input_boolean','toggle',{entity_id:'input_boolean.force_simu_voletroulantwc_1'})">
      </ha-switch>`;
    ]]]
  dropdown:
    card:
      type: custom:mushroom-select-card
      entity: input_select.mode_simu_voletroulantwc_1
      primary_info: none
      secondary_info: none
      icon_type: none
      hold_action:
        action: more-info
      double_tap_action:
        action: none
      card_mod:
        style: >
          ha-card { box-shadow:none; border:none; padding:0; margin:0;
          background:none; }

          state-badge { display:none; }

          hui-generic-entity-row { padding:0 !important; min-height:0
          !important; }
styles:
  card:
    - padding: 0px 10px
    - height: 50px
    - border-radius: 12px
    - display: flex
    - align-items: center
    - overflow: visible
  grid:
    - grid-template-areas: "\"i time toggle dropdown\""
    - grid-template-columns: 22px 92px 44px 1fr
    - grid-template-rows: 1fr
    - align-items: center
    - column-gap: 6px
  icon:
    - width: 20px
    - color: |
        [[[
          const s = states['cover.voletroulantwc'].state;
          if (['opening','closing'].includes(s)) return 'orange';
          if (s === 'open') return 'green';
          if (s === 'closed') return 'red';
          return 'orange';
        ]]]
  custom_fields:
    time:
      - font-size: 11px
      - justify-self: start
      - white-space: nowrap
    toggle:
      - justify-self: center
    dropdown:
      - width: 100%
      - justify-self: stretch