There is a lot of institutional knowledge here on the forum and in the docs, but I just spent a couple of days updating a couple of my widgets, and I’ve been struggling.
I’ve read through parts of a bunch of tutorials and documents and looked at many forum postings and looked at examples, but I still feel lost.
Should I be using f7-block, f7-row, and f7-col all the time?
Should I always use a block as the outermost container or could I start with a column or a row?
If yes, should there be only one widget at the bottom of the hierarchy?
Where do I go to find all the “classes” supported by these and maybe a little description of the behavior. For example, if I set a class “justification-content-center” on the f7-block, is it supposed to apply to everything in that block?
How do I make a widget that sizes its height based on the content of the widget instead of hard coding it?
What the heck does the display-flex class do, and when should I use it?
What are the defaults when I don’t set any classes or style to the blocks, rows and cols?
When should I use absolute positioning instead of relative?
The oh-X stuff seem very well documented and I never feel lost using them. But I don’t feel like I have a grasp of the overall proper way to use blocks, rows, and cols to build a widget that looks like I want yet resizes in meaningful ways. And when I follow the links in the docs or search online I feel like I’m dropped into the deep end of a vast pool of information and can’t seem to find what I’m looking for.
A concrete example might be worthwhile for discussion purposes.
There are warnings about not overly nesting the cols, rows and block because of performance, but what is too much? What’s the alternative?
I had an old widget that was pretty hard coded with lost of absolute positioning. I’ve recently tried to rewrite it using best practices, and I am not at all happy with the results.
Here’s the code.
uid: new_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: Nov 6, 2025, 8:35:02 AM
component: f7-list-item-row
config: {}
slots:
default:
- component: oh-context
config:
constants:
it:
display: =props.equ+'_Display'
empty: =props.equ+'_WaterLowEmpty'
level: =props.equ+'_HumidityLevel'
mist: =props.equ+'_MistLevel'
mode: =props.equ+'_OperationMode'
power: =props.equ+'_SwitchedOn'
sp: =props.equ+'_HumiditySetPoint'
warm: =props.equ+'_WarmLevel'
warmOn: =props.equ+'_WarmModeEnabled'
functions:
color: "=(level) => (level > 45) ? 'purple' : (level > 30) ? 'blue' : 'orange'"
slots:
default:
- component: div
config:
class:
- card
- card-content-padding
style:
boarder-radius: var(--f7-card-expandable-board-radius)
height: 155px
noShadow: true
width: 100%
slots:
default:
- component: f7-block
slots:
default:
- component: f7-row
config:
class:
- justify-content-center
- align-items-start
slots:
default:
- component: f7-col
config:
class:
- align-items-start
slots:
default:
- component: f7-icon
config:
color: =fn.color(#const.it["level"])
f7: drop
size: 20
- component: f7-col
slots:
default:
- component: Label
config:
style:
color: =fn.color(#const.it["level"])
font-size: 14px
font-weight: 700
margin-top: 0px
width: 200px
text: "=(@@const.it['power'] == 'OFF') ? 'Humidifier is off' : props.title + '
is set to ' + @@const.it['sp']"
- component: f7-col
config:
class:
- align-items-end
slots:
default:
- component: f7-chip
config:
style:
--f7-chip-bg-color: rgba(255, 255, 255, 0)
color: =fn.color(#const.it['level'])
font-size: 14px
position: absolute
right: 0px
top: 0px
width: 12%
text: =@@const.it['level']
- component: f7-row
slots:
default:
- component: f7-col
config:
style:
width: 100%
slots:
default:
- component: oh-slider
config:
color: =fn.color(#const.it["level"])
item: =const.it['sp']
label: true
max: 80
min: 20
releaseOnly: true
step: 1
style:
top: px
width: 100%
unit: "%"
- component: f7-row
slots:
default:
- component: f7-col
slots:
default:
- component: f7-row
config:
class:
- justify-content-center
- align-items-center
slots:
default:
- component: oh-knob
config:
item: =const.it['mist']
max: 3
min: 0
size: 80
stepSize: 1
style:
color: =fn.color(#const.it["level"])
textColor: =fn.color(#const.it["level"])
- component: f7-row
config:
class:
- justify-content-center
- align-items-start
slots:
default:
- component: Label
config:
class:
- justify-content-center
style:
color: =fn.color(#const.it["level"])
font-size: 10px
font-weight: 500
text: Mist Level
unit: "%"
- component: f7-col
slots:
default:
- component: f7-row
config:
class:
- justify-content-center
- align-items-center
slots:
default:
- component: oh-knob
config:
item: =const.it['warm']
max: 3
min: 0
primaryColor: orange
size: 80
stepSize: 1
style:
color: orange
textColor: orange
- component: f7-row
config:
class:
- justify-content-center
- align-items-center
slots:
default:
- component: Label
config:
style:
color: orange
font-size: 10px
font-weight: 500
text: Warm Mode
- component: f7-col
slots:
default:
- component: Label
config:
style:
color: "=(@const.it['empty'] == 'ON') ? 'red' : fn.color(#const.it['level'])"
font-size: 14px
font-weight: 700
text: "=(@const.it['empty'] == 'ON') ? 'Water: empty' : 'Water: ok'"
- component: Label
config:
style:
color: =fn.color(#const.it["level"])
font-size: 14px
font-weight: 700
text: "='Mode: ' + @@const.it['mode']"
- component: f7-col
config:
class:
- align-items-end
slots:
default:
- component: f7-row
config:
class:
- align-items-end
slots:
default:
- component: f7-col
config:
class:
- align-items-end
slots:
default:
- component: Label
config:
style:
color: =fn.color(#const.it["level"])
font-size: 14px
font-weight: 700
text: Power
- component: f7-col
config:
class:
- align-items-end
slots:
default:
- component: oh-toggle
config:
item: =const.it['power']
color: =fn.color(#const.it["level"])
fill: true
- component: f7-row
config:
class:
- align-items-end
slots:
default:
- component: f7-col
config:
class:
- align-items-end
slots:
default:
- component: Label
config:
style:
color: =fn.color(#const.it["level"])
font-size: 14px
font-weight: 700
text: Display
- component: f7-col
config:
class:
- align-items-end
slots:
default:
- component: oh-toggle
config:
item: =const.it['display']
color: =fn.color(#const.it["level"])
fill: true
What I don’t like and haven’t figured out how to address:
- How do I get rid of that space at the top? Alternatively, how do I set the block title if I can’t get rid of that space?
- Can I get the icon all the way to the left so it lines up with the start of the slider?
- Same for the percent in the upper right.
- I want the two arcs to remain close together pinned to the left when it resizes and the label and toggles to stay together at the right when the widget resizes. But everything spreads apart.
- This widget doesn’t seem to display consistently on the pages. I think it has to do with the hard coded height. How do I make it consistent? I had to make the height larger than necessary to keep the bottom of the second widget from overflowing into the widget below it. This may be a more specific question since this is an odd widget being used in a list card.
I feel like I’m really close to having a good understanding and then when I try to apply my understanding, either things don’t change at all or change in unexpected ways which shows I don’t really understand.
Any advice for MainUI Widget experts would be most welcome. I’m not necessarily looking to fix this specific widget as much as I am looking for understanding which I can apply to my other widgets and helping on the forum going forward.












