::after
```
#### Selector CSS Attributes and HTML
| Name | Description |
| --------------- | ------------------------------------------------- |
| --color | Hover and selected background color of an option. |
| --hover-opacity | Opacity to use when hovering over an option. |
```html
#shadow-root
```
#### Toggle CSS Attributes and HTML
##### Checkbox
| Name | Description |
| -------------------------------------- | --------------------------------------- |
| --ha-checkbox-checked-background-color | Background color of checked checkboxes. |
| --ha-checkbox-checked-border-color | Border color of checked checkboxes. |
| --ha-checkbox-border-color | Border color of unchecked checkboxes. |
| --ha-checkbox-checked-icon-color | Icon color of checked checkboxes. |
##### Switch
| Name | Description |
| ------------------------------------------------ | --------------------------------------------------------------------------------------- |
| --ha-switch-checked-thumb-background-color | Handle color of checked switches. |
| --ha-switch-thumb-background-color | Handle color of unchecked switches. |
| --ha-switch-checked-thumb-background-color-hover | Hover/focused/pressed handle overlay color for checked Material Design 3 switches. |
| --ha-switch-thumb-background-color-hover | Hover/focused/pressed handle overlay color for unchecked Material Design 3 switches. |
| --ha-switch-checked-background-color | Background color of checked switches. |
| --ha-switch-background-color | Background color of unchecked switches. |
| --ha-switch-track-state-layer | Hover/focused/pressed background overlay color for unchecked Material Design 3 switches |
| --ha-switch-checked-icon-color | Icon color of checked switches. |
| --ha-switch-icon-color | Icon color of unchecked switches. |
```html
#shadow-root
#shadow-root
#shadow-root
#shadow-root
```
## Interactions
There are three traditional interactions supported by Home Assistant - tap, double tap, and hold. Buttons, selector options, and spinbox buttons support all three. Dropdown options, sliders, and toggles only support tap actions. While you can swipe horizontally on the default toggle this is considered a tap action. Defining a double tap action that is not `none` introduces a 200ms delay to single tap actions.

Each action also supports the `confirmation` field. More information on Home Assistant action confirmations can be found [here](https://www.home-assistant.io/dashboards/actions/#options-for-confirmation).
When setting the action certain features like the slider, you must use `value` within a template in the action data to use the feature value in action. Click the visual/code editor button toggle in the feature editor header to show the yaml config for just this feature.

### Action Types
Actions follow the [Home Assistant actions](https://www.home-assistant.io/dashboards/actions/) syntax. All Home Assistant actions are supported along with some additional ones.
| Action | Description |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| More info | Open the more info dialog. |
| Toggle | Toggle between the target's on and off (or similar) states. |
| Navigate | Navigate to another Home Assistant page. |
| URL | Navigate to an external URL. |
| Perform action | Call any Home Assistant service action. |
| Assist | Open the assist dialog. Uses the mobile dialog if available, like in the Home Assistant app. The pipeline ID and start listening options only work in the mobile assist dialog. |
| Fire DOM event | Fire a browser dom event using the action object as the event detail. Useful for opening [browser mod popup cards](https://github.com/thomasloven/hass-browser_mod?tab=readme-ov-file#how-do-i-update-a-popup-from-the-browser-mod-15) and for [setting custom features attributes](#setting-and-using-attributes). **Note**: Home Assistant hardcodes the event type to `ll-custom`. For other events use the `event_type` field. |
| Evaluate JS | Evaluate a string as JavaScript code. You have access to the custom feature via `this`, which includes `this.hass`, `this.config`, and `this.value`. **Potentially dangerous, do not do something dumb like call actions in a loop with no delay which can crash your browser or server.** |
| Repeat | Repeat the tap action ten times a second while held. Only applicable to hold. |
| No action | Explicilty set a command to do nothing. |
The entire action `data` and `target` fields are templatable, meaning that you can define the entire field as a YAML string in a template like so:
```yaml
action: light.turn_{{ iif(checked, 'on', 'off') }}
data: |
{% if checked %}
brightness_pct: 100
{% endif %}
```
### Adjustable Timings
Double tap and hold actions have user adjustable timings to change how they are triggered.
#### Hold Time
Hold actions are triggered by holding down on a button for a defined amount of time and then releasing. The default amount of time is 500ms. You can change this by setting `Hold time` in the hold action to a different number.
#### Repeat and Repeat Delay
By setting a hold action to `repeat`, the tap action will repeat while the button is held down. The default delay between repeats is 100ms. You can change this by setting `Repeat delay` in the hold action to a different number. See the below section on [repeat](#repeat) for more.
#### Double Tap Window
Double tap actions have a default window of 200ms to trigger before a single tap action is triggered instead. You can change this by setting `Double tap window` in the double tap action to a different number.
**Note**: Setting `Double tap window` above or too close to `Hold time` can result in undesirable behavior, as the hold timer expires before the double tap timer does. If you increase the `Double tap window` you should also increase `Hold time` to not be too close to it by at least 100ms if not more. In a custom feature without a hold action defined, you can set `Hold behavior` explicitly to `Nothing` to render the `Hold time` field.
### Momentary Button mode

As an alternative to normal tap, hold, and double tap actions, buttons, including those within selectors and spinboxes, can also be used in a momentary mode. Configuring this option disables the normal tap, hold, and double tap actions.
The momentary start action is fired when you first press down on a button. The momentary end action is fired when you release the button. Similarly to repeat hold action, the momentary repeat action is held when you hold down on the button for a set number of milliseconds. Unlike the repeat hold action, it can be different from the momentary start action. These actions can be used together or separately.
For momentary repeat and end actions you can include the number of seconds a button has been held down using `hold_secs` in a template. Click the visual/code editor button toggle in the feature editor header to show the yaml config for just this feature.
### Setting and Using Attributes
You can use the `Fire DOM event` action to set attributes on custom features rows and custom features cards. You can then use CSS to apply custom styles to custom features based on these attributes.
```yaml
action: fire-dom-event
event_type: cf-attributes
attributes:
button: ''
slider:
- brightness
- color
```
Setting an attribute value to a string like `button` in the example above will add it to the parent row. Setting it to an array like `slider` in the example above will cycle through the values in the array, removing it once it cycles through all values. If you want to set attributes without removing them, use a string value. If you want to toggle the attribute or cycle through attribute values, use an array value.
# YAML Examples
While all configuration can now be done through the user interface, these YAML examples can provide some insight on how to do some advanced styling and templating.
## Example 1
A lock tile with a toggle.
Config
```yaml
features:
- type: custom:service-call
entries:
- type: toggle
entity_id: lock.front_door_ble
value_attribute: state
styles: |-
:host {
--color: var(--feature-color);
}
tap_action:
action: toggle
target:
entity_id: lock.front_door_ble
data: {}
checked_values:
- locked
checked_icon: mdi:lock
unchecked_icon: mdi:lock-open-outline
icon: mdi:door
type: tile
entity: lock.front_door_ble
show_entity_picture: false
vertical: false
layout_options:
grid_columns: 4
grid_rows: 2
features_position: bottom
card_mod:
style:
ha-tile-info$: |
.secondary:after {
visibility: visible;
content: " ⸱ {{ states('sensor.front_door_battery_level') }}%";
}
```
## Example 2
A light tile with a button for each bulb, a color selector and dropdown, brightness and temperature sliders, and a brightness spinbox with emphasis on certain options.
Config
```yaml
type: tile
layout_options:
grid_columns: 4
grid_rows: 5
entity: light.chandelier
vertical: false
icon_tap_action:
action: perform-action
perform_action: light.toggle
target:
entity_id: light.chandelier
confirmation: true
features:
- type: custom:service-call
entries:
- type: toggle
tap_action:
action: perform-action
confirmation:
text: >-
Are you sure you want to turn the light {{ 'on' if checked else
'off' }}?
exemptions: []
target:
entity_id:
- light.chandelier
perform_action: light.turn_{{ 'on' if checked else 'off' }}
data: |
{% if checked %}
kelvin: 2000
{% endif %}
icon: ''
label: >-
{{ value if checked else states(config.entity) }}{{ unit if checked
else "" }}
value_attribute: brightness
unit_of_measurement: '%'
entity_id: light.chandelier
styles: |-
:host {
flex-basis: 400%;
}
haptics: true
unchecked_icon: mdi:ceiling-light-outline
checked_icon: mdi:ceiling-light
- tap_action:
action: call-service
service: light.toggle
target:
entity_id: light.chandelier_bulb_1
icon: mdi:lightbulb
label: Bulb 1
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--icon-color: orange;
}
- tap_action:
action: call-service
service: light.toggle
target:
entity_id: light.chandelier_bulb_2
icon: mdi:lightbulb
label: Bulb 2
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--icon-color: yellow;
}
- tap_action:
action: call-service
service: light.toggle
target:
entity_id: light.chandelier_bulb_3
icon: mdi:lightbulb
label: Bulb 3
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--icon-color: green;
}
- tap_action:
action: call-service
service: light.toggle
target:
entity_id: light.chandelier_bulb_4
icon: mdi:lightbulb
label: Bulb 4
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--icon-color: blue;
}
- tap_action:
action: call-service
service: light.toggle
target:
entity_id: light.chandelier_bulb_5
icon: mdi:lightbulb
label: Bulb 5
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--icon-color: purple;
}
styles: ''
- type: custom:service-call
entries:
- type: selector
entity_id: light.chandelier
value_attribute: rgb_color
options:
- tap_action:
action: call-service
service: light.turn_on
data:
color_name: red
target:
entity_id: light.chandelier
confirmation: true
option: 255,0,0
label: Red
icon: mdi:alpha-r
entity_id: light.chandelier
type: button
value_attribute: state
styles: |
:host {
--label-color: red;
--color: red;
{% if (state_attr(config.entity, "rgb_color") or []).join(',') == '255,0,0' %}
--label-filter: invert(1);
{% endif %}
}
- tap_action:
action: call-service
service: light.turn_on
data:
color_name: green
target:
entity_id: light.chandelier
option: 0,255,0
label: Green
icon: mdi:alpha-g
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--label-color: green;
--color: green;
{% if (state_attr(config.entity, "rgb_color") or []).join(',') == '0,255,0' %}
--label-filter: invert(1);
{% endif %}
}
- tap_action:
action: call-service
service: light.turn_on
data:
color_name: blue
target:
entity_id: light.chandelier
option: 11,0,255
label: Blue
icon: mdi:alpha-b
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--label-color: blue;
--color: blue;
{% if (state_attr(config.entity, "rgb_color") or []).join(',') == '11,0,255' %}
--label-filter: invert(1);
{% endif %}
}
- tap_action:
action: call-service
service: light.turn_on
data:
color_temp: 500
target:
entity_id: light.chandelier
option: 255,137,14
label: White
icon: mdi:alpha-w
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
flex-basis: 200%;
--label-color: white;
--color: white;
{% if (state_attr(config.entity, "rgb_color") or []).join(',') == '255,137,14' %}
--label-filter: invert(1);
--icon-filter: invert(1);
{% endif %}
}
- tap_action:
action: call-service
service: light.turn_on
target:
entity_id: light.chandelier
data:
color_name: purple
option: 254,11,255
label: Purple
icon: mdi:alpha-p
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
--label-color: purple;
--color: purple;
{% if (state_attr(config.entity, "rgb_color") or []).join(',') == '254,11,255' %}
--label-filter: invert(1);
{% endif %}
}
- entity_id: light.chandelier
option: 'null'
tap_action:
action: perform-action
perform_action: light.turn_off
target:
entity_id:
- light.chandelier
data: {}
label: 'Off'
icon: mdi:alpha-o
styles: |
:host {
{% if (state_attr(config.entity, "rgb_color") or []).join(',') == '255,0,0' %}
--label-filter: invert(1);
{% endif %}
}
styles: |-
:host {
flex-basis: 500%;
}
.background {
{% if is_state(config.entity, "on") %}
--background: rgb({{ state_attr(config.entity, "rgb_color") }});
{% endif %}
}
- type: dropdown
entity_id: light.chandelier
options:
- entity_id: light.chandelier
option: 255,137,14
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id: light.chandelier
data:
brightness_pct: 100
kelvin: 2000
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: rgb(255,137,14);
}
- entity_id: light.chandelier
option: 255,0,0
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.chandelier
data:
brightness_pct: 100
color_name: red
confirmation: true
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--red-color);
}
- entity_id: light.chandelier
option: 0,255,0
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.chandelier
data:
brightness_pct: 100
color_name: green
confirmation: false
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--green-color);
}
- entity_id: light.chandelier
option: 11,0,255
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.chandelier
data:
brightness_pct: 100
color_name: blue
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--blue-color);
}
- entity_id: light.chandelier
option: 254,11,255
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.chandelier
data:
rgb_color:
- 128
- 0
- 128
brightness_pct: 100
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--purple-color);
}
- entity_id: light.chandelier
tap_action:
action: perform-action
perform_action: light.turn_off
target:
entity_id:
- light.chandelier
data: {}
icon: mdi:checkbox-blank-circle-outline
styles: ''
option: 'null'
value_attribute: rgb_color
styles: |-
.option {
min-width: 0;
}
styles: ''
- type: custom:service-call
entries:
- type: slider
label: '{{ value }}{{ unit }}'
unit_of_measurement: '%'
value_attribute: brightness
icon: mdi:brightness-4
tap_action:
action: call-service
service: light.turn_on
data:
brightness_pct: '{{ value }}'
target:
entity_id: light.chandelier
entity_id: light.chandelier
styles: |
:host {
flex-basis: 200%;
}
range:
- 0
- 100
step: 1
thumb: default
- type: slider
thumb: line
value_attribute: color_temp_kelvin
tap_action:
action: perform-action
target:
entity_id: light.chandelier
confirmation: true
perform_action: light.turn_on
data:
color_temp_kelvin: '{{ value }}'
label: '{{ value }}{{ unit }}'
unit_of_measurement: ' Mireds'
icon: mdi:thermometer
range:
- '{{ state_attr("light.chandelier", "min_color_temp_kelvin") }}'
- '{{ state_attr("light.chandelier", "max_color_temp_kelvin") }}'
step: 1
entity_id: light.chandelier
styles: |-
:host {
--label-color: var(--disabled-color);
--background: linear-gradient(90deg, rgb(255, 167, 87), rgb(255, 255, 251));
--background-opacity: 1;
}
styles: ''
- type: custom:service-call
entries:
- type: input
entity_id: light.chandelier
tap_action:
action: perform-action
target:
entity_id:
- light.chandelier
perform_action: light.turn_on
data:
brightness_pct: '{{ value | int }}'
range:
- 0
- 100
styles: ''
value_attribute: brightness
thumb: number
- type: spinbox
haptics: true
icon: mdi:brightness-4
unit_of_measurement: '%'
label: '{{ value }}{{ unit }}'
step: 1
range:
- 0
- 100
value_attribute: brightness
tap_action:
action: call-service
service: light.turn_on
data:
brightness_pct: '{{ value | float }}'
target:
entity_id: light.chandelier
decrement:
icon: mdi:brightness-3
label: down
hold_action:
action: repeat
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
flex-flow: row;
}
.icon {
padding-right: 4px;
}
increment:
icon: mdi:brightness-2
label: up
hold_action:
action: repeat
entity_id: light.chandelier
type: button
value_attribute: state
styles: |-
:host {
flex-flow: row-reverse;
}
.icon {
padding-left: 4px;
}
hold_action:
action: repeat
entity_id: light.chandelier
styles: |-
:host {
flex-basis: 500%;
--light-color: rgb({{ state_attr(config.entity, "rgb_color") }});
--on-color: {{ "var(--light-color)" if is_state(config.entity, "on") else "initial" }};
--background: var(--on-color);
--icon-color: var(--on-color);
--label-color: var(--on-color);
}
debounce_time: 1004
features_position: bottom
```
## Example 3
Multiple sliders for a room's light, curtains, and media control.
Config
```yaml
features:
- type: custom:service-call
entries:
- type: button
haptics: true
tap_action:
action: perform-action
perform_action: light.turn_on
data:
entity_id: light.sunroom_ceiling
color_name: red
double_tap_action:
action: perform-action
perform_action: light.turn_on
xdouble_tap_window: 1000
data:
entity_id: light.sunroom_ceiling
color_name: green
hold_action:
action: perform-action
perform_action: light.turn_on
xhold_time: 2000
data:
entity_id: light.sunroom_ceiling
color_name: blue
icon: mdi:power
label: '{{ states(config.entity) }}'
entity_id: light.sunroom_ceiling
value_attribute: state
styles: |-
:host {
{% if is_state(config.entity, "on") %}
--color: rgb({{ state_attr(config.entity, "rgb_color") }});
{% endif %};
}
- type: slider
haptics: true
label: '{{ value }}{{ unit }}'
unit_of_measurement: '%'
value_attribute: brightness
icon: mdi:brightness-4
tap_action:
action: perform-action
perform_action: light.turn_on
data:
brightness_pct: '{{ value | int }}'
target:
entity_id: light.sunroom_ceiling
entity_id: light.sunroom_ceiling
range:
- 0
- 100
step: 1
thumb: default
styles: |
:host {
flex-basis: 200%;
{% if is_state(config.entity, "on") %}
--color: rgb({{ state_attr(config.entity, "rgb_color") }})
{% endif %}
}
.tooltip {
{% if is_state(config.entity, "off") %}
display: none;
{% endif %}
}
- type: custom:service-call
entries:
- type: slider
thumb: line
range:
- 0
- 360
step: 0.1
value_attribute: hs_color.0
icon: mdi:palette
tap_action:
action: perform-action
target:
entity_id: light.sunroom_ceiling
data:
hs_color:
- '{{ value }}'
- 100
perform_action: light.turn_on
entity_id: light.sunroom_ceiling
styles: |-
:host {
flex-basis: 200%;
--background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 66%, #f0f 83%, #f00 100%);
--background-opacity: 1;
}
.tooltip {
background: hsl({{ value }}, 100%, 50%);
}
- type: slider
thumb: line
value_attribute: color_temp
tap_action:
action: perform-action
perform_action: light.turn_on
data:
color_temp: '{{ value }}'
entity_id: light.sunroom_ceiling
label: '{{ value }}{{ unit }}'
unit_of_measurement: ' Mireds'
icon: mdi:thermometer
range:
- '{{ state_attr(config.entity, "min_mireds") }}'
- '{{ state_attr(config.entity, "max_mireds") }}'
step: 1
styles: |-
:host {
--background: linear-gradient(-90deg, rgb(255, 167, 87), rgb(255, 255, 251)); --background-opacity: 1; --label-color: var(--disabled-color);
}
entity_id: light.sunroom_ceiling
- type: custom:service-call
entries:
- type: slider
tap_action:
action: perform-action
perform_action: cover.set_cover_position
data:
position: '{{ value }}'
entity_id: cover.sunroom_curtains
value_attribute: current_position
icon: mdi:curtains
entity_id: cover.sunroom_curtains
styles: |-
:host {
--color: var(--feature-color);
--icon-color: var(--disabled-color);
}
range:
- 0
- 100
step: 1
- type: custom:service-call
entries:
- type: button
entity_id: cover.sunroom_curtains
icon: mdi:curtains-closed
momentary_end_action:
action: perform-action
perform_action: cover.stop_cover
target:
entity_id: cover.sunroom_curtains
momentary_start_action:
action: perform-action
perform_action: cover.close_cover
target:
entity_id: cover.sunroom_curtains
- type: button
entity_id: cover.sunroom_curtains
icon: mdi:curtains
momentary_end_action:
action: perform-action
perform_action: cover.stop_cover
target:
entity_id: cover.sunroom_curtains
momentary_start_action:
action: perform-action
perform_action: cover.open_cover
target:
entity_id: cover.sunroom_curtains
- type: custom:service-call
entries:
- type: slider
entity_id: media_player.spotify
tap_action:
action: perform-action
perform_action: media_player.volume_set
data:
volume_level: '{{ value }}'
target:
entity_id: media_player.spotify
value_attribute: volume_level
icon: mdi:spotify
label: |
{{ state_attr(config.entity, "media_title") }}
{{ state_attr(config.entity, "media_artist") }}
range:
- 0
- 1
thumb: round
styles: |-
:host {
--color: rgb(31, 223, 100);
flex-direction: row;
border-radius: 42px;
--tooltip-label: '{{ (value * 100) | int }}%';
flex-basis: 500%;
}
.icon {
color: rgb(37, 79, 55);
padding: 8px;
flex: auto;
position: absolute;
transform: translateX(var(--thumb-offset));
}
.label {
left: -16px;
filter: invert(1);
}
step: 0.01
- type: button
entity_id: media_player.spotify
icon: mdi:play-pause
tap_action:
action: perform-action
perform_action: media_player.media_play_pause
data: {}
target:
entity_id: media_player.spotify
double_tap_action:
action: perform-action
perform_action: script.spotify_refresh_every_5_seconds_for_30_seconds
target:
entity_id: media_player.spotify
value_attribute: state
styles: ''
- type: custom:service-call
entries:
- type: button
entity_id: media_player.spotify
value_attribute: media_position
tap_action:
action: perform-action
perform_action: media_player.media_previous_track
target:
entity_id: media_player.spotify
label: >-
{% set minutes = (value / 60) | int %} {% set seconds = (value - 60 *
minutes) | int %} {{ minutes }}:{{ 0 if seconds < 10 else "" }}{{
seconds | int }}
styles: |-
:host {
overflow: visible;
height: 12px;
border-radius: 0px;
--color: none;
}
- type: slider
tap_action:
action: perform-action
perform_action: media_player.media_seek
data:
seek_position: '{{ value }}'
target:
entity_id: media_player.spotify
entity_id: media_player.spotify
value_attribute: media_position
value_from_hass_delay: 5000
range:
- 0
- '{{ state_attr(config.entity, "media_duration") }}'
step: 1
thumb: flat
styles: |-
:host {
--color: rgb(31, 223, 100);
--tooltip-label: '{{ (value / 60) | int }}:{{ 0 if (value - 60*((value / 60) | int)) < 10 else "" }}{{ (value - 60*((value / 60) | int)) | int }}';
--thumb-width: 1px;
flex-basis: 1200%;
height: 10px;
}
- type: button
entity_id: media_player.spotify
value_attribute: media_position
tap_action:
action: perform-action
perform_action: media_player.media_next_track
target:
entity_id: media_player.spotify
label: >-
{{ (state_attr(config.entity, "media_duration") / 60) | int }}:{{ 0 if
(state_attr(config.entity, "media_duration") -
60*((state_attr(config.entity, "media_duration") / 60) | int)) < 10
else "" }}{{ (state_attr(config.entity, "media_duration") -
60*((state_attr(config.entity, "media_duration") / 60) | int)) | int
}}
styles: |-
:host {
overflow: visible;
height: 12px;
border-radius: 0px;
--color: none;
}
type: tile
entity: binary_sensor.sun_room
color: accent
icon: ''
layout_options:
grid_columns: 4
grid_rows: 6
```
## Example 4
Selectors for input selects. Note that the opacity of selector buttons is set to 0 by default, so they are completely transparent against the selector background.
Config
```yaml
features:
- type: custom:service-call
entries:
- type: selector
entity_id: input_select.lounge_tv_theater_mode
options:
- icon: mdi:movie
entity_id: input_select.lounge_tv_theater_mode
option: Theater
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Theater
target:
entity_id: input_select.lounge_tv_theater_mode
type: button
value_attribute: state
styles: ''
- icon: mdi:movie-off
entity_id: input_select.lounge_tv_theater_mode
option: Light
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Light
target:
entity_id: input_select.lounge_tv_theater_mode
type: button
value_attribute: state
styles: ''
- icon: mdi:movie-outline
entity_id: input_select.lounge_tv_theater_mode
option: Dark
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Dark
target:
entity_id: input_select.lounge_tv_theater_mode
type: button
value_attribute: state
styles: ''
- icon: mdi:movie-off-outline
entity_id: input_select.lounge_tv_theater_mode
option: 'Off'
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: 'Off'
target:
entity_id: input_select.lounge_tv_theater_mode
type: button
value_attribute: state
styles: ''
value_attribute: state
styles: |-
:host {
--color: var(--blue-color);
flex-basis: 140%;
}
- type: selector
entity_id: input_select.lounge_tv_listening_mode
options:
- icon: mdi:dolby
entity_id: input_select.lounge_tv_listening_mode
option: Movie
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Movie
target:
entity_id: input_select.lounge_tv_listening_mode
type: button
value_attribute: state
styles: ''
- icon: mdi:music
entity_id: input_select.lounge_tv_listening_mode
option: Music
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Music
target:
entity_id: input_select.lounge_tv_listening_mode
type: button
value_attribute: state
styles: ''
- icon: mdi:microsoft-xbox-controller
entity_id: input_select.lounge_tv_listening_mode
option: Game
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Game
target:
entity_id: input_select.lounge_tv_listening_mode
type: button
value_attribute: state
styles: ''
value_attribute: state
styles: ''
- type: custom:service-call
entries:
- type: selector
entity_id: input_select.lounge_tv_source
options:
- icon: mdi:television-box
entity_id: input_select.lounge_tv_source
option: Google TV
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Google TV
target:
entity_id: input_select.lounge_tv_source
type: button
value_attribute: state
styles: ''
- icon: mdi:microsoft-windows
entity_id: input_select.lounge_tv_source
option: HTPC
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: HTPC
target:
entity_id: input_select.lounge_tv_source
type: button
value_attribute: state
styles: ''
- icon: mdi:vhs
entity_id: input_select.lounge_tv_source
option: DVD/VHS
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: DVD/VHS
target:
entity_id: input_select.lounge_tv_source
type: button
value_attribute: state
styles: ''
- icon: mdi:record-player
entity_id: input_select.lounge_tv_source
option: Vinyl
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: Vinyl
target:
entity_id: input_select.lounge_tv_source
type: button
value_attribute: state
styles: ''
- icon: mdi:video-input-hdmi
entity_id: input_select.lounge_tv_source
option: External
tap_action:
action: perform-action
perform_action: input_select.select_option
data:
option: External
target:
entity_id: input_select.lounge_tv_source
type: button
value_attribute: state
styles: ''
value_attribute: state
styles: |-
:host {
--color: var(--red-color);
}
type: tile
entity: input_select.lounge_tv_listening_mode
color: green
layout_options:
grid_columns: 4
grid_rows: 3
```
## Example 5
Using a selector to display different features in a custom features card based on attributes set by selector option actions.
Config
```yaml
features:
- type: custom:service-call
entries:
- type: selector
option_type: template
option_template:
entity_id: ''
icon: mdi:alpha-{{ option | safe | lower }}
tap_action:
action: fire-dom-event
event_type: cf-attributes
attributes:
option: '{{ option }}'
confirmation: '{{ option == "C" }}'
options_template: A,B,C
value_template: A
value_from_hass_delay: 999999999
entity_id: ''
styles: |-
.selected {
--icon-color: var(--disabled-color);
}
#A {
--color: var(--red-color);
}
#B {
--color: var(--green-color);
}
#C {
--color: var(--blue-color);
}
- type: custom:service-call
entries:
- type: button
icon: mdi:arrow-down-bold
tap_action:
action: perform-action
data: {}
target:
entity_id: input_number.slider_test
perform_action: input_number.decrement
hold_action:
action: repeat
repeat_delay: 10
entity_id: input_number.slider_test
value_attribute: state
styles: ''
- type: slider
thumb: round
entity_id: input_number.slider_test
label: |
Input Number
{{ value }}{{ unit }}
unit_of_measurement: '#'
icon: mdi:numeric
styles: |-
:host {
flex-basis: 600%;
--tooltip-label: "The number is {{ value }}";
--label-color: var(--disabled-color);
}
.icon {
color: var(--disabled-color);
padding: 8px;
flex: auto;
position: absolute;
translate: var(--thumb-translate);
transition: var(--thumb-transition);
--mdc-icon-size: 24px;
}
{% if value == config.range[0] %}
.thumb {
visibility: hidden;
}
.icon {
visibility: hidden;
}
{% endif %}
range:
- -128
- 128
tap_action:
action: perform-action
target:
entity_id:
- input_number.slider_test
data:
value: '{{ value | int }}'
confirmation: false
perform_action: input_number.set_value
step: 0.5
value_attribute: state
- type: button
icon: mdi:arrow-up-bold
action: call-service
haptics: true
hold_action:
action: repeat
repeat_delay: 10
entity_id: input_number.slider_test
tap_action:
action: perform-action
target:
entity_id:
- input_number.slider_test
perform_action: input_number.increment
data: {}
value_attribute: state
styles: ''
styles: |-
:host {
--feature-height: 64px;
}
:host(:not([option='A'])) {
display: none;
}
:host(:not([option])) {
display: block;
}
- type: custom:service-call
entries:
- type: spinbox
tap_action:
action: perform-action
data:
value: '{{ value | float }}'
target:
entity_id: input_number.slider_test
perform_action: input_number.set_value
range:
- -128
- 128
step: 0.5
label: '{{ value }}'
hold_action:
action: repeat
repeat_delay: 50
decrement:
entity_id: input_number.slider_test
type: button
value_attribute: state
styles: ''
entity_id: input_number.slider_test
increment:
entity_id: input_number.slider_test
type: button
value_attribute: state
styles: ''
haptics: true
value_attribute: state
styles: ''
haptics: false
icon: ''
styles: |-
:host(:not([option='A'])) {
display: none;
}
:host(:not([option])) {
display: block;
}
- type: custom:service-call
entries:
- type: button
icon: mdi:youtube
tap_action:
action: url
url_path: youtube.com
double_tap_action:
action: url
url_path: play.spotify.com
entity_id: input_select.select_test
value_attribute: state
styles: ''
- type: button
icon: mdi:view-dashboard
tap_action:
action: navigate
navigation_path: /lovelace/0
double_tap_action:
action: navigate
navigation_path: /lovelace-extra/0
entity_id: input_select.select_test
value_attribute: state
styles: ''
- type: button
icon: mdi:view-compact
tap_action:
action: navigate
navigation_path: /lovelace-extra/subview
entity_id: input_select.select_test
value_attribute: state
styles: ''
styles: |-
:host(:not([option='B'])) {
display: none;
}
- type: custom:service-call
entries:
- type: button
icon: mdi:assistant
tap_action:
action: assist
pipeline_id: last_used
label: ''
entity_id: input_select.select_test
value_attribute: state
styles: ''
double_tap_action:
action: navigate
navigation_path: ?conversation=1
- type: button
tap_action:
action: eval
confirmation:
text: This is a test of a Home Assistant confirmation with custom text
eval: >-
console.log("This was made using an exec call with a
confirmation.");
value_attribute: state
styles: |-
.icon {
color: var(--red-color);
}
icon: mdi:language-javascript
entity_id: input_select.select_test
- type: button
tap_action:
action: eval
confirmation: false
eval: >-
console.log("This was made using an exec call with no
confirmation.");
value_attribute: state
styles: |-
.icon {
color: var(--yellow-color);
}
icon: mdi:language-javascript
entity_id: input_select.select_test
styles: |-
:host(:not([option='C'])) {
display: none;
}
type: custom:custom-features-card
```
## Example 6
A better looking temperature spinbox with hold on repeat, tile color, and an icon and label. Also an XKCD button that opens a different comic based on how long you hold it using momentary button mode, some momentary button test code, two buttons that change background color based on their state, and an example of how to use a button as a transparent label.
Config
```yaml
type: tile
layout_options:
grid_columns: 4
grid_rows: 4
entity: climate.downstairs_thermostat
vertical: false
features:
- type: custom:service-call
entries:
- type: spinbox
icon: mdi:thermometer
label: '{{ number_translated(value, 1) }}{{ unit }}'
step: 0.5
debounceTime: 1000
range:
- '{{ state_attr("climate.downstairs_thermostat", "min_temp") }}'
- '{{ state_attr("climate.downstairs_thermostat", "max_temp") }}'
value_attribute: temperature
unit_of_measurement: °F
hold_action:
action: repeat
entity_id: climate.downstairs_thermostat
styles: |-
:host {
--background: var(--tile-color);
--icon-color: var(--tile-color);
flex-flow: row;
}
tap_action:
action: perform-action
target:
entity_id: climate.downstairs_thermostat
data:
temperature: '{{ value | float }}'
perform_action: climate.set_temperature
- type: spinbox
icon: mdi:test-tube
step: 1
debounceTime: 1000
range:
- '{{ state_attr("climate.downstairs_thermostat", "min_temp") }}'
- '{{ state_attr("climate.downstairs_thermostat", "max_temp") }}'
value_attribute: temperature
hold_action:
action: repeat
styles: |-
:host {
--background: var(--primary-color);
}
tap_action:
action: none
decrement:
entity_id: climate.downstairs_thermostat
tap_action:
action: eval
eval: console.log('Going Down!');
increment:
tap_action:
action: eval
eval: console.log('Going Up');
entity_id: climate.downstairs_thermostat
hold_action:
action: repeat
entity_id: climate.downstairs_thermostat
styles: ''
- type: custom:service-call
entries:
- type: button
label: Temperature
icon: mdi:thermometer
styles: |-
:host {
flex-direction: row;
flex-basis: 400%;
justify-content: left;
gap: 24px;
margin-left: 12px;
}
.label {
width: unset;
}
thumb: transparent
- type: button
entity_id: sensor.downstairs_indoor_temperature
styles: |-
.label {
text-align: center;
}
label: |-
Indoors
{{ value }}{{ unit }}
unit_of_measurement: ' °F'
thumb: transparent
- type: button
entity_id: sensor.upstairs_thermostat_current_temperature
styles: |-
.label {
text-align: center;
}
label: |-
Upstairs
{{ value }}{{ unit }}
unit_of_measurement: ' °F'
thumb: transparent
- type: button
entity_id: sensor.downstairs_outdoor_temperature
styles: |-
.label {
text-align: center;
}
label: |-
Outdoors
{{ value }}{{ unit }}
unit_of_measurement: ' °F'
thumb: transparent
styles: ''
- type: custom:service-call
entries:
- type: button
entity_id: climate.downstairs_thermostat
label: XKCD
value_from_hass_delay: 5000
momentary_end_action:
action: url
url_path: https://xkcd.com/{{ (1000 * hold_secs) | int }}
value_attribute: state
- type: button
entity_id: sensor.cold_flu_index_today
label: |-
State Float
{{ value }}
value_attribute: state
styles: ''
icon: ''
momentary_end_action:
action: eval
eval: >-
const actionType = 'momentary_end_action'
const message = `${actionType} - ${Math.floor(this.momentaryEnd -
this.momentaryStart)}ms`
console.log(message)
const event = new Event('hass-notification', { bubbles: true,
composed: true })
event.detail = { message }
this.dispatchEvent(event)
momentary_start_action:
action: eval
eval: >-
const actionType = 'momentary_start_action'
const message = `${actionType}`
console.log(message)
const event = new Event('hass-notification', { bubbles: true,
composed: true })
event.detail = { message }
this.dispatchEvent(event)
momentary_repeat_action:
action: eval
eval: >-
const actionType = 'momentary_repeat_action'
const message = `${actionType} - ${Math.floor(this.momentaryEnd -
this.momentaryStart)}ms`
console.log(message)
const event = new Event('hass-notification', { bubbles: true,
composed: true })
event.detail = { message }
this.dispatchEvent(event)
- type: button
entity_id: sensor.cold_flu_index_today
label: |-
State Float
{{ value }}
value_attribute: strep_index
icon: ''
styles: |-
.background {
--color: {{ "blue" if state_attr(config.entity, config.attribute) < 3 else "green" }};
}
tap_action:
action: more-info
target:
entity_id: sensor.cold_flu_index_today
confirmation:
text: Show Cold & Flu Index?
exemptions:
- user: af773a442cd7493f8178f7c23b7882d7
- user: 7e9bf9d73edc48df8ece5cec7e9a4f00
data: {}
styles: ''
features_position: bottom
```
## Example 7
A read only timer display with buttons and multiple labels.
Config
```yaml
features:
- type: custom:service-call
entries:
- type: button
value_attribute: elapsed
label: >-
{% set t = as_timedelta(state_attr(config.entity, "duration")).seconds -
value %}
{% set minutes = (t/ 60) | int %}
{% set seconds = (t- 60 * minutes) | int %}
{{ minutes }}:{{ 0 if seconds < 10 else "" }}{{ seconds | int }}
styles: |-
:host {
overflow: visible;
height: 12px;
border-radius: 0px;
--color: none;
}
entity_id: timer.timer_test
- type: slider
tap_action:
action: none
value_attribute: elapsed
thumb: flat
step: 1
range:
- 0
- >-
{% set hms = (state_attr(config.entity, "duration") or
'0:0:0').split(":") %} {{ (hms[0] |int ) * 3600 + (hms[1] | int) * 60 +
(hms[2] | int) }}
styles: |-
:host {
flex-basis: 1200%;
height: 10px;
--thumb-width: 1px;
}
entity_id: timer.timer_test
- type: button
value_attribute: duration
label: '{% set hms = value.split(":") %} {{ hms[1] | int }}:{{ hms[2] }}'
styles: |-
:host {
overflow: visible;
height: 12px;
border-radius: 0px;
--color: none;
}
entity_id: timer.timer_test
- type: custom:service-call
entries:
- type: button
icon: mdi:timer-check
tap_action:
action: perform-action
perform_action: timer.start
target:
entity_id: timer.timer_test
entity_id: timer.timer_test
value_attribute: elapsed
- type: button
icon: mdi:timer-pause
tap_action:
action: perform-action
perform_action: timer.pause
target:
entity_id: timer.timer_test
entity_id: timer.timer_test
value_attribute: elapsed
- type: button
icon: mdi:timer-cancel
tap_action:
action: perform-action
perform_action: timer.cancel
target:
entity_id: timer.timer_test
entity_id: timer.timer_test
value_attribute: elapsed
styles: |-
:host {
--mdc-icon-size: 32px;
}
type: tile
entity: timer.timer_test
layout_options:
grid_columns: 4
grid_rows: 3
```
## Example 8
Dropdown features with different appearance types that are manually defined or automatically defined by an entity attribute.
Config
```yaml
type: tile
grid_options:
columns: 12
rows: auto
entity: input_select.select_test
name: Dropdowns?
icon: mdi:form-dropdown
color: green
vertical: false
features:
- type: custom:service-call
entries:
- type: dropdown
entity_id: input_select.select_test
icon: mdi:ab-testing
option_type: attribute
option_template:
label: '{{ option | safe }}'
tap_action:
action: perform-action
perform_action: input_select.select_option
target:
entity_id: '{{ config.entity }}'
data:
option: '{{ option | safe }}'
icon: mdi:alpha-{{ option | lower }}-circle
entity_id: input_select.select_test
options_attribute: options
option_attribute: options
option_entity: input_select.select_test
value_attribute: state
- type: dropdown
entity_id: light.desk_lights
value_attribute: effect
icon: mdi:string-lights
label: '{{ value }}'
option_type: attribute
option_attribute: effect_list
option_entity: light.desk_lights
option_template:
entity_id: light.desk_lights
label: '{{ option | safe }}'
tap_action:
action: perform-action
perform_action: light.turn_on
data:
effect: '{{ option | safe }}'
target:
entity_id: '{{ config.entity }}'
thumb: md3-standard
- type: dropdown
entity_id: light.sunroom_ceiling
options:
- entity_id: light.sunroom_ceiling
option: 255,167,87
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id: light.sunroom_ceiling
data:
brightness_pct: 100
color_temp_kelvin: 2000
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: rgb(255,166,87);
}
- entity_id: light.sunroom_ceiling
option: 255,0,0
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.sunroom_ceiling
data:
rgb_color:
- 255
- 0
- 0
brightness_pct: 100
confirmation:
text: RED ALERT?!
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--red-color);
}
- entity_id: light.sunroom_ceiling
option: 0,255,0
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.sunroom_ceiling
data:
rgb_color:
- 0
- 255
- 0
brightness_pct: 100
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--green-color);
}
- entity_id: light.sunroom_ceiling
option: 0,0,255
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.sunroom_ceiling
data:
rgb_color:
- 0
- 0
- 255
brightness_pct: 100
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--blue-color);
}
- entity_id: light.sunroom_ceiling
option: 128,0,128
tap_action:
action: perform-action
perform_action: light.turn_on
target:
entity_id:
- light.sunroom_ceiling
data:
rgb_color:
- 128
- 0
- 128
brightness_pct: 100
icon: mdi:checkbox-blank-circle
styles: |-
.icon {
color: var(--purple-color);
}
- entity_id: light.sunroom_ceiling
tap_action:
action: perform-action
perform_action: light.turn_off
target:
entity_id:
- light.sunroom_ceiling
data: {}
icon: mdi:checkbox-blank-circle-outline
styles: ''
option: 'null'
value_attribute: rgb_color
icon: mdi:ceiling-light
thumb: md3-fab-primary
features_position: bottom
```
## Example 9
A custom features card made to look like a tile card with selectively displayed light brightness and color sliders.
Config
```yaml
type: custom:custom-features-card
features:
- type: custom:service-call
entries:
- type: button
entity_id: light.desk_lights
thumb: transparent
styles: |-
:host {
position: absolute;
height: 100%;
width: 100%;
top: 0;
}
md-ripple {
visibility: visible;
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
--md-ripple-hover-color: var(--feature-color);
--md-ripple-hover-opacity: 0.04;
}
tap_action:
action: more-info
target:
entity_id: '{{ config.entity }}'
icon: ''
- type: button
entity_id: light.desk_lights
icon: mdi:desk
tap_action:
action: toggle
target:
entity_id: light.desk_lights
data: {}
thumb: tile-icon
- type: button
entity_id: light.desk_lights
icon: ''
unit_of_measurement: '%'
value_attribute: brightness
thumb: transparent
label: Desk Lights
styles: |-
:host {
justify-content: flex-start;
flex-direction: row;
pointer-events: none;
}
.label {
display: flex;
flex-direction: column;
width: fit-content;
font-size: 14px;
font-weight: 500;
}
.label::after {
content: '{{ value or 'Off' }}{{ unit if value or '' }}';
align-self: flex-start;
font-size: 12px;
font-weight: 400;
}
- type: slider
entity_id: light.desk_lights
icon: mdi:brightness-4
tap_action:
action: perform-action
target:
entity_id:
- light.desk_lights
perform_action: light.turn_on
data:
brightness_pct: '{{ value }}'
range:
- 0
- 100
step: 1
thumb: round
value_attribute: brightness
unit_of_measurement: '%'
- type: slider
thumb: line
range:
- 0
- 360
step: 0.1
value_attribute: hs_color.0
icon: mdi:palette
tap_action:
action: perform-action
target:
entity_id: light.desk_lights
data:
hs_color:
- '{{ value }}'
- 100
perform_action: light.turn_on
entity_id: light.desk_lights
styles: |-
:host {
--background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 66%, #f0f 83%, #f00 100%);
--background-opacity: 1;
}
.tooltip {
background: hsl({{ value }}, 100%, 50%);
}
- type: button
entity_id: light.desk_lights
icon: mdi:palette
styles: |-
:host {
flex: 1 1 0;
min-width: var(--feature-height);
}
tap_action:
action: fire-dom-event
event_type: cf-attributes
attributes:
slider:
- brightness
- color
styles: |-
:host([slider]) custom-feature-button:nth-of-type(2),
:host([slider]) custom-feature-button:nth-of-type(3){
display: none;
}
custom-feature-slider {
display: none;
}
:host([slider='brightness']) custom-feature-slider:nth-of-type(1) {
display: flex;
}
:host([slider='color']) custom-feature-slider:nth-of-type(2) {
display: flex;
}
styles: |-
:host {
--feature-color: rgb({{ state_attr('light.desk_lights', 'rgb_color') or '187,168,50' }});
--ha-ripple-color: var(--feature-color);
}
```