Loading...
E.G.

Custom Cards in Home Assistant: Mushroom und button-card

30. September 2026
Inhaltsverzeichnis

Mit den Bordmitteln von Home Assistant steht ein brauchbares Dashboard an einem Nachmittag. Das habe ich in fünf Aufbaustufen beschrieben und eine Weile so gelassen. Was die Bordmittel nicht hergeben, ist Kontrolle über die Typografie, darüber, wie eine Zahl gezeichnet wird, oder über einen Ring, der einen Batteriestand zeigt. Dafür braucht es Custom Cards, und in diesem Post geht es um die vier, bei denen ich gelandet bin, um das Theme, das sie zusammenhält, und um die vier Dinge, die beim Bauen kaputt waren.

Alles hier läuft auf einer Demo-Instanz auf einer kleinen Hetzner-VM mit simulierten Sensoren, derselben, auf der der Theme-Vergleich aufgenommen wurde. Das vollständige YAML des fertigen Dashboards steht im Vorlagen-Post. Dieser Post erklärt die Teile davon, die man der Datei nicht ansieht.

Vier Cards, ohne HACS geladen

Die vier Custom Cards sind card-mod (CSS in beliebige Karten einschleusen), Mushroom (die kompakten Kachelkarten), mini-graph-card (die Liniendiagramme) und button-card (eine Karte, die rendert, was man ihr als JavaScript gibt). Auf einer normalen Installation holt man sie über HACS, und HACS registriert die Ressourcen. Auf der Demo-VM wollte ich das Setup allein aus dem Repository reproduzierbar haben, deshalb liegen die vier Bundles unter www/cards und werden aus der configuration.yaml geladen:

frontend:
  themes: !include_dir_merge_named themes
  extra_module_url:
    - /local/cards/card-mod.js
    - /local/cards/mushroom.js
    - /local/cards/mini-graph-card-bundle.js
    - /local/cards/button-card.js
    - /local/ha-fonts.js

Der fünfte Eintrag ist keine Karte. Es ist der Schriftlader, und den gibt es wegen der ersten Sache, die sich als unmöglich herausstellte.

Themes können keine Schriften deklarieren

Die Gestaltungsrichtung für dieses Dashboard war eine Instrumententafel: Beschriftungen klein, versal und weit gesperrt in Archivo, Werte groß und ruhig in IBM Plex Mono mit Tabellenziffern. Tabellenziffern sind wichtiger, als es klingt. Mit Proportionalziffern ändert ein Wert wie 2.449 W bei jedem Ziffernwechsel seine Breite, und die ganze Kachel zuckt einmal pro Minute. Im Screenshot ist das unsichtbar. Im Betrieb ist es der Unterschied zwischen einer Anzeige und einem Zappeln.

Ein Home-Assistant-Theme kann nur CSS-Variablen setzen. Eine @font-face-Regel kann es nicht deklarieren, die Schriften müssen also von woanders kommen. Ein winziges ES-Modul, über extra_module_url geladen, hängt die Regeln in den Document-Head, und weil @font-face dokumentweit gilt, wirkt es auch in den Shadow-Roots der Karten:

// www/ha-fonts.js
const CSS = `
@font-face {
  font-family: 'Archivo';
  src: url('/local/fonts/archivo-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('/local/fonts/plexmono-500.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}`;
const style = document.createElement("style");
style.textContent = CSS;
document.head.appendChild(style);

Die Schriftdateien werden von /local/fonts auf derselben Maschine ausgeliefert, 76 KB für beide Familien. Kein Request an fonts.googleapis.com. Für einen Blog über selbst betriebene Software wäre die Schrift von Google eine seltsame Ausnahme, und so rendert das Dashboard auch ohne Internet identisch. Eine kleine Überraschung dabei: Googles Font-API lieferte für alle vier angefragten Archivo-Schnitte dieselbe Variable-Font-Datei. Vier Downloads, eine Datei, 35 KB. Erst die Prüfsummen haben es gezeigt.

Mushroom-Template-Cards als Wertekacheln

Die drei Kacheln unter dem Energiediagramm, Verbrauch, Einspeisung und Netzbezug, sind Mushroom-Template-Cards. Die Template-Card nimmt einen Primär- und einen Sekundärtext, beide Jinja, ein Icon und eine Icon-Farbe, die ebenfalls ein Template sein darf. Genau das macht die Kacheln auf einen Blick lesbar: Netzbezug wird nur rot, solange es welchen gibt, Einspeisung nur teal, solange es welche gibt, sonst sind beide grau.

- type: custom:mushroom-template-card
  primary: Verbrauch
  secondary: "{{ states('sensor.demo_hausverbrauch') | int }} W"
  icon: mdi:home-lightning-bolt
  icon_color: grey
  layout: vertical
  card_mod: &werteblock
    style: |
      ha-card {
        border: 1px solid var(--ha-card-border-color);
        padding-bottom: 4px;
      }
      mushroom-state-info$: |
        .primary {
          font-family: Archivo, sans-serif !important;
          font-size: 0.7rem !important;
          letter-spacing: 0.14em;
          text-transform: uppercase;
        }
        .secondary {
          font-family: 'IBM Plex Mono', monospace !important;
          font-size: 1.15rem !important;
          font-variant-numeric: tabular-nums;
        }

- type: custom:mushroom-template-card
  primary: Netzbezug
  secondary: "{{ states('sensor.demo_netzbezug') | int }} W"
  icon: mdi:transmission-tower-export
  icon_color: >
    {{ 'red' if states('sensor.demo_netzbezug') | int > 0 else 'grey' }}
  layout: vertical
  card_mod: *werteblock

Der card-mod-Block setzt die Typografie durch. Der Selektor mushroom-state-info$ mit dem Dollarzeichen greift in den Shadow-Root der Karte, wo die Spans für Primär- und Sekundärtext liegen. Der YAML-Anker in der ersten Karte und der Alias in den anderen halten den Stil an einer Stelle. Elf Template-Cards auf diesem Dashboard teilen sich zwei solche Anker.

Der Batteriering ist ein Strich, keine Scheibe mit Loch

Die Speicherkachel zeigt den Ladestand als Ring. Meine erste Version war eine button-card mit einem conic-gradient als Hintergrund und einem kleineren Kreis darüber, dessen Hintergrund auf var(--ha-card-background) stand, um das Loch zu stanzen. Das ergab eine volle Scheibe. Im Shadow-DOM von button-card löst die Variable nicht auf, der innere Kreis war also transparent, und der Verlauf schien durch.

Die Version, die funktioniert, zeichnet den Ring als SVG-Kreis und zeigt den gefüllten Anteil über stroke-dasharray. Ein Ring aus einem Strich hat kein Loch, das schiefgehen kann. Die Farbe wechselt bei 60 und 25 Prozent, und das Ganze ist ein Custom Field in button-card, in JavaScript aus dem Sensorzustand berechnet:

- type: custom:button-card
  entity: sensor.demo_batterie_ladestand
  show_name: false
  show_state: false
  show_icon: false
  custom_fields:
    ring: |
      [[[
        const soc = Math.max(0, Math.min(100,
          Number(states['sensor.demo_batterie_ladestand'].state) || 0));
        const col = soc >= 60 ? '#54cabe' : (soc >= 25 ? '#f0a44a' : '#e0685f');
        const R = 52, C = 2 * Math.PI * R;
        const dash = (soc / 100) * C;
        return `
          <svg class="ring" viewBox="0 0 120 120">
            <circle class="track" cx="60" cy="60" r="${R}"/>
            <circle class="val" cx="60" cy="60" r="${R}"
                    stroke="${col}"
                    stroke-dasharray="${dash.toFixed(1)} ${(C - dash).toFixed(1)}"
                    transform="rotate(-90 60 60)"/>
            <text class="num" x="60" y="58">${soc}</text>
            <text class="lbl" x="60" y="76">LADESTAND</text>
          </svg>`;
      ]]]

Zwei weitere Dinge, die kaputt waren

Markdown-Karten als Abschnittsüberschriften haben das Raster zerstört. Für die kleinen versalen Abschnittslabels hatte ich Markdown-Karten benutzt. Eine Markdown-Karte zählt als Gitterzelle, also rutschten die Kacheln darunter in falsche Spalten. Die native Heading-Karte, die es seit dem Sections-Layout gibt, spannt über die volle Abschnittsbreite und nimmt am Raster nicht teil. Jedes Abschnittslabel auf dem Dashboard ist jetzt eine Heading-Karte mit card-mod für die Sperrung.

Der Regler blieb neongelb. Die Zieltemperatur unten rechts ist eine Mushroom-Number-Card. Ich hatte Mushrooms eigene Farbvariable im Theme gesetzt, und der Regler hat sie ignoriert. Mushroom färbt diesen Regler über Home Assistants eigene Variable --rgb-state-number, und gefunden habe ich das durch Auslesen der berechneten Stile im laufenden Frontend, nicht durch Raten. Beide Variablen stehen jetzt im Theme:

# themes/instrument.yaml (Auszug)
instrument:
  modes:
    dark:
      mush-rgb-state-number: "240, 164, 74"
      # HA färbt den input_number-Regler über --rgb-state-number,
      # Mushrooms eigene Variable reicht nicht.
      rgb-state-number: "240, 164, 74"
    light:
      mush-rgb-state-number: "180, 106, 18"
      rgb-state-number: "180, 106, 18"

Ein viertes Problem ist eher ein Testproblem als ein Designproblem. Der Hellmodus zeigte immer wieder Home Assistants Standardweiß statt meines Themes. Der Dienst frontend.set_theme setzt nur den Server-Standard je Modus, und solange im Benutzerprofil „Backend-selected“ steht, ist nicht zuverlässig steuerbar, welcher Modus greift. Für reproduzierbare Aufnahmen muss das Theme explizit gesetzt werden, was das Screenshot-Skript tut, indem es vor dem Laden der Seite selectedTheme in localStorage schreibt.

Was ich nicht noch einmal machen würde

Das Instrument-Dashboard braucht vier Custom Cards und rund 300 Zeilen YAML für das, was die Bordmittel-Version mit 23 Karten und ohne Plugins macht. Der Unterschied ist real, die beiden Screenshots im Vorlagen-Post zeigen ihn, aber die Wartungskosten sind es auch. Jede der vier Cards ist ein eigenes Projekt mit eigenem Release-Zyklus, und card-mod greift auf Shadow-DOM-Selektoren zu, die die Mushroom-Maintainer jederzeit umbenennen können. Die Wertekacheln überleben ein Mushroom-Update. Die card-mod-Typografie vielleicht nicht.

Würde ich ein Dashboard für den täglichen Gebrauch anfangen und nicht für eine Blogserie, würde ich Mushroom allein nehmen und card-mod weglassen, die Standard-Typografie akzeptieren und die Zeit in die Sensoren stecken. Den Ring würde ich behalten. Er ist eine Karte, hängt an nichts außer button-card, und er zeigt einen Messwert.

Häufige Fragen

Brauche ich HACS, um Mushroom oder button-card zu benutzen?

Nein. HACS lädt das Bundle herunter und registriert die Ressource, das ist bequem, aber die Cards sind gewöhnliche JavaScript-Module. Die Datei unter www ablegen und unter extra_module_url eintragen, oder sie in der Oberfläche als Dashboard-Ressource hinzufügen. Die Demo-VM lädt alle vier auf diesem Weg, damit das Setup vollständig im Repository liegt.

Warum braucht card-mod das Dollarzeichen im Selektor?

Das Dollarzeichen weist card-mod an, in den Shadow-Root des davor genannten Elements zu gehen. Mushroom rendert seine Texte in mushroom-state-info, dessen Inneres mit einem normalen CSS-Selektor von außen nicht erreichbar ist. Ohne Dollarzeichen trifft die Regel das Host-Element und bewirkt nichts Sichtbares.

Aktualisiert sich der SVG-Ring live?

Ja. button-card wertet das JavaScript in einem Custom Field jedes Mal neu aus, wenn die gebundene Entität ihren Zustand ändert. Auf der Demo-VM aktualisiert sich der Ladestandsensor alle paar Sekunden, und der Ring folgt ohne Neuladen der Seite.

Verwandte Artikel