Display equipment as custom widget in auto generated pages

Hi all. I recently discovered how to represent items better in the MainUI, thanks to few examples I’ve found in the community.
Now I would like to change –under Equipment>Television– a group item representing the TV remote of an LG WebOS using a custom widget I’ve whipped up, rearranging all the group members in a more accessible way.

I can’t seem to modify the metadata in a way that I achieve that. I’ve tried all the options circled in the picture below, but even after a lot of head scratching, I think I’m missing something. I do realise that the group item is not explicitly shown as an item in the semantic model because equipments are not shown as items, but I wonder if there is a way to achieve this. Am I actually trying to do something not possible?

Ultimately this is not going to work like you want. I’ve long since filed an issue on this but there’s a problem which needs to be fixed before something like this can work. The problem is if you have a list Item widget that takes up more than one row, it breaks the close and analyze buttons at the bottom of the cards. For example:

I created this widget to consolidate the controls for one of my smart humidifiers.

But because it’s larger than one row it breaks something in the framework and I end up with a bunch of extra space at the bottom of the card and I can no longer click the close or analyize all buttons. I’m pretty sure it’s because it’s not a list item widget.

Your widget will have the same problem. It will break any of the automatically generated cards it appears in.

But let’s say the problem gets fixed. The widget that gets displayed in the automatically generated cards are the “default list item widgets” so that’s the metadata that you need to use. And the root widget must be a list item widget (e.g. one of these 9 widgets. It cannot be a card. It cannot be a cell.

I suspect your widget is a card widget which works great placed on a page. But you cannot use it in one of the autogenerated cards because those ultimately are list item cards (i.e. they are a card intended to hold list item widgets, not just any old widget).

uid: levot_humidifier_list
tags:
  - list
props:
  parameters:
    - default: Humidifier
      description: Set the title for this Humidifier
      label: Humidifier
      name: title
      required: false
      type: TEXT
    - context: item
      description: Parent Equipment Group representing the humidifier
      label: Equipment
      name: equ
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Feb 14, 2023, 9:36:23 AM
component: f7-card
config:
  style:
    boarder-radius: var(--f7-card-expandable-board-radius)
    class:
      - padding: 0px
    height: 150px
    margin-left: 5px
    margin-right: 5px
    noShadow: true
    width: calc(100%)
slots:
  content:
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 15px
          position: absolute
          top: -5px
      slots:
        default:
          - component: f7-icon
            config:
              color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                'purple' :
                (Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 30)
                ? 'blue' :'orange'"
              f7: f7:drop
              size: 20
              style:
                margin-right: 10px
          - component: Label
            config:
              style:
                color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                  'purple' :
                  (Number.parseInt(items[props.equ+'_HumidityLevel'].state) >
                  30) ? 'blue' :'orange'"
                font-size: 14px
                font-weight: 700
                margin-top: 0px
              text: "=(items[props.equ+'_SwitchedOn'].state == 'OFF') ? 'Humidifier is off' :
                'Humidifier is set to ' +
                items[props.equ+'_HumiditySetPoint'].state"
    - component: f7-block
      config:
        style:
          position: absolute
          width: 100%
      slots:
        default:
          - component: f7-chip
            config:
              style:
                --f7-chip-bg-color: rgba(255, 255, 255, 0)
                color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                  'purple' :
                  (Number.parseInt(items[props.equ+'_HumidityLevel'].state) >
                  30) ? 'blue' :'orange'"
                font-size: 14px
                position: absolute
                right: 0px
                top: 8px
              text: =items[props.equ+'_HumidityLevel'].state
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 15px
          position: absolute
          top: 18px
          width: 100%
      slots:
        default:
          - component: oh-slider
            config:
              color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                'purple' :
                (Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 30)
                ? 'blue' :'orange'"
              item: =props.equ+'_HumiditySetPoint'
              label: true
              max: 80
              min: 20
              releaseOnly: true
              step: 1
              style:
                position: absolute
                top: 38px
                width: 95%
              unit: "%"
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          position: absolute
          top: 75px
          width: 25%
      slots:
        default:
          - component: oh-knob
            config:
              item: =props.equ+'_MistLevel'
              max: 3
              min: 0
              size: 80
              stepSize: 1
              style:
                color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                  'purple' :
                  (Number.parseInt(items[props.equ+'_HumidityLevel'].state) >
                  30) ? 'blue' :'orange'"
                position: absolute
              textColor: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                'purple' :
                (Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 30)
                ? 'blue' :'orange'"
          - component: Label
            config:
              style:
                color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                  'purple' :
                  (Number.parseInt(items[props.equ+'_HumidityLevel'].state) >
                  30) ? 'blue' :'orange'"
                font-size: 10px
                font-weight: 500
                left: 31px
                position: absolute
                top: 70px
              text: Mist Level
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 100px
          position: absolute
          top: 75px
          width: 25%
      slots:
        default:
          - component: oh-knob
            config:
              item: =props.equ+'_WarmLevel'
              max: 3
              min: 0
              primaryColor: orange
              size: 80
              stepSize: 1
              style:
                color: orange
                position: absolute
              textColor: orange
          - component: Label
            config:
              style:
                color: orange
                font-size: 10px
                font-weight: 500
                left: 27px
                position: absolute
                top: 70px
              text: Warm Mode
          - component: oh-toggle
            config:
              color: orange
              item: =props.equ+'_WarmModeEnabled'
              name: Warm Mode
              style:
                left: 19px
                top: 30px
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 140px
          position: absolute
          top: 79px
          width: 60%
      slots:
        default:
          - component: Label
            config:
              style:
                color: "=(items[props.equ+'_WaterLowEmpty'].state == 'ON') ? 'red' :
                  (Number.parseInt(items[props.equ+'_HumidityLevel'].state) >
                  45) ? 'purple' :
                  (Number.parseInt(items[props.equ+'_HumidityLevel'].state) >
                  30) ? 'blue' :'orange'"
                font-size: 14px
                font-weight: 700
                left: 65px
                position: absolute
                top: 0px
                width: 100%
              text: "=(items[props.equ+'_WaterLowEmpty'].state == 'ON') ? 'Water: OK' :
                'Water: Out'"
          - component: Label
            config:
              style:
                color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                  'purple' :
                  (Number.parseInt(items[props.equ+'_HumidityLevel'].state) >
                  30) ? 'blue' :'orange'"
                font-size: 14px
                font-weight: 700
                left: 65px
                position: absolute
                top: 20px
                width: 100%
              text: "='Mode: ' + items[props.equ+'_OperationMode'].state"
          - component: oh-button
            config:
              action: toggle
              actionCommand: ON
              actionItem: =props.equ+'_SwitchedOn'
              color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                'purple' :
                (Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 30)
                ? 'blue' :'orange'"
              fill: true
              style:
                left: 65px
                position: absolute
                top: 50px
                width: 45%
              text: ='Humidifier ' + items[props.equ+'_SwitchedOn'].state
          - component: oh-button
            config:
              action: toggle
              actionCommand: ON
              actionItem: =props.equ+'_Display'
              color: "=(Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 45) ?
                'purple' :
                (Number.parseInt(items[props.equ+'_HumidityLevel'].state) > 30)
                ? 'blue' :'orange'"
              fill: true
              style:
                position: absolute
                right: -50px
                top: 50px
                width: 45%
              text: ='Display ' + items[props.equ+'_SwitchedOn'].state

You might be able to use a couple of widgets that don’t take up much room to creates something reasonable. I created a couple of widgets to represent my chromecasts which are based on list item widgets and they combine multiple Items into one cell.

image
See Mediaplayer Now Playing and Control

Just make sure to set the default list item widget for all of the Items that are used with this widget, setting the visibility property to false for all but one of them. For one of them set to your custom widget.

You might need two or three widgets to fit everything in but that’s better than nine.

Somehow, I’ve missed this previously.

I suspect that you can work around this fairly easily with an f7-list-item-row.

If someone posts an example of one of these list item widgets that causes the problem, I can take a look.

Wow, thank you @rlkoshak for the quick reply. Yes the widget I’ve made is the usual f7-card (YAML at the bottom), I thought that it was somehow possible to display the group items as cards instead of lists of members.

I wonder what would happen if I were to change the base component to what you suggest and maintain all classes and styling somehow… I will try later.

I wasn’t sure this could be an “elegant” solution, but tried your suggestion, and… everything works? It doesn’t seem to break everything, the widget takes up just the space I would expect it to.

All the cards buttons Analyse and Close under Properties tab are working fine: I changed the metadata of the power button, so the widget shows up in that property card too, but the analyse button works and shows every item. I have to admit I’m in the process of configuring persistence, so I might not see problems yet.

Again, in other properties cards though the items related to the television are hidden (as expected), but the Analyse/Close button works fine.

On second thought, I haven’t used any padding/margin in the card I’ve made, just a bunch of absolute positions, could those be influencing the overall behaviour of your page @rlkoshak?

@JustinG not sure my example could help you, but

here is the remote widget I am using
uid: widget_247c2c9eb0
tags: []
props:
  parameters:
    - context: item
      description: The members of the group TV item will be pulled automatically.
      label: Television
      name: tvItems
      required: true
      type: TEXT
      groupName: remoteItems
  parameterGroups:
    - name: remoteItems
      label: Select TV item.
timestamp: Nov 3, 2025, 4:45:54 PM
component: f7-card
config:
  style:
    background-color: rgba(25, 25, 25, 1)
    --f7-card-margin-horizontal: 0px
    border-radius: 30px
    outline-border-color: rgba(126, 126, 126, 1)
    width: 12rem
    height: 35rem
    left: calc(50% - 12rem/2)
slots:
  default:
    - component: oh-button
      config:
        iconF7: power
        raised: true
        large: true
        textColor: white
        action: toggle
        actionCommand: ON
        actionCommandAlt: OFF
        actionItem: =props.tvItems + "__switch"
        class:
          - align-content-center
        style:
          background-color: rgba(126, 26, 26, 1)
          position: absolute
          left: 15px
          top: 15px
          height: 38px
          width: 38px
          border-radius: 50%
    - component: oh-button
      config:
        iconF7: house_fill
        raised: true
        large: true
        textColor: white
        action: command
        actionCommand: HOME
        actionItem: =props.tvItems + "__RCButton"
        class:
          - align-content-center
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 15px
          top: 120px
          height: 38px
          width: 38px
          border-radius: 50%
    - component: oh-button
      config:
        iconF7: chevron_left
        raised: false
        large: true
        textColor: white
        action: command
        actionCommand: LEFT
        actionItem: =props.tvItems + "__RCButton"
        class:
          - text-align-left
          - align-content-center
        style:
          background-color: rgba(20,20,20,1)
          position: absolute
          left: 21px
          top: 155px
          clip-path: polygon(0 3%, 0 97%, 100% 50%)
          transform: rotate(0deg)
          height: 150px
          width: 70px
          border-radius: 75px 0 0 75px
          z-index: 2
    - component: oh-button
      config:
        iconF7: chevron_right
        raised: false
        large: true
        textColor: white
        action: command
        actionCommand: RIGHT
        actionItem: =props.tvItems + "__RCButton"
        class:
          - text-align-right
          - align-content-center
        style:
          background-color: rgba(20,20,20,1)
          position: absolute
          left: 99px
          top: 155px
          clip-path: polygon(0 50%, 100% 97%, 100% 3%)
          transform: rotate(0deg)
          height: 150px
          width: 70px
          border-radius: 0 75px 75px 0
          z-index: 2
    - component: oh-button
      config:
        iconF7: chevron_up
        raised: false
        large: true
        textColor: white
        action: command
        actionCommand: UP
        actionItem: =props.tvItems + "__RCButton"
        style:
          background-color: rgba(20,20,20,1)
          position: absolute
          left: 20px
          top: 156px
          clip-path: polygon(3% 0, 97% 0, 50% 100%)
          transform: rotate(0deg)
          height: 70px
          width: 150px
          border-radius: 75px 75px 0 0
          z-index: 2
    - component: oh-button
      config:
        iconF7: chevron_down
        raised: false
        large: true
        textColor: white
        action: command
        actionCommand: DOWN
        actionItem: =props.tvItems + "__RCButton"
        class:
          - align-content-flex-end
        style:
          background-color: rgba(20,20,20,1)
          position: absolute
          left: 20px
          top: 234px
          clip-path: polygon(3% 100%, 97% 100%, 50% 0)
          transform: rotate(0deg)
          height: 70px
          width: 150px
          border-radius: 0 0 75px 75px
          z-index: 2
    - component: oh-button
      config:
        iconF7: square
        raised: false
        large: true
        textColor: white
        action: command
        actionCommand: ENTER
        actionItem: =props.tvItems + "__RCButton"
        class:
          - align-content-center
        style:
          background-color: rgba(20,20,20,1)
          position: absolute
          left: 70px
          top: 205px
          height: 50px
          width: 50px
          border-radius: 25px 25px 25px 25px
          z-index: 4
    - component: f7-badge
      config:
        style:
          background: rgba(35, 35, 35, 1)
          position: absolute
          left: 65px
          top: 200px
          width: 60px
          height: 60px
          border-radius: 50%
          z-index: 3
    - component: oh-button
      config:
        iconF7: arrow_uturn_left
        raised: true
        large: true
        textColor: white
        action: command
        actionCommand: BACK
        actionItem: =props.tvItems + "__RCButton"
        class:
          - align-content-center
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 15px
          top: 300px
          height: 38px
          width: 38px
          border-radius: 50%
    - component: oh-button
      config:
        iconF7: gear_alt
        raised: true
        large: true
        textColor: white
        action: command
        actionCommand: IMPOSTAZIONI
        actionItem: =props.tvItems + "__application"
        class:
          - align-content-center
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 135px
          top: 300px
          height: 38px
          width: 38px
          border-radius: 50%
    - component: f7-badge
      config:
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 15px
          top: 350px
          height: 38px
          width: 160px
          border-radius: 19px
    - component: oh-slider
      config:
        min: 0
        max: 100
        step: 1
        releaseOnly: true
        item: =props.tvItems + "__volume"
        style:
          position: absolute
          left: 20px
          top: 350px
          height: 38px
          width: 150px
          border-radius: 20px
          --f7-range-knob-size: 0px
          --f7-range-bar-size: 28px
          --f7-range-bar-bg-color: rgba(15, 15, 15, 1)
          --f7-range-bar-active-bg-color: rgba(205, 205, 205, 1)
          --f7-range-bar-border-radius: 28px
    - component: oh-button
      config:
        text: Netflix
        raised: true
        large: true
        textColor: red
        action: command
        actionCommand: Netflix
        actionItem: =props.tvItems + "__application"
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 15px
          top: 400px
          height: 30px
          width: 78px
          border-radius: 15px 0 0 15px
    - component: oh-button
      config:
        text: YouTube
        raised: true
        large: true
        textColor: white
        action: command
        actionCommand: Youtube
        actionItem: =props.tvItems + "__application"
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 98px
          top: 400px
          height: 30px
          width: 78px
          border-radius: 0 15px 15px 0
    - component: f7-badge
      config:
        style:
          icon-f7: circle
          background: rgba(35, 35, 35, 1)
          position: absolute
          left: 15px
          top: 150px
          width: 160px
          height: 160px
          border-radius: 50%
          z-index: 1
    - component: f7-badge
      config:
        style:
          background-color: rgba(35, 35, 35, 1)
          position: absolute
          left: 135px
          top: 120px
          width: 38px
          height: 38px
          border-radius: 50%
          --f7-badge-text-color: rgba(200,200,0,.5)
          z-index: 1
    - component: oh-button
      config:
        iconF7: arrow_2_circlepath
        raised: true
        large: true
        textColor: white
        action: command
        actionCommand: Home Dashboard service launcher
        actionItem: =props.tvItems + "__application"
        class:
          - align-content-center
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 15px
          top: 70px
          width: 38px
          height: 38px
          border-radius: 50%
    - component: oh-button
      config:
        iconF7: list_dash
        raised: true
        large: true
        textColor: white
        action: command
        actionCommand: Guide
        actionItem: =props.tvItems + "__application"
        class:
          - align-content-center
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 135px
          top: 70px
          width: 38px
          height: 38px
          border-radius: 50%
    - component: f7-badge
      config:
        style:
          background-color: rgba(35, 35, 35, 1)
          position: absolute
          left: 75px
          top: 95px
          width: 38px
          height: 38px
          border-radius: 50%
          z-index: 1
    - component: oh-button
      config:
        iconF7: speaker_slash
        raised: true
        large: true
        textColor: white
        action: toggle
        actionCommand: ON
        actionCommandAlt: OFF
        actionItem: =props.tvItems + "__mute"
        class:
          - align-content-center
        style:
          background-color: rgba(15, 15, 15, 1)
          position: absolute
          left: 135px
          top: 15px
          width: 38px
          height: 38px
          border-radius: 50%

I posted the YAML for the humidifier widget I use above (the levot_humidifier_list code).

Here’s another one, this time for a thermostat:

uid: simple_thermostat_list
tags:
  - list
props:
  parameters:
    - default: Thermostat
      description: Set the title for this thermostat
      label: Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: Item representing the setpoint
      label: Setpoint Item
      name: setpoint
      required: true
      type: TEXT
    - context: item
      description: The current temperature
      label: Current Temperature
      name: currentTemp
      required: true
      type: TEXT
    - context: item
      description: HVAC Mode
      label: Current thermostat mode
      name: mode
      required: true
      type: TEXT
    - default: °F
      description: Set the temperature units
      label: Units
      name: units
      required: false
      type: TEXT
      options:
        - label: °C
          value: °C
        - label: °F
          value: °F
  parameterGroups: []
timestamp: Oct 4, 2022, 3:17:30 PM
component: f7-card
config:
  style:
    boarder-radius: var(--f7-card-expandable-board-radius)
    class:
      - padding: 0px
    height: 100px
    margin-left: 5px
    margin-right: 5px
    noShadow: true
    width: calc(100%)
slots:
  content:
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 15px
          position: absolute
          top: -5px
      slots:
        default:
          - component: f7-icon
            config:
              color: "=(items[props.mode].state == 'HEAT') ? 'orange' :
                (items[props.mode].state == 'COOL') ? 'blue' : 'gray'"
              f7: "=(items[props.mode].state == 'HEAT') ? 'thermometer_sun' :
                (items[props.mode].state == 'COOL') ? 'thermometer_snowflake' :
                'thermometer'"
              size: 32
              style:
                margin-right: 10px
          - component: Label
            config:
              style:
                color: "=(items[props.mode].state == 'HEAT') ? 'orange' :
                  (items[props.mode].state == 'COOL') ? 'blue' : 'gray'"
                font-size: 14px
                font-weight: 700
                margin-top: 0px
              text: "=(items[props.mode].state == 'OFF') ? 'Thermostat is off' : 'Thermostat
                is set to ' + items[props.mode].displayState + ' to ' +
                items[props.setpoint].state"
    - component: f7-block
      config:
        style:
          position: absolute
          width: 100%
      slots:
        default:
          - component: f7-chip
            config:
              style:
                --f7-chip-bg-color: rgba(255, 255, 255, 0)
                color: "=(items[props.mode].state == 'HEAT') ? 'orange' :
                  (items[props.mode].state == 'COOL') ? 'blue' : 'gray'"
                font-size: 14px
                position: absolute
                right: 18px
                top: 8px
              text: =items[props.currentTemp].state
    - component: oh-slider
      config:
        color: "=(items[props.mode].state == 'HEAT') ? 'orange' :
          (items[props.mode].state == 'COOL') ? 'blue' : 'gray'"
        item: Thermostat_TargetSetpoint
        label: true
        max: "=(props.units == '°F') ? 80 : 27"
        min: "=(props.units == '°F') ? 50 : 10"
        releaseOnly: true
        step: "=(props.units == '°F') ? 1 : 0.5"
        style:
          position: absolute
          top: 35px
          width: 90%
        unit: =props.units
    - component: oh-button
      config:
        action: options
        actionItem: Thermostat_ThermostatMode
        color: "=(items[props.mode].state == 'HEAT') ? 'orange' :
          (items[props.mode].state == 'COOL') ? 'blue' : 'gray'"
        fill: true
        style:
          position: absolute
          top: 60px
          width: 90%
        text: Mode

It’s been a long time since I coded these and I am now realizing that they both are f7-cards which instead of list items which might actually be the root cause of the problem. But I have a vague recollection that the existing list item widgets didn’t have the flexibility for me to lay things out. But that’s is likely my own lack of skill more than a limitation of the widgets.

:person_shrugging: I’m really far from being an expert on MainUI widgets. I don’t use the UI much at all so it’s mostly an exercise in trying stuff out to see what works and doesn’t when I hav time and time is pretty scarse these days.

It’s kind of weird that mine have the problem but your’s does not. Clearly I’m doing something weird or unexpected. Perhaps it is the offsets.

Yep, the f7-card is 100% of the issue. F7 lists (and oh lists, therefore) are just unordered list elements and so prefer list item child elements. Nothing about the f7 library wraps items placed in a list in an <li> element by default (the child elements take care of this). So, the items themselves have to have <li> as the root element in one way or another or the list winds up with some degenerate html. In this case, the issue is not that the card disables the lower parts of the list, it’s actually just rendered above them because the card’s contents container winds up without proper sizing and overrides things further down the list. The buttons still work; you just can’t click them because the invisible part of the card is in the way.

Yeah, your recollection is entirely accurate. Most of the f7 list components have all manner of internal formatting that makes a general widget impossible, but that’s where the f7-list-item-row comes in. It’s basically just a blank container with the <li> root and the minimum required bit of f7 sugar on top.

I think you’ll get the results you’re looking for most directly with just a few quick changes:

  1. wrap the whole thing in an f7-list-item-row
  2. swap the card out for a div with card-like classes (note the slot change from content to default)
component: f7-list-item-row
config: {}
slots:
  default:
    #f7-cards are just divs with more baggage than you need for this
    - component: div
      config:
        #but it can still look like a card
        class:
          - card
          - card-content-padding
        style:
          boarder-radius: var(--f7-card-expandable-board-radius)
          height: 100px
          margin-left: 5px
          margin-right: 5px
          noShadow: true
          width: 100%
      slots:
        #'content' won't work here, must be 'default'
        default: 
          ... rest of widget

Yep, all the same applies here. The card as the root component makes it hard for the the styling to be applied properly and for the html to be correctly formed. The same basic fix should work here too (you’ll want to keep your custom styles instead of the ones I pasted from Rich’s widget in the yaml above). You may also find that this impacts the absolute positioning you have set and you’ll either have to re-adjust that or move to more responsive positioning.

A second way of dealing with this problem (and the one that I employ in the few places where I need something like this) is to still use a standard f7- or oh- list item as the root component but put the compound widget in the accordion slot. Yes, it’s one additional click to get to the widget, but the accordion slot doesn’t require the extra list item root element because it’s just a basic container at that point. This also keeps the list from getting unmanageable in length with several large widgets.

1 Like

I admit I’m having to practice cargo-cult-programming here. I think I did what you recommended and all appears to work. I did have a little problem because I didn’t change “content” to “default” before the start of my widget’s code but once I realized that it all started to work.

I only have a couple of larger than one row widgets like this, so I don’t think I need the accordion approach. But I’ll keep that in mind.

Thanks!

I’ll go update that issue I opened a long time ago and close it. Problem lies between keyboard and chair. I closed the issue and referenced the above post.

All right, thank you @JustinG, I’ll try to put the changes you suggested. Now I’m confused on why I am not seeing any abnormal behaviour in my case, but I imagine that just because I can’t see it, it doesn’t mean the underlying html is fine.

Actually, I might try this instead. It seems more manageable for multiple widgets. But I think that the extra click you mention can be circumvented. I remember I built a widget a couple years ago that automatically collapses and expands whenever at least one of toggle members under it was in the ON state. It was something along the lines of an boolean to set.

Yes, when a list item is in accordion mode it accepts the opened property. So you can set this to a widget expression that returns a boolean-ish value which then gives some automatic open and close effects. In your case, I could see having the accordion open automatically if the remote’s target device is ON.

1 Like

Getting this working properly coupled with the fact that my thermostat now has an upper and lower setpoint gave me the little push I needed to rework my thermostat widget posted above. Not only is it fixed to not break the overview cards, I decided to take advantage of oh-context and wow does that make it easier to create reasonable widgets. Here is the new version (I’m a little embarrassed by the version above now).

uid: new_thermostat_list
tags:
  - list
props:
  parameters:
    - default: Thermostat
      description: Set the title for this thermostat
      label: Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: Equipment Item holding all the Items
      label: Equipment Item
      name: equ
      required: true
      type: TEXT      
    - default: °F
      description: Set the temperature units
      label: Units
      name: units
      required: false
      type: TEXT
      options:
        - label: °C
          value: °C
        - label: °F
          value: °F
  parameterGroups: []
timestamp: Nov 5, 2025, 10:09:23 AM
component: f7-list-item-row
config: {}
slots:
  default:
    - component: oh-context
      config:
        constants:
          thermoItems:
            lower: "=props.equ+'_Lower_Setpoint'"
            upper: "=props.equ+'_Upper_Setpoint'"
            mode: "=props.equ+'_Thermostat_Mode'"
            temp: "=props.equ+'_Temperature'"
          modeColor:
            HEAT: orange
            COOL: blue
            AUTO: green
            OFF: black
          modeIcon:
            HEAT: thermometer_sun
            COOL: thermometer_snowflake
            AUTO: thermometer
            OFF: power
        functions:
          HEAT: =(title, lsp, usp) => title + ' is heating to ' + @@lsp
          COOL: =(title, lsp, usp) => title + ' is cooling to ' + @@usp
          AUTO: =(title, lsp, usp) => title + " is in auto mode  " + @@lsp + ' <=> ' + @@usp
          OFF: =(title, lsp, usp) => title + ' is off'
          other: =(title) = title + ' is in an unknown state'
      slots:
        default:
          - component: div
            config:
              class:
                - card
                - card-content-padding
              style:
                boarder-radius: var(--f7-card-expandable-board-radius)
                class:
                  - padding: 0px
                height: 70px
                margin-left: 5px
                margin-right: 5px
                noShadow: true
                width: calc(100%)
            slots:
              default:
                - component: f7-block
                  config:
                    style:
                      display: flex
                      flex-direction: row
                      left: 15px
                      position: absolute
                      top: -5px
                  slots:
                    default:
                      - component: f7-icon
                        config:
                          color: =const.modeColor[@@const.thermoItems.mode] || gray
                          f7: =const.modeIcon[@@const.thermoItems.mode] || 'thermometer'
                          size: 32
                          style:
                            margin-right: 10px
                      - component: Label
                        config:
                          style:
                            color: =const.modeColor[@@const.thermoItems.mode] || gray
                            font-size: 14px
                            font-weight: 700
                            margin-top: 0px
                          text: =fn[@@const.thermoItems.mode](props.title, const.thermoItems.lower, const.thermoItems.upper) || fn.other(props.title)
                - component: f7-block
                  config:
                    style:
                      position: absolute
                      width: 100%
                  slots:
                    default:
                      - component: f7-chip
                        config:
                          style:
                            --f7-chip-bg-color: rgba(255, 255, 255, 0)
                            color: "=const.modeColor[@@const.thermoItems.mode] || gray"
                            font-size: 14px
                            position: absolute
                            right: 18px
                            top: 8px
                          text: =@@const.thermoItems.temp
                - component: oh-slider
                  config:
                    color: blue
                    item: =const.thermoItems.upper
                    label: true
                    max: "=(props.units == '°F') ? 80 : 27"
                    min: "=(props.units == '°F') ? 50 : 10"
                    releaseOnly: true
                    step: "=(props.units == '°F') ? 1 : 0.5"
                    style:
                      position: absolute
                      top: 35px
                      width: 90%
                    unit: =props.units
                - component: oh-slider
                  config:
                    color: orange
                    item: =const.thermoItems.lower
                    label: true
                    max: "=(props.units == '°F') ? 80 : 27"
                    min: "=(props.units == '°F') ? 50 : 10"
                    releaseOnly: true
                    step: "=(props.units == '°F') ? 1 : 0.5"
                    style:
                      position: absolute
                      top: 35px
                      width: 90%
                    unit: =props.units
                - component: oh-button
                  config:
                    action: options
                    actionItem: =const.thermoItems.mode
                    color: "=const.modeColor[@@const.thermoItems.mode] || gray"
                    fill: true
                    style:
                      position: absolute
                      top: 60px
                      width: 90%
                    text: Mode  

I need to do this to the rest of my widgets eventually. Long term maintainability will be much better.

I’m also posting it here so that one can see the full solution in context.

3 Likes

Thank you a lot @JustinG for the help. It took me a while to understand the implementation with the accordion slot, but now I have managed.

For anyone that like me is not that skilled, I post my troubleshooting here. From the code of the widget I published above:

  • The behaviour of the accordion doesn’t work well because of
    • Class card-content-padding is preventing the accordion from fully closing.
    • The vertical margin of the card class has also to be set as zero, otherwise the accordion will not fully close. So set –f7-card-margin-vertical: 0px under style
  • What @JustinG meant with this is that once I try to open the accordion, the accordion will show as open, but the it will look as closed. The absolute positioning of all the content I had inside the f7-accordion-content “detaches” the components from the “canvas”, so when the accordion opens and is expected to size up, it “doesn’t see” any component on the page.

It took me a couple hours to realise this, but as solution I decided to not touch any position attribute, and instead create another f7-badge, with position attribute relative, with same geometries of the page, and superimpose it, adding under style the z-index: 0 (I had other components with indices starting from 1, so I tried to see if 0 was allowed).

Final revised widget code (if @JustinG blesses it) is here:

TV remote widget
uid: widget-TV_remote
tags: []
props:
  parameters:
    - context: item
      description: The members of the group TV item will be pulled automatically.
      label: Television
      name: tvItems
      required: true
      type: TEXT
      groupName: remoteItems
  parameterGroups:
    - name: remoteItems
      label: Select TV item.
timestamp: Nov 5, 2025, 3:48:50 PM
component: f7-list-item
config:
  title: Television
  accordionItem: true
  accordionItemOpened: true
slots:
  default:
    - component: f7-accordion-content
      config:
        style:
          --f7-card-margin-horizontal: 0px
          --f7-card-margin-vertical: 0px
          border-radius: 30px
          width: 12rem
          height: 35rem
          left: calc(50% - 12rem/2)
        class:
          - card
      slots:
        default:
          - component: f7-badge
            config:
              style:
                background-color: rgba(25, 25, 25, 1)
                position: relative
                left: 0px
                top: 0
                width: 12rem
                height: 35rem
                border-radius: 30px
                z-index: 0
          - component: oh-button
            config:
              iconF7: power
              raised: true
              large: true
              textColor: white
              action: toggle
              actionCommand: ON
              actionCommandAlt: OFF
              actionItem: =props.tvItems + "__switch"
              class:
                - align-content-center
              style:
                background-color: rgba(126, 26, 26, 1)
                position: absolute
                left: 15px
                top: 15px
                height: 38px
                width: 38px
                border-radius: 50%
          - component: oh-button
            config:
              iconF7: house_fill
              raised: true
              large: true
              textColor: white
              action: command
              actionCommand: HOME
              actionItem: =props.tvItems + "__RCButton"
              class:
                - align-content-center
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 15px
                top: 120px
                height: 38px
                width: 38px
                border-radius: 50%
          - component: oh-button
            config:
              iconF7: chevron_left
              raised: false
              large: true
              textColor: white
              action: command
              actionCommand: LEFT
              actionItem: =props.tvItems + "__RCButton"
              class:
                - text-align-left
                - align-content-center
              style:
                background-color: rgba(20,20,20,1)
                position: absolute
                left: 21px
                top: 155px
                clip-path: polygon(0 3%, 0 97%, 100% 50%)
                transform: rotate(0deg)
                height: 150px
                width: 70px
                border-radius: 75px 0 0 75px
                z-index: 2
          - component: oh-button
            config:
              iconF7: chevron_right
              raised: false
              large: true
              textColor: white
              action: command
              actionCommand: RIGHT
              actionItem: =props.tvItems + "__RCButton"
              class:
                - text-align-right
                - align-content-center
              style:
                background-color: rgba(20,20,20,1)
                position: absolute
                left: 99px
                top: 155px
                clip-path: polygon(0 50%, 100% 97%, 100% 3%)
                transform: rotate(0deg)
                height: 150px
                width: 70px
                border-radius: 0 75px 75px 0
                z-index: 2
          - component: oh-button
            config:
              iconF7: chevron_up
              raised: false
              large: true
              textColor: white
              action: command
              actionCommand: UP
              actionItem: =props.tvItems + "__RCButton"
              style:
                background-color: rgba(20,20,20,1)
                position: absolute
                left: 20px
                top: 156px
                clip-path: polygon(3% 0, 97% 0, 50% 100%)
                transform: rotate(0deg)
                height: 70px
                width: 150px
                border-radius: 75px 75px 0 0
                z-index: 2
          - component: oh-button
            config:
              iconF7: chevron_down
              raised: false
              large: true
              textColor: white
              action: command
              actionCommand: DOWN
              actionItem: =props.tvItems + "__RCButton"
              class:
                - align-content-flex-end
              style:
                background-color: rgba(20,20,20,1)
                position: absolute
                left: 20px
                top: 234px
                clip-path: polygon(3% 100%, 97% 100%, 50% 0)
                transform: rotate(0deg)
                height: 70px
                width: 150px
                border-radius: 0 0 75px 75px
                z-index: 2
          - component: oh-button
            config:
              iconF7: square
              raised: false
              large: true
              textColor: white
              action: command
              actionCommand: ENTER
              actionItem: =props.tvItems + "__RCButton"
              class:
                - align-content-center
              style:
                background-color: rgba(20,20,20,1)
                position: absolute
                left: 70px
                top: 205px
                height: 50px
                width: 50px
                border-radius: 25px 25px 25px 25px
                z-index: 4
          - component: f7-badge
            config:
              style:
                background: rgba(35, 35, 35, 1)
                position: absolute
                left: 65px
                top: 200px
                width: 60px
                height: 60px
                border-radius: 50%
                z-index: 3
          - component: oh-button
            config:
              iconF7: arrow_uturn_left
              raised: true
              large: true
              textColor: white
              action: command
              actionCommand: BACK
              actionItem: =props.tvItems + "__RCButton"
              class:
                - align-content-center
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 15px
                top: 300px
                height: 38px
                width: 38px
                border-radius: 50%
          - component: oh-button
            config:
              iconF7: gear_alt
              raised: true
              large: true
              textColor: white
              action: command
              actionCommand: IMPOSTAZIONI
              actionItem: =props.tvItems + "__application"
              class:
                - align-content-center
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 135px
                top: 300px
                height: 38px
                width: 38px
                border-radius: 50%
          - component: f7-badge
            config:
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 15px
                top: 350px
                height: 38px
                width: 160px
                border-radius: 19px
          - component: oh-slider
            config:
              min: 0
              max: 100
              step: 1
              releaseOnly: true
              item: =props.tvItems + "__volume"
              style:
                position: absolute
                left: 20px
                top: 350px
                height: 38px
                width: 150px
                border-radius: 20px
                --f7-range-knob-size: 0px
                --f7-range-bar-size: 28px
                --f7-range-bar-bg-color: rgba(15, 15, 15, 1)
                --f7-range-bar-active-bg-color: rgba(205, 205, 205, 1)
                --f7-range-bar-border-radius: 28px
          - component: oh-button
            config:
              text: Netflix
              raised: true
              large: true
              textColor: red
              action: command
              actionCommand: Netflix
              actionItem: =props.tvItems + "__application"
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 15px
                top: 400px
                height: 30px
                width: 78px
                border-radius: 15px 0 0 15px
          - component: oh-button
            config:
              text: YouTube
              raised: true
              large: true
              textColor: white
              action: command
              actionCommand: Youtube
              actionItem: =props.tvItems + "__application"
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 98px
                top: 400px
                height: 30px
                width: 78px
                border-radius: 0 15px 15px 0
          - component: f7-badge
            config:
              style:
                icon-f7: circle
                background: rgba(35, 35, 35, 1)
                position: absolute
                left: 15px
                top: 150px
                width: 160px
                height: 160px
                border-radius: 50%
                z-index: 1
          - component: f7-badge
            config:
              style:
                background-color: rgba(35, 35, 35, 1)
                position: absolute
                left: 135px
                top: 120px
                width: 38px
                height: 38px
                border-radius: 50%
                --f7-badge-text-color: rgba(200,200,0,.5)
                z-index: 1
          - component: oh-button
            config:
              iconF7: arrow_2_circlepath
              raised: true
              large: true
              textColor: white
              action: command
              actionCommand: Home Dashboard service launcher
              actionItem: =props.tvItems + "__application"
              class:
                - align-content-center
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 15px
                top: 70px
                width: 38px
                height: 38px
                border-radius: 50%
          - component: oh-button
            config:
              iconF7: list_dash
              raised: true
              large: true
              textColor: white
              action: command
              actionCommand: Guide
              actionItem: =props.tvItems + "__application"
              class:
                - align-content-center
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 135px
                top: 70px
                width: 38px
                height: 38px
                border-radius: 50%
          - component: f7-badge
            config:
              style:
                background-color: rgba(35, 35, 35, 1)
                position: absolute
                left: 75px
                top: 95px
                width: 38px
                height: 38px
                border-radius: 50%
                z-index: 1
          - component: oh-button
            config:
              iconF7: speaker_slash
              raised: true
              large: true
              textColor: white
              action: toggle
              actionCommand: ON
              actionCommandAlt: OFF
              actionItem: =props.tvItems + "__mute"
              class:
                - align-content-center
              style:
                background-color: rgba(15, 15, 15, 1)
                position: absolute
                left: 135px
                top: 15px
                width: 38px
                height: 38px
                border-radius: 50%

1 Like