Universal Remote Card - Buttons, Touchpads, Sliders, and Keyboards for Multiple Platforms

Hi Nerwyn! Thank you for your amazing work on this awesome card. It brings everything together in such a cool, usable way. I’m super happy with what I have put together (pic below).

There are a few things with the slider I am still working on, and I would appreciate some guidance (code below).

  1. When volume is dragged down to 0, I lose the icon (just see black slider bar), and I would like to have this trigger a mute state w/ corresponding mute icon.

  2. Is there a way to sneak an icon under the slider (for night mode), on the same row?

  3. I’ve noticed the slider tends to get taller than the associated circlepad & left sided buttons, depending on device (i.e. totally fills the row). Is there simple CSS I am missing for this?

Thanks again for everything!

type: custom:universal-remote-card
media_player_id: media_player.sonos
rows:
  - - - appletv
    - - bluray
    - - PS5
    - - shield
    - - power
  - |
    {% if is_state('input_boolean.ha_scene_bluray','on') %}
    - - - menu
        - null
        - home
        - null
        - back
      - circlepad
      - slider
    - - chapter_back
      - rewind
      - play_pause
      - fast_forward
      - chapter_forward
    {% elif is_state('input_boolean.ha_scene_atv','on') %}
    - - - menu_atv
        - null
        - home_atv
        - null
        - back_atv
      - touchpad
      - slider
    - - rewind_atv
      - play_pause_atv
      - fast_forward_atv
    {% elif is_state('input_boolean.universal_remote_helper','on') %}
    - - null
    - - slider_horizontal
    - - null
    {% elif is_state('input_boolean.ha_scene_theater_off','on') %}
    - - null
    {% endif %}
    - - null
      - fan_down
      - fan_on
      - fan_up
      - null
custom_actions:
  - type: slider
    name: slider
    icon: |
      {% if is_state_attr('media_player.sonos','is_volume_muted',false) %}
      mdi:volume-high
      {% elif is_state_attr('media_player.sonos','is_volume_muted',true) %}
      mdi:volume-mute
      {% endif %}
    range:
      - 0
      - 1
    step: 0.01
    value_attribute: volume_level
    tap_action:
      action: perform-action
      perform_action: media_player.volume_set
      data:
        volume_level: "{{ value | float }}"
    vertical: true
    styles: |-
      {% if is_state_attr('media_player.sonos','is_volume_muted',false) %}
        :host {
          --color: white;}
      {% elif is_state_attr('media_player.sonos','is_volume_muted',true) %}
        :host {
          --color: gray;}
        .icon {
          color: white;}
      {% endif %}

2 Likes
  1. When volume is dragged down to 0, I lose the icon (just see black slider bar), and I would like to have this trigger a mute state w/ corresponding mute icon.

Templates! Almost all fields are templatable. Use this one for changing the icon based on the slider value. I also added a check for the media player is_volume_muted attribute, assuming that the slider entity is the media player.

mdi:volume-{{ "mute" if (value == config.range[0] or is_state_attr(config.entity, "is_volume_muted", true)) else "high" }}

You’ll also want to add this CSS style to the slider to make the icon visible against the background.

.off .icon {
  color: var(--color);
}
  1. Is there a way to sneak an icon under the slider (for night mode), on the same row?

I’m not sure what you mean. You can’t add a second icon to the slider, but you could use a separate custom button element and some CSS on it, the slider, and their parent row/column to position the button below/above the slider.

  1. I’ve noticed the slider tends to get taller than the associated circlepad & left sided buttons, depending on device (i.e. totally fills the row). Is there simple CSS I am missing for this?

By default vertical sliders will match their siblings in max height and horizontal sliders will fill the available card/row width. I hadn’t accounted for the circlepads 1:1 aspect ratio shrinking and how that looks with vertical sliders, which do not shrink. You could try to set a static height or max-height to the slider host styles using view width (like max-height: 70vw) to sort of make it shrink with display size like the circlepad does.

Thank you so much!

1 Like

Version 4.6.0 has one big feature - the Android TV keyboard no longer uses ADB. Huge thanks to @astrokin for reverse engineering sending text via the Android TV Remote API and @tronikos for adding a way to use this new method via Home Assistant actions. You’ll no longer see the Keyboard ID field for most platforms as it is redundant and no longer needed.

You can still use the old ADB keyboard and search methods by creating a custom element keyboard/textbox/search action with the platform Fire TV.

1 Like

Hello, I get this error when trying to use the Slider! How can I solve it?
I have a PhilipsHue Android TV

That’s telling you the volume function doesn’t work with your specific device.

What integration did you use to add the ambiligth_2?

1 Like

What a fantasic card. I’m on the the way to exchange my Logitech Harmony with an old smartphone with this card.
I would like to have a different color behind the navigation buttons, anyone knows the css for this?

In the editor preview hover over the navigation buttons such that all of them have a red outline like in your screenshot but wait until the column ID selector appears as a tooltip, and then use that to add a background with the general CSS styles field.

#column-1 {
  background: var(--input-background-color);
  border-radius: 9999px;
}
1 Like

Thanks a lot. I saw the tooltip before, but did not know there is a general CSS styles field.
Works as expected, keep on the fantastic work and many thanks for the support!

1 Like

What a well-designed card! Took a while to figure out syntax, but now I have my Logitech Harmony Hub working with my Vizio SmartCast TV, along with a Denon receiver.
I couldn’t find any examples of this combination, so here I submit my 300 lines of YAML, in the hope it inspires someone else.

Great work!

type: panel
title: TV
path: Vizio
cards:
  - type: custom:universal-remote-card
    remote_id: remote.bookcase_harmony
    media_player_id: media_player.denon_avr
    entity_id: remote.bookcase_harmony
    title: " "
    rows:
      - - vizio
        - return
        - null
        - power
      - - null
      - - netflix
        - primevideo
        - youtubetv
        - appletv
        - max
      - - null
      - - touchpad
        - slider
      - - null
      - - soundstyle
        - null
        - surroundmode
        - null
        - closed_caption
        - null
        - mute
    custom_actions:
      - type: button
        name: vizio
        icon: mdi:home-circle-outline
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: SmartCastTV
            device: Vizio TV
          target:
            entity_id: remote.bookcase_harmony
      - type: button
        name: return
        icon: mdi:arrow-u-left-top
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: Return
            device: Vizio TV
          target:
            entity_id: remote.bookcase_harmony
      - type: button
        name: power
        icon: mdi:power
        tap_action:
          action: perform-action
          perform_action: switch.toggle
          data: {}
          target:
            entity_id: switch.bookcase_watch_vizio
      - type: button
        name: netflix
        icon: mdi:netflix
        tap_action:
          action: perform-action
          perform_action: script.vizio_set_tv_to_netflix_set_denon_avr_to_mch_stereo
      - type: button
        name: primevideo
        icon: primevideo
        tap_action:
          action: perform-action
          perform_action: script.vizio_set_tv_to_amazon_prime_set_denon_avr_to_mch_stereo
        image: /local/svg/Prime_Video.svg
      - type: button
        name: youtubetv
        icon: mdi:youtube-tv
        tap_action:
          action: perform-action
          perform_action: script.vizio_set_tv_to_youtube_tv_set_denon_avr_to_mch_stereo
      - type: button
        name: appletv
        icon: appletv
        tap_action:
          action: perform-action
          perform_action: script.vizio_set_to_apple_tv_set_denon_avr_to_moviemode
      - type: button
        name: max
        icon: max
        tap_action:
          action: perform-action
          perform_action: script.vizio_set_to_hbomax_set_denon_avr_to_moviemode
      - type: slider
        name: slider
        range:
          - 0
          - 0.6
        step: 0.01
        value_attribute: volume_level
        tap_action:
          action: perform-action
          perform_action: media_player.volume_set
          data:
            volume_level: "{{ value | float }}"
        vertical: true
        icon: mdi:volume-high
        styles: |-
          :host {
            --background-height: 12px;
            --color: darkslategray;
            --background: gray;
          }
          .icon {
            color: white;
          }
          .label {
            color: white;
          }
      - type: touchpad
        name: touchpad
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            device: Vizio TV
            command: OK
          target:
            entity_id: remote.bookcase_harmony
        up:
          tap_action:
            action: perform-action
            perform_action: remote.send_command
            data:
              device: Vizio TV
              command: DirectionUp
            target:
              entity_id: remote.bookcase_harmony
          hold_action:
            action: repeat
        down:
          tap_action:
            action: perform-action
            perform_action: remote.send_command
            data:
              device: Vizio TV
              command: DirectionDown
            target:
              entity_id: remote.bookcase_harmony
          hold_action:
            action: repeat
        left:
          tap_action:
            action: perform-action
            perform_action: remote.send_command
            data:
              device: Vizio TV
              command: DirectionLeft
            target:
              entity_id: remote.bookcase_harmony
          hold_action:
            action: repeat
        right:
          tap_action:
            action: perform-action
            perform_action: remote.send_command
            data:
              device: Vizio TV
              command: DirectionRight
            target:
              entity_id: remote.bookcase_harmony
          hold_action:
            action: repeat
      - type: slider
        name: slider
        value_attribute: volume_level
        entity_id: media_player.denon_avr
        range:
          - 0.01
          - 0.6
        step: 0.01
        styles: |-
          :host {
            height: 24px;
            border-radius: 4px;
            --background-height: 12px;
            --color: darkred;
            --background: red;
            --thumb-border-radius: 0;
          }
          .icon {
            display: none;
          }
          .label {
            {% if not states(config.entity, 'on') %}
            display: none;
            {% endif %}
            transform: var(--icon-transform);
            font-size: 14px;
            font-weight: 1000;
            color: var(--primary-text-color);
          }
          .tooltip {
            display: none;
          }
          label: '{{ (value * 100 ) | int }}%'
      - type: button
        name: soundstyle
        icon: mdi:speaker-multiple
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: ModeMultiChStereo
            device: Denon AV Receiver
        label: Multi-Channel Stereo
      - type: button
        name: surroundmode
        icon: mdi:movie-open-outline
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: ModeMovie
            device: Denon AV Receiver
        label: Movie mode
      - type: button
        name: closed_caption
        icon: mdi:closed-caption-outline
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: ClosedCaption
            device: Vizio TV
        label: ClosedCaption
      - type: button
        name: soundstyle
        icon: mdi:speaker-multiple
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: ModeMultiChStereo
            device: Denon AV Receiver
      - type: button
        name: surroundmode
        icon: mdi:movie-open-outline
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: ModeMovie
            device: Denon AV Receiver
          target:
            entity_id: remote.bookcase_harmony
      - type: button
        name: mute
        icon: mdi:volume-mute
        tap_action:
          action: perform-action
          perform_action: remote.send_command
          data:
            command: Mute
            device: Vizio TV
          target:
            entity_id: remote.bookcase_harmony
        label: Mute
    styles: |
      #netflix::part(icon) {
        color: rgb(229, 9, 20);
      }
      #hulu::part(icon) {
        color: rgb(28, 231, 131);
      }
      #disney::part(icon) {
        color: rgb(17, 60, 207);
      }
      #max::part(icon) {
        color: rgb(0, 35, 246);
      }
      #primevideo::part(icon) {
        color: rgb(0, 165, 222);
      }
      #youtubetv::part(icon) {
        color: rgb(229, 9, 20);
      }
    device: Bookcase Harmony
    platform: Generic Remote
    custom_icons:
      - name: Prime Video
        path: /local/svg/Prime_Video.svg
    autofill_entity_id: true
    repeat_delay: 500
    hold_time: 300
icon: mdi:television-classic
theme: Material You Dark

1 Like

You used the card configuration UI right? It makes setup much easier. FYI Material You theme works best when you set it at the user level (not in the view or card) with the companion Material You Utilities module.

Only recently discovered how comprehensive the config UI is; still experimenting with styles & themes. Lots of examples for these. Thanks!

This is a fantastic card, many thanks @Nerwyn

I need some help with configuring my mute button to toggle mute on/off. I’m using a SONOS Beam for the volume and have failed so far to toggle the mute with a single button. I know this needs some kind of boolean check to see whether the volume is muted or not but I don’t know how to add that. Can anyone help amend my code:

  - type: button
    name: volume_mute
    icon: mdi:volume-mute 
    tap_action:
      action: perform-action
      perform_action: media_player.volume_mute
      target: {}
      data:
        is_volume_muted: false
    entity_id: media_player.kitchen_tv

Hey @Nerwyn Great plugin and I’m busy setting up dashboards and remotes for my living room, cinema, etc.

But, I have an odd problem. For my Harmony Hub integration I have setup a number of actions and in the remote I have successfully assigned this actions to custom buttons. For example, a button for the TV directional control up works great. Same for the other directional controls.

But, the circlepad looks nicer in the UI. So, I created a custom circlepad control and the assigned the up, down, etc to the same actions as the existing equivalent buttons. But, tapping the circlepad doesn’t run any of the actions nothing happens. What am I missing? Here’s a cut-down configuration:

type: custom:universal-remote-card
rows:
  - - hub_volume_up
    - null
    - direction_up
    - null
    - tv_channel_up
  - - volume_text
    - direction_left
    - select
    - direction_right
...
custom_actions:
  - type: button
    name: direction_up
    icon: mdi:chevron-up
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_up
      target: {}
  - type: button
    name: direction_down
    icon: mdi:chevron-down
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_down
      target: {}
...
  - type: circlepad
    name: Navigation
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_select
      target: {}
    up:
      tap_action:
        action: perform-action
        perform_action: script.living_room_tv_up
        target: {}
    autofill_entity_id: false
    haptics: false
    entity_id: remote.harmony_hub
    down:
      tap_action:
        action: perform-action
        perform_action: script.living_room_tv_down
        target: {}
platform: Generic Remote
remote_id: remote.harmony_hub
hold_time: 100
repeat_delay: 50
visibility:
  - condition: state
    entity: remote.harmony_hub
    state: "on"

Any ideas on what I’m doing wrong here?

Use a template

- type: button
    name: volume_mute
    icon: mdi:volume-mute 
    tap_action:
      action: perform-action
      perform_action: media_player.volume_mute
      target: {}
      data:
        is_volume_muted: '{{ not state_attr("media_player.kitchen_tv", "is_volume_muted") }}'
    entity_id: media_player.kitchen_tv
1 Like

It’s working fine for me on desktop Brave and Firefox, and Android. What devices/browsers is it not working on for you?

It fails on all my devices, latest chrome on my Windows laptop, Android tablet and mobile phone. I also tried using Edge on Windows, still doesn’t work.

The odd thing is I’ve also setup the circlepad on a Roku remote. That works, but that is one of the built in supported remotes and its tap behaviour is key.

It just appears to be the generic remote / perform action setup that fails.

UPDATE: I get the following error in the logs when I try and use the circlepad:

Logger: homeassistant.components.harmony.remote
Source: components/harmony/remote.py:242
integration: Logitech Harmony Hub (documentation, issues)
First occurred: 15 July 2025 at 16:39:43 (42 occurrences)
Last logged: 14:29:13

Harmony Hub: Missing required argument: device

But, the script has the device:

action: remote.send_command
target:
  device_id: 2e15471ad5c009a09a2b7ed3aab27fbb
data:
  command: DirectionDown
  device: Manhattan DVR
  delay_secs: 0.6

I don’t get the log message when I create a button and add that action to the button. Up/down work fine when it’s a button. It’s only when the circlepad buttons are pressed that I get the log message.

Check what version of the module is loading in the browser dev tool logs. Can you post your full remote layout from your config?

Dev tools tells me the version is UNIVERSAL-REMOTE-CARD v4.5.5

Here’s the complete remote config:

type: custom:universal-remote-card
rows:
  - - hub_volume_up
    - null
    - direction_up
    - null
    - tv_channel_up
  - - volume_text
    - direction_left
    - select
    - direction_right
    - channel
  - - hub_volume_down
    - null
    - direction_down
    - null
    - tv_channel_down
  - - null
  - - dvr
    - guide
    - tv_info
    - tv_return
    - tv_exit
    - hub_volume_mute
  - - null
  - - tv_rewind
    - tv_play
    - tv_fast_forward
  - - tv_record
    - tv_pause
    - tv_stop
  - - null
  - - tv_1
    - tv_2
    - tv_3
  - - tv_4
    - tv_5
    - tv_6
  - - tv_7
    - tv_8
    - tv_9
  - - - tv_0
  - - circlepad
custom_actions:
  - type: button
    name: direction_up
    icon: mdi:chevron-up
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_up
      target: {}
  - type: button
    name: direction_down
    icon: mdi:chevron-down
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_down
      target: {}
  - type: button
    name: direction_left
    icon: mdi:chevron-left
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_left
      target: {}
  - type: button
    name: direction_right
    icon: mdi:chevron-right
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_right
      target: {}
  - type: button
    name: select
    icon: mdi:checkbox-blank-circle
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_select
      target: {}
  - type: button
    name: dvr
    icon: mdi:format-list-checkbox
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_dvr
      target: {}
    label: DVR
  - type: button
    name: guide
    icon: mdi:television-guide
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_guide
      target: {}
    label: GUIDE
  - type: button
    name: tv_0
    icon: mdi:numeric-0
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_0
      target: {}
  - type: button
    name: tv_1
    icon: mdi:numeric-1
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_1
      target: {}
  - type: button
    name: tv_2
    icon: mdi:numeric-2
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_2
      target: {}
  - type: button
    name: tv_3
    icon: mdi:numeric-3
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_3
      target: {}
  - type: button
    name: tv_4
    icon: mdi:numeric-4
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_4
      target: {}
  - type: button
    name: tv_5
    icon: mdi:numeric-5
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_5
      target: {}
  - type: button
    name: tv_6
    icon: mdi:numeric-6
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_6
      target: {}
  - type: button
    name: tv_7
    icon: mdi:numeric-7
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_7
      target: {}
  - type: button
    name: tv_8
    icon: mdi:numeric-8
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_8
      target: {}
  - type: button
    name: tv_9
    icon: mdi:numeric-9
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_9
      target: {}
  - type: button
    name: tv_info
    icon: mdi:information-box
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
    label: INFO
  - type: button
    name: tv_channel_down
    icon: mdi:arrow-down
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_channel_down
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
  - type: button
    name: tv_channel_up
    icon: mdi:arrow-up
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_channel_up
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
  - type: button
    name: tv_rewind
    icon: mdi:rewind
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_rewind
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
  - type: button
    name: tv_fast_forward
    icon: mdi:fast-forward
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_fast_forward
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
  - type: button
    name: tv_stop
    icon: mdi:stop
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_stop
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
  - type: button
    name: tv_play
    icon: mdi:play
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_play
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
  - type: button
    name: tv_record
    icon: mdi:record
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_record
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
    styles: |-
      .icon {
        color: red;
      }
  - type: button
    name: tv_pause
    icon: mdi:pause
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_pause
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
  - type: button
    name: tv_exit
    icon: mdi:exit-to-app
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_exit
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
    label: EXIT
  - type: button
    name: tv_return
    icon: mdi:keyboard-return
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_return
      target: {}
    tap_actions:
      action: perform-action
      perform_action: script.living_room_tv_info
      target: {}
    label: RETURN
  - type: button
    name: hub_volume_up
    icon: mdi:volume-plus
    tap_action:
      action: perform-action
      perform_action: script.living_room_volume_up
      target: {}
  - type: button
    name: hub_volume_down
    icon: mdi:volume-minus
    tap_action:
      action: perform-action
      perform_action: script.living_room_volume_up_duplicate
      target: {}
  - type: button
    name: hub_volume_mute
    icon: mdi:volume-mute
    tap_action:
      action: perform-action
      perform_action: script.living_room_mute
      target: {}
    label: MUTE
  - type: button
    name: channel
    label: Channel
    tap_action:
      action: none
  - type: button
    name: volume_text
    label: Volume
    tap_action:
      action: none
  - type: circlepad
    name: Navigation
    tap_action:
      action: perform-action
      perform_action: script.living_room_tv_select
      target: {}
    up:
      tap_action:
        action: perform-action
        perform_action: script.living_room_tv_up
        target: {}
    autofill_entity_id: false
    haptics: false
    entity_id: remote.harmony_hub
    down:
      tap_action:
        action: perform-action
        perform_action: script.living_room_tv_down
        target: {}
    left:
      tap_action:
        action: perform-action
        perform_action: script.living_room_tv_left
        target: {}
    right:
      tap_action:
        action: perform-action
        perform_action: script.living_room_tv_right
        target: {}
platform: Generic Remote
remote_id: remote.harmony_hub
hold_time: 100
repeat_delay: 50
visibility:
  - condition: state
    entity: remote.harmony_hub
    state: "on"

You named your circlepad Navigation but are using the default name circlepad in your layout.