Custom Features for Home Assistant Cards - Buttons, Dropdowns, Selectors, Sliders, Spinboxes, and Toggles

I’m trying to replace the icon with an image in the Selector section.
Unfortunately, your example didn’t work. How can I add an image?

type: tile
entity: climate.salon_klima_2
vertical: false
features:
  - type: custom:service-call
    entries:
      - type: selector
        entity_id: climate.salon_klima_2
        options:
          - entity_id: climate.salon_klima_2
            option: "off"
            tap_action:
              action: call-service
              service: climate.set_hvac_mode
              target:
                entity_id: climate.salon_klima_2
              data:
                hvac_mode: "off"
            icon: mdi:power
            styles: |-
              :host {
                transition: width 0.5s ease;
                --icon-color: red;
                .icon {
                  --mdc-icon-size: 32px;
                }
              }
          - entity_id: climate.salon_klima_2
            option: fan_only
            tap_action:
              action: call-service
              service: climate.set_hvac_mode
              target:
                entity_id: climate.salon_klima_2
              data:
                hvac_mode: fan_only
            icon: mdi:fan
            styles: |-
              :host {
                transition: width 0.5s ease;
                --icon-color: blue;
                .icon {
                  --mdc-icon-size: 32px;
                }
              }
          - entity_id: climate.salon_klima_2
            option: dry
            tap_action:
              action: call-service
              service: climate.set_hvac_mode
              target:
                entity_id: climate.salon_klima_2
              data:
                hvac_mode: dry
            icon: mdi:water-percent
            styles: |-
              :host {
                transition: width 0.5s ease;
                --icon-color: cyan;
                .icon {
                  --mdc-icon-size: 32px;
                }
              }
          - entity_id: climate.salon_klima_2
            option: cool
            tap_action:
              action: call-service
              service: climate.set_hvac_mode
              target:
                entity_id: climate.salon_klima_2
              data:
                hvac_mode: cool
            icon: mdi:snowflake
            styles: |-
              :host {
                transition: width 0.5s ease;
                --icon-color: gray;
                .icon {
                  --mdc-icon-size: 32px;
                }
              }
features_position: bottom
card_mod:
  style: |
    ha-card {
      height: auto !important;
      padding-top: 10px !important;
      background: none !important;
      border: none !important;
      .content {
        display: none !important;
      }
    }

Also, is there a way to cancel the hover effect that occurs when it comes to the frame?

That config is a bit out of date, but you can do the same using the newer CSS styles option. In the configuration UI at the option level put this in the CSS styles field in the appearance section:

.background::before {
  background-image: url("/local/path/to/your/picture.png");
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
}

Disabling the hover color is a bit trickier as there’s a few CSS variables at play there, but these styles at the selector level should make it so that only the currently selected option is highlighted and hovered options are not highlighted.

.option:not(.selected) {
  --color: transparent;
  --opacity: 1;
  --hover-opacity: 1;
}
1 Like

Thank you so much for your answer.
I was able to put an image instead of an icon.
When I add animations and effects, the entire button is affected.
Is there a way to put it inside the tag? (I think this can be considered a request)

I wasn’t referring to the buttons inside the selector, but rather the part that appears and then disappears around the edges of the card when you hover over it. (This is probably related to “tile card”)

I will share the progress I’ve made here after working on it a bit more.

I’m not sure what you mean. Do you mean the accessibility focus outline? Or the selector selected option background color?

I’m referring to the outermost hover effect that occurs when I hover over it with the mouse.

Yeah that’s the tile card ripple, outside of these features. You can hide it with card-mod at the root of the card config:

card_mod:
  style: |
    ha-tile-container {
      --ha-ripple-hover-opacity: 0;
      --ha-ripple-pressed-opacity: 0;
    }

You could also consider using custom features card, which is a headless container (like a vertical/horizontal stack or grid card) for custom card features that doesn’t have it’s own ripple or action.

1 Like

Version 4.8.0 adds tile icon buttons and momentary repeat actions.

Tile icon buttons are a new button variant that mimics the tile icon. Combine it with a transparent button and some CSS and you can recreate the tile card header.

Momentary repeat actions allow you to fire an action on repeat when a button is held in momentary mode. This way you can fire three different actions for momentary press start, hold repeat, and release end.

1 Like

I have a problem with the editor, see the screenshot. There is no “Add Custom feature” button, but instead a list of the various kinds of features available. Clicking them has no effect.

This was fixed in 4.7.12 as Home Assistant version 2026.2.0 changed its dropdown components. Update to the latest version of this module and Home Assistant, make sure that there are not extra copies of this module installed, and possibly clear cache.

I am on HA 2026.2.3 and on Custom Features 4.8.0. Have cleared all caches and also tried on “clean” devices/browsers that have not used HA before. Still the same.

Check your browser console logs and make sure that it’s actually loading the latest version of the module. There’s probably an extra copy of an older version of the module installed that’s loading before the latest version. It may have the name service-call-tile-feature.

That seems to be the case, yes.
image
When I hover over that link, the path is http://homeassistant:8123/hacsfiles/service-call-tile-feature/custom-features-row.min.js?hacstag=698501153434

But I have no idea of how to get rid of the old version. Have tried removing Custom Features in HACS.

It should say this instead:
image

Check your frontend resources list for custom-features-row.min.js and delete it. Keep custom-card-features.min.js. Also check your /config/www/community/ directory for a folder named custom-features-row or service-call-tile-feature and delete it. If custom-features-row.min.js is in the /config/www/community/custom-card-features/ folder delete it.

That was the solution. It also seems I had a similar issue for Universal Remote Card, so I cleaned up that as well. Thank you!

1 Like

Hello everyone,
I’m having some trouble using the input feature with the 'time` type and an interaction.

I want to use the interaction to write the changed value of the feature to a modbus register. I first need to adjust the value because the modbus device expects the time as a BCD value.

I’ve successfully tested the following code in the development tools:

action: modbus.write_register
data:
  hub: logo4
  address: 65
  value: >-
   {% set value = '17:48' %}
   {{("{:04b}{:04b}{:04b}{:04b}".format((value[0]|int(0)), (value[1]|int(0)), (value[3]|int(0)), (value[4]|int(0)) ) | int(0,2))}}

However, when I try to apply this code to the feature, I get an error message: “…expected int @ data[‘value’][0]”.

The value “17:48” is only for testing in the development tools and should be replaced by the value in the feature.

I’ve tried several variations, but I’m stuck. My programming skills are somewhat limited, and I really need some help. Perhaps someone has a suggestion?

The following is the code for the feature card.

type: custom:custom-features-card
features:
  - type: custom:service-call
    entries:
      - type: input
        entity_id: sensor.uhr_sonnenschutz_ost
        tap_action:
          action: none
        thumb: time
        step: 60
        label: Einschaltzeit
        value_attribute: einschaltzeit
        range:
          - "00:00:00"
          - "23:59:59"
      - type: input
        tap_action:
          action: perform-action
          perform_action: modbus.write_register
          data:
            address: 65
            hub: logo4
            value: >-
              {% set value = '17:48' %}
              {{("{:04b}{:04b}{:04b}{:04b}".format((value[0]|int(0)),
              (value[1]|int(0)), (value[3]|int(0)), (value[4]|int(0)) ) |
              int(0,2))}}
        thumb: time
        step: 60
        range:
          - "00:00:00"
          - "23:59:59"
        label: Auschaltzeit
        entity_id: sensor.uhr_sonnenschutz_ost
        value_attribute: ausschaltzeit
  - type: custom:service-call
    entries:
      - type: button
        label: Mo
        tap_action:
          action: perform-action
          perform_action: modbus.write_coil
          data:
            hub: logo4
            address: 1025
            state: |-
              {%if is_state_attr(config.entity,'montag','true')%} 
                0 
              {% elif is_state_attr(config.entity,'montag','false')%}
                1 
              {% endif %}
          target:
            entity_id: sensor.uhr_sonnenschutz_ost
        styles: |-
          :host {
            --color: 
              {%if is_state(config.entity,'on')%}
                green
              {% elif is_state(config.entity,'off')%}
                red
              {% endif %}
          }
        momentary_start_action:
          action: perform-action
          perform_action: switch.toggle
          target:
            entity_id:
              - switch.logo4_testtagemo
          data: {}
        entity_id: switch.logo4_testtagemo
      - type: button
        entity_id: ""
        label: Di
      - type: button
        entity_id: ""
        label: Mi
      - type: button
        entity_id: ""
        label: Do
      - type: button
        entity_id: ""
        label: Fr
      - type: button
        entity_id: ""
        label: Sa
      - type: button
        entity_id: ""
        label: So
    styles: ""

Custom card features (and Universal Remote Card) use a different but very similar templating system called ha-nunjucks, which is frontend only, synchronous, and more efficient, but sometimes differs in syntax. In this case .format isn’t supported as it’s a Python function.

I don’t think there is a good way to do this with a filter, but you should be able to by using the Evaluate JS action instead. I think there may be a typo in your calculation though as you provided two arguments to the last int filter but it should only accept two arguments total. Still this should get you most of the way there:

tap_action:
  action: eval
  eval: |
    const value = '17:48';
    const b_values = value.replace(':', '')
      .split('')
      .map(n => parseInt(n)
        .toString(2)
        .padStart(4,'0')
      )
    const res = parseInt(b_values.join('')).toString(2) // FIX ME
    console.log(res)
    this.hass.callService('modbus', 'write_register', {
      address: 65,
      hub: 'logo4',
      value: res
    })

Thanks, Nerwyn. That works for me. I was finally able to implement the desired functionality.

I already suspected that the ‘format’ command wouldn’t work with nunjucks, but I couldn’t find any definitive information or an alternative.

Learned something new again.

1 Like

@Nerwyn

I’m having trouble understanding how to use the value variable in an action template.

I have a select entity with predefined times in 5-minute increments.

Now I want to use the input to select one of these values ​​from the entity.

To do this, I want to modify the input so that the value matches the next available selection. For example, change 12:36 to 12:35, or 10:28 to 10:30, or 08:58 to 09:00.

Below is my approach to implementing this in the action template. However, I’m not sure if I can use the value variable this way.

action: perform-action
target:
  entity_id:
    - select.solax_charger_start_time_1
perform_action: select.select_option
data:
  option: |
    {% set zeit = value %}
    {% set h = zeit[:2]|int(0) %}
    {% set mz = zeit[3]|int(0) %}
    {% set me = zeit[4]|int(0) %}

    {% if h == 0 and mz == 0 and me >=1 and me < 3 %}
      {{ zeit[:4]+"1" }}
    {% elif h == 23 and mz == 5 and me >=8 and me < 10 %}
      {{ zeit[:4]+"9" }}
    {% elif me >= 0 and me < 3 %}
      {{ zeit[:4]+"0" }}
    {% elif me >= 3 and me < 5 %}
      {{ zeit[:4]+"5" }}
    {% elif me >= 5 and me < 8 %}
      {{ zeit[:4]+"5" }}
    {% elif me >= 8 and me < 10 %}
      {% if mz == 5 %}
        {% if h < 9 %}
          {{"0"+(h+1)|string+":00"}}
        {%else%}
          {{(h+1)|string+":00"}}
        {%endif%}
      {%else%}
        {{zeit[:3]+(mz+1)|string+"0"}}
      {%endif%}
    {%endif%}

When I implement it like this, I get the following error message:

  • UndefinedError: ‘value’ is undefined

Do you have any tips on how I can use the value variable?

If value is undefined it may be because you didn’t set the feature entity to the correct entity or it’s attribute to the correct attribute. Try putting {{ value }} in the label and see what appears in the feature.

Your template also appears to be incorrect. For input 10:00 it’s returning 1,0,:,00. Instead of manually parsing the input as you’re doing, try using the time functions and some math. Specifically the ts-py-datetime time and timedelta objects. This template should work:

{% set zeit = time.fromisoformat(value) %}
{% set diff = zeit.minute  % 5 %}

{% if diff < 3 %}
  {% set seconds = zeit - dt.timedelta(minutes=diff) %}
{% else %}
  {% set seconds = zeit + dt.timedelta(minutes=5 - diff) %}
{% endif %}

{% set hours = (seconds / 3600) | int %}
{% set minutes = ((seconds - hours * 3600) / 60) | int %}
{% set seconds = seconds - hours * 3600 - minutes * 60 %}

{% if hours == 24 %}
  {% set hours = 0 %}
{% endif %}

{{ dt.time(hours, minutes, seconds) }}

@Nerwyn

Thanks again for your help. My initial approach was a quick fix to test setting values. Your solution is definitely much more elegant.

I’ve now tested your code and it basically works as desired. The only remaining problem is the output format. “dt.time” returns a value like “10:15:00”.

However, I need output in the format “10:15”.

I tried using the timestamp_custom filter, but it uses the local time zone and adds an hour to the time, so for example, the input value “10:15” becomes “11:15”.

{{dt.time(hours, minutes, seconds) | timestamp_custom("%H:%M")}}

Is there another way to adjust the output format?

1 Like