Styling
Every UI element inherits a set of visual properties from the Element base class. This page covers the styling options available across all element types.
Colour
The Colour class provides predefined colour constants and methods for custom colours.
Predefined Colours
| Constant | Value |
|---|---|
Colour.blue | "blue" |
Colour.yellow | "yellow" |
Colour.red | "red" |
Colour.green | "green" |
Colour.magenta | "magenta" |
Colour.limegreen | "limegreen" |
Colour.tomato | "tomato" |
Colour.orange | "orange" |
Colour.purple | "purple" |
Colour.grey | "grey" |
Custom Colours
Use from_hex() for hex colour codes or from_string() for any HTML colour name.
from pydoover.ui import Colour
# Hex colour
custom = Colour.from_hex("#FF5733")
# HTML colour name
brown = Colour.from_string("brown")
The Doover portal accepts any valid HTML colour name or hex string, so these methods are primarily for documentation clarity in your code.
Using Colours
Colours can be applied to elements, ranges, thresholds, series, and confirm dialogs.
from pydoover.ui import NumericVariable, Range, Threshold, Colour
temperature = NumericVariable(
"Temperature",
value=22.5,
colour=Colour.red,
ranges=[
Range(label="Normal", min_val=15, max_val=30, colour=Colour.green),
Range(label="Warning", min_val=30, max_val=40, colour=Colour.yellow),
Range(label="Critical", min_val=40, max_val=60, colour=Colour.red),
],
thresholds=[
Threshold(label="Max Safe", value=40, colour=Colour.tomato),
],
)
Icons
The icon parameter accepts an icon identifier string. Icons appear alongside the element's display name in the portal.
from pydoover.ui import NumericVariable, Button, Submodule
temp = NumericVariable("Temperature", value=22.5, icon="thermometer")
restart = Button("Restart", icon="refresh")
settings = Submodule("Settings", children=[], icon="cog")
Icons are also available on UI subclass definitions:
from pydoover.ui import UI, Colour
class MyUI(UI, icon="gauge", colour=Colour.blue):
pass
Widget
The Widget class controls how NumericVariable values are visualised beyond the standard number display. Gauge widgets give operators at-a-glance visual feedback for readings like engine RPM, tank levels, or pressures — pairing a gauge with Range colour bands lets operators instantly see whether a value is in a safe operating zone.
| Widget | Value | Description |
|---|---|---|
Widget.linear | "linearGauge" | Horizontal bar gauge |
Widget.radial | "radialGauge" | Circular dial gauge |
from pydoover.ui import NumericVariable, Widget, Range, Colour
cpu_load = NumericVariable(
"CPU Load",
value=45,
units="%",
form=Widget.radial,
ranges=[
Range(min_val=0, max_val=70, colour=Colour.green),
Range(min_val=70, max_val=90, colour=Colour.yellow),
Range(min_val=90, max_val=100, colour=Colour.red),
],
)
An engine controller uses Widget.radial for the main RPM gauge and Widget.linear for secondary readings like engine load. Range colour bands give operators at-a-glance status — blue for idle, green for normal operation, yellow for approaching limits, and red for critical:
from pydoover import ui
rpm_gauge = ui.NumericVariable(
"Engine RPM",
value=MyTags.engine_rpm,
precision=0,
form=ui.Widget.radial,
ranges=[
ui.Range(label="Idle", min_val=0, max_val=800, colour=ui.Colour.blue),
ui.Range(label="Run", min_val=800, max_val=2200, colour=ui.Colour.green),
ui.Range(label="High", min_val=2200, max_val=4000, colour=ui.Colour.yellow),
],
)
engine_load = ui.NumericVariable(
"Engine Load",
value=MyTags.engine_load_pct,
units="%",
precision=0,
form=ui.Widget.linear,
ranges=[
ui.Range(label="Idle", min_val=0, max_val=15, colour=ui.Colour.blue),
ui.Range(label="Working", min_val=15, max_val=85, colour=ui.Colour.green),
ui.Range(label="High", min_val=85, max_val=100, colour=ui.Colour.yellow),
ui.Range(label="Overload", min_val=100, max_val=125, colour=ui.Colour.red),
],
)
Use Widget.radial for primary readings operators check first (RPM, speed), and Widget.linear for supporting metrics (load, temperature, pressure). Ranges should cover the full expected value span with no gaps.
Conditional Display
hidden
The hidden parameter controls whether an element is visible. It accepts a boolean, a tag reference, a Tag descriptor, or a config variable reference string for dynamic visibility.
from pydoover.ui import NumericVariable, Submodule, UI, tag_ref
from pydoover.tags import Tag, Tags
class MyTags(Tags):
show_advanced = Tag("boolean", default=False)
# Always hidden
debug_info = NumericVariable("Debug", value=0, hidden=True)
# Dynamically hidden based on a tag reference
advanced_panel = Submodule(
"Advanced",
children=[],
hidden=tag_ref("hide_advanced", tag_type="boolean", default_value=True),
)
# Dynamically hidden using a Tag descriptor directly
advanced_panel = Submodule(
"Advanced",
children=[],
hidden=MyTags.show_advanced,
)
# Dynamically hidden using a config variable reference
class OptionalUI(UI, hidden="$config.app().hide_ui"):
pass
The config variable reference form ("$config.app().field_name") reads from a config.Boolean element in the application's configuration schema, allowing operators to toggle UI visibility through the deployment config rather than a runtime tag.
is_available
Controls whether the element appears as available (enabled) or unavailable (greyed out) in the portal.
from pydoover.ui import Button
# Conditionally available
manual_btn = Button("Manual Override", is_available=False)
conditions
A dictionary of conditions that must be met for the element to be displayed. The portal evaluates these conditions against the current state.
from pydoover.ui import FloatInput
# Only show when mode is "manual"
manual_setpoint = FloatInput(
"Manual Setpoint",
conditions={"mode": "manual"},
)
Position
The position parameter controls the ordering of elements within their parent container. Lower values appear first. If not set, elements are automatically assigned incrementing positions in the order they are defined.
from pydoover.ui import NumericVariable
# Explicit positioning
first = NumericVariable("First", value=0, position=1)
second = NumericVariable("Second", value=0, position=2)
third = NumericVariable("Third", value=0, position=3)
For the UI subclass itself, position controls ordering among multiple applications on a device page:
class PrimaryUI(UI, position=1):
pass
class SecondaryUI(UI, position=50):
pass
RangeView
RangeView controls how ranges and thresholds are displayed on plots. It provides three modes:
| Mode | Value | Description |
|---|---|---|
RangeView.line | "line" | Show thresholds as horizontal lines |
RangeView.zone | "zone" | Shade the area covered by each range |
RangeView.off | "off" | Hide ranges and thresholds |
from pydoover.ui import NumericVariable, RangeView, Range, Threshold, Colour
# Use zones for ranges
tank_level = NumericVariable(
"Tank Level",
value=75,
units="%",
ranges=[
Range(label="Low", min_val=0, max_val=20, colour=Colour.red),
Range(label="Normal", min_val=20, max_val=80, colour=Colour.green),
Range(label="High", min_val=80, max_val=100, colour=Colour.yellow),
],
default_range_view=RangeView.zone,
)
# Use lines for thresholds
pressure = NumericVariable(
"Pressure",
value=50,
thresholds=[
Threshold(label="Max", value=100, colour=Colour.red),
],
default_range_view=RangeView.line,
)
When default_range_view is not set, the portal defaults to "line" when thresholds are defined, "zone" when ranges are defined, or "off" when neither is present.
show_on_graph
Each Range has a show_on_graph parameter (default True) that controls whether the range band appears on the element's data graph. Set it to False to use ranges only for gauge colouring without cluttering the graph view.
from pydoover.ui import NumericVariable, Range, Colour
rssi = NumericVariable(
"RSSI",
value=-75,
units="dBm",
ranges=[
Range("Weak", -130, -100, Colour.red, show_on_graph=True),
Range("Fair", -100, -80, Colour.yellow, show_on_graph=True),
Range("Good", -80, -50, Colour.green, show_on_graph=True),
],
)
ConfirmDialog
ConfirmDialog customises the confirmation dialog shown before an interaction executes. Pass it to requires_confirm on any interactive element.
from pydoover.ui import Button, ConfirmDialog, Colour
emergency_stop = Button(
"Emergency Stop",
requires_confirm=ConfirmDialog(
title="Emergency Stop",
subtitle="All processes will be halted immediately",
warning_reason="This action cannot be undone remotely",
colour=Colour.red,
help_text="Press confirm to stop all running equipment",
icon="alert-octagon",
),
)
| Parameter | Description |
|---|---|
title | Dialog title (defaults to "Confirm {display_name} change") |
subtitle | Text shown below the title |
warning_reason | Warning message explaining why confirmation is needed |
colour | Dialog accent colour |
help_text | Additional explanatory text |
icon | Icon shown in the dialog header |
For a simple default confirmation dialog, just pass True:
delete_btn = Button("Delete Data", requires_confirm=True)
An engine controller uses ConfirmDialog on safety-critical controls to warn operators about the consequences of remote commands. The warning_reason highlights the risk, and help_text explains what will happen:
from pydoover import ui
stop_btn = ui.Button(
"Stop Pump",
requires_confirm=ui.ConfirmDialog(
title="Stop the pump?",
warning_reason=(
"This will shut the engine down. Anyone relying on "
"flow downstream will lose it."
),
help_text=(
"Sends a remote STOP command to the panel. "
"The engine will enter Cooldown for ~30 seconds "
"and then stop."
),
),
)
rpm_slider = ui.Slider(
"Target RPM",
min_val=600,
max_val=2400,
step_size=10,
dual_slider=False,
inverted=False,
requires_confirm=ui.ConfirmDialog(
title="Change target RPM?",
help_text=(
"Sets the engine speed setpoint while the pump is "
"running. The value is clamped to the configured "
"Target RPM Min / Max."
),
),
)
ConfirmDialog works on any interactive element — Button, Switch, Slider, Select, etc. Use warning_reason for destructive or irreversible actions and help_text for additional context.
ApplicationVariant
ApplicationVariant controls how applications are displayed on the device page. This is set on the Application container or through the UI framework's internal wiring.
| Variant | Value | Description |
|---|---|---|
ApplicationVariant.submodule | "submodule" | Embeds the app inside a collapsible submodule (default) |
ApplicationVariant.stacked | "stacked" | Places elements directly on the page without submodule wrapping |
Next Steps
- UI Overview -- introduction to the UI system
- Display Elements -- read-only variables and charts
- Interactive Elements -- user input controls