New lovelace custom card approach - fully customizable card with your own design

I think the new lovelace ui along with home assistant cast has lots of potential. And i’m thinking of creating a lovelace card builder so people can create custom cards and import designs created in Sketch or Illustrator. And a easy to use GUI editor for mapping states and attaching actions.

My approach is to break down the card into 3 parts.

  1. Use any SVG or HTML file as the source of content
  2. Define data bindings to elements in the UI
  3. Attach actions on any element in the UI

This way you can have a designer work solely on the visual part. The best part is it allows to update the design of your card later on by just simply import a svg or html file.

Here is a demo of what I have so far

Import design into existing card

Take full advantage of HA event bus for updating states and calling services

And this is how the configuration looks like

So i need more input on this. What do you guys think, should I continue with this, or is it something that has been done?

10 Likes

That looks super interesting to me and I would love to try it out for a custom card of mine.

how is this different from layout-card paired with all of thomasloven’s other custom cards?

1 Like

I cant really answer that, I’m relative new to hass and currently working on a first custom integration to which I would like to make a lovelace-card-gui.combining html, css with events and data. This looked to be the cook book for it. But please advice if there’s better path to take as I haven’t gone through all the beutiful cards from Thomas yet.

I was focusing on fullscreen card and take advantage of SVG scaling capability. Imagine something was designed for 720p screen and displaying theme on a 4K screen. It scales out beautifully.

I have lots of designer friends and they can produce beautiful designs, and their outputs are SVG, my card is a bridge to bring their designs to lovelace UI, that’s all.

1 Like

By the way, my card is available to try in repo. Just include the script, and the card will show up in new card selection modal.

if the code is being share. I would also love to test it

These are some of the yaml config as seen in the video

    cards:
      - actions:
          - call: entity.toggle()
            selector: '#light'
            type: click
        bindings:
          - bind: return Math.round((attr.brightness || 0) / 255 * 100) || '--'
            selector: '#light-value'
            type: html
          - bind: 'return Math.round(hass.states["sensor.balcony_temperature"].state)'
            selector: '#temp-value'
            type: html
          - bind: 'return hass.states["sensor.balcony_humidity"].state'
            selector: '#humid-value'
            type: html
          - bind: 'return state == "on" ? "orange" : "gray"'
            selector: '#light circle'
            type: stroke
          - bind: 'return state == "on" ? "#ff98001a" : "#2196f317"'
            selector: '#background'
            type: fill
          - bind: return (attr.brightness || 0) / 255 * 0.8 + 0.2
            selector: '#light circle'
            type: opacity
        content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 180 110\">\n<style type=\"text/css\">\n\t.st1{fill:none;stroke-width:2;}\n\t.st2{fill:#919DA9;}\n\t.st4{font-size:18px;}\n\t.st5{font-size:6px;}\n\t.st6{font-size:4px;}\n\ttext { text-anchor: middle; alignment-baseline: middle; }\n</style>\n<rect id=\"background\" class=\"st0\" width=\"180\" height=\"110\" fill=\"transparent\"/>\n<g id=\"temp\" transform=\"translate(20.000000, 55.000000)\">\n\t<circle stroke=\"#D2D8DE\" id=\"circle\" class=\"st1\" cx=\"20\" cy=\"20\" r=\"21\"/>\n\t<text x=\"20\" y=\"8\" class=\"st2 st3 st6\">TEMP</text>\n\t<text x=\"20\" y=\"32\" class=\"st2 st3 st5\">ºC</text>\n\t<text id=\"temp-value\" x=\"20\" y=\"21\" class=\"st2 st3 st4\">0</text>\n</g>\n<g id=\"humid\" transform=\"translate(65.000000, 15.000000)\">\n\t<circle stroke=\"#D2D8DE\" id=\"circle_1_\" class=\"st1\" cx=\"20\" cy=\"20\" r=\"20\"/>\n\t<text x=\"20\" y=\"8\" class=\"st2 st3 st6\">HUMID</text>\n\t<text x=\"20\" y=\"32\" class=\"st2 st3 st5\">%</text>\n\t<text id=\"humid-value\" x=\"20\" y=\"21\" class=\"st2 st3 st4\">0</text>\n</g>\n<g id=\"light\" transform=\"translate(120.000000, 45.000000)\">\n\t<circle id=\"circle_2_\" class=\"st1\" cx=\"20\" cy=\"20\" r=\"30\"/>\n\t<text x=\"20\" y=\"8\" class=\"st2 st3 st6\">LIGHT</text>\n\t<text x=\"20\" y=\"32\" class=\"st2 st3 st5\">%</text>\n\t<text id=\"light-value\" x=\"20\" y=\"21\" class=\"st2 st3 st4\">0</text>\n</g>\n</svg>"
        entity: light.bedroom_light
        type: 'custom:example-card'
      - entity: light.bedroom_light
        type: light
      - actions:
          - call: entity.toggle()
            selector: '#light'
            type: click
        bindings:
          - bind: return Math.round((attr.brightness || 0) / 255 * 100) || '--'
            selector: '#light-value'
            type: html
          - bind: 'return Math.round(hass.states["sensor.balcony_temperature"].state)'
            selector: '#temp-value'
            type: html
          - bind: 'return hass.states["sensor.balcony_humidity"].state'
            selector: '#humid-value'
            type: html
          - bind: 'return state == "on" ? "lightseagreen" : "gray"'
            selector: '#light circle'
            type: stroke
          - bind: 'return state == "on" ? "#ff98001a" : "#2196f317"'
            selector: '#background'
            type: fill
          - bind: return (attr.brightness || 0) / 255 * 0.8 + 0.2
            selector: '#light circle'
            type: opacity
        content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 180 110\">\n<style type=\"text/css\">\n\t.st1{fill:none;stroke-width:2;}\n\t.st2{fill:#919DA9;}\n\t.st4{font-size:18px;}\n\t.st5{font-size:6px;}\n\t.st6{font-size:4px;}\n\ttext { text-anchor: middle; alignment-baseline: middle; }\n</style>\n<rect id=\"background\" class=\"st0\" width=\"180\" height=\"110\" fill=\"transparent\"/>\n<g id=\"temp\" transform=\"translate(20.000000, 35.000000)\">\n\t<circle stroke=\"#D2D8DE\" id=\"circle\" class=\"st1\" cx=\"20\" cy=\"20\" r=\"21\"/>\n\t<text x=\"20\" y=\"8\" class=\"st2 st3 st6\">TEMP</text>\n\t<text x=\"20\" y=\"32\" class=\"st2 st3 st5\">ºC</text>\n\t<text id=\"temp-value\" x=\"20\" y=\"21\" class=\"st2 st3 st4\">0</text>\n</g>\n<g id=\"humid\" transform=\"translate(70.000000, 35.000000)\">\n\t<circle stroke=\"#D2D8DE\" id=\"circle_1_\" class=\"st1\" cx=\"20\" cy=\"20\" r=\"20\"/>\n\t<text x=\"20\" y=\"8\" class=\"st2 st3 st6\">HUMID</text>\n\t<text x=\"20\" y=\"32\" class=\"st2 st3 st5\">%</text>\n\t<text id=\"humid-value\" x=\"20\" y=\"21\" class=\"st2 st3 st4\">0</text>\n</g>\n<g id=\"light\" transform=\"translate(120.000000, 35.000000)\">\n\t<circle id=\"circle_2_\" class=\"st1\" cx=\"20\" cy=\"20\" r=\"21\"/>\n\t<text x=\"20\" y=\"8\" class=\"st2 st3 st6\">LIGHT</text>\n\t<text x=\"20\" y=\"32\" class=\"st2 st3 st5\">%</text>\n\t<text id=\"light-value\" x=\"20\" y=\"21\" class=\"st2 st3 st4\">0</text>\n</g>\n</svg>"
        entity: light.bathroom_light
        type: 'custom:example-card'
    panel: false
    path: test-2
    title: Demo
cards:
      - actions:
          - call: entity.toggle()
            selector: button
            type: click
          - call: >-
              this.value == 0 ? entity.turn_off() : entity.turn_on({ brightness:
              this.value })
            selector: input
            type: change
        bindings:
          - bind: return attr.brightness || 0
            selector: input
            type: value
          - bind: return attr.color_temp || 0
            selector: span
            type: text
        content: |-
          <section>
            <h3>Custom design card</h3>
            <button>Click me</button>
            <input disable />
            <span style="font-size: 20px; font-weight: bold;"></span>
          </section>
        entity: light.bedroom_light
        type: 'custom:example-card'
      - actions:
          - call: entity.toggle()
            selector: button
            type: click
          - call: >-
              this.value == 0 ? entity.turn_off() : entity.turn_on({ brightness:
              this.value })
            selector: input
            type: change
        bindings:
          - bind: return attr.brightness || 0
            selector: input
            type: value
          - bind: return attr.color_temp || 0
            selector: span
            type: text
        content: |-
          <section>
            <h3>Custom design card</h3>
            <button>Click me</button>
            <input disable />
            <span style="font-size: 20px; font-weight: bold;"></span>
          </section>
        entity: light.balcony_light
        type: 'custom:example-card'
      - actions:
          - call: 'this.checked ? entity.turn_on() : entity.turn_off()'
            selector: '#t1'
            type: change
        bindings:
          - bind: 'return state === "on" ? true : false'
            selector: '#t1'
            type: checked
            valueMap:
              'off': false
              'on': true
        content: |-
          <ha-card header="Another custom card">
          <div style="padding: 10px;">
            <ha-switch id="t1">Toogle 1</ha-switch>
          </div>
          <div style="padding: 10px;">
            <ha-switch id="t2">Toogle 2</ha-switch>
          </div>
          </ha-card>
        entity: light.bedroom_light
        type: 'custom:example-card'
      - cards:
          - actions:
              - call: 'entity.turn_on({ brightness: this.value })'
                selector: .slider
                type: change
            bindings:
              - bind: return attr.brightness
                selector: .slider
                type: value
              - bind: return attr.brightness
                selector: .slider
                type: 'value:text'
            content: >-
              <ui-round-slider track:color="lightcoral" class="slider"
              max="255"></ui-round-slider>
            entity: light.bedroom_light
            type: 'custom:example-card'
          - actions: []
            bindings:
              - bind: 'return state === "on" ? 1 : 0'
                selector: .slider
                type: value
              - bind: return state
                selector: .slider
                type: valueText
            content: >-
              <ui-round-slider class="slider" track:color="orange"
              max="1"></ui-round-slider>
            entity: light.balcony_light
            type: 'custom:example-card'
          - actions: []
            bindings:
              - bind: 'return state === "on" ? 1 : 0'
                selector: .slider
                type: value
            content: >-
              <ui-round-slider class="slider" track:color="lightseagreen"
              max="1"></ui-round-slider>
            entity: light.bathroom_light
            type: 'custom:example-card'
        type: horizontal-stack
      - cards:
          - entity: light.bedroom_light
            type: light
          - entity: light.balcony_light
            type: light
        type: horizontal-stack
1 Like

Designs like these, which is very hard to do using default lovelace cards, can be made possible. Just import the SVG and link all the parameters.

2 Likes

I like your idea!
The low amount of replies tell me that another way to make custom cards is existing.
If some else bumps on this thread you may find these link interesting