Widget elements
A widget spec’s elements array is a flat list of typed objects drawn in
order — later elements sit on top of earlier ones. This page is the field
reference for every element type, the fields every element shares, and the
higher-level structures (pages, layers, menu theming) that sit around them.
For the file layout, top-level spec fields, and the serial protocol, see Widget spec. For live data binding and events, see Data & events.
Shared fields
Section titled “Shared fields”Every element type accepts these fields, in addition to whatever fields are specific to its type:
| field | meaning |
|---|---|
t |
element type (label, bar, arc, img, panel, led, line, qr, btn, slider, toggle) |
x y |
position, top-left origin |
c |
main color, a hex string like "1DB954" |
bind |
data key to subscribe to — the element refreshes live when the key updates |
fmt |
text template, e.g. "{v}%" — {v} is replaced with the bound value. Never a printf format string |
show |
layer visibility key — see Layers below |
cbind |
color bind — the key’s hex value drives the element’s accent color live (bars that go red past 90%, panels that flash on alerts) |
obind |
opacity bind — the key’s value (0-255) fades the element live |
o |
static opacity, 0-255, on any element |
pulse |
1 = infinite opacity breathe — blinking alert LEDs with zero PC traffic |
{ "t": "label", "x": 10, "y": 6, "w": 120, "s": 14, "c": "8E8E93", "text": "CPU" }Icons — sym
Section titled “Icons — sym”"sym": "wifi" turns a label or btn’s text into an LVGL symbol glyph
instead of text. Friendly names include play pause wifi home settings warning bell power trash refresh up down left right ok close volume_max mute battery_full charge bluetooth usb gps eye_open and more, or you can pass raw
LV_SYMBOL_* names. Symbol font sizes run 12-48.
Combine sym with bind to make the data value itself the symbol name —
useful for a live status icon:
WDATA,conn,wifi → shows the wifi glyphWDATA,conn,close → flips to the close glyphClock / date — reserved keys
Section titled “Clock / date — reserved keys”While a custom widget is active, the PC auto-pushes _time (12h),
_time24, _ampm, and _date every 15 seconds — no element setup needed
beyond binding to them. A watchface is just:
{ "t": "label", "s": 38, "bind": "_time" }Element reference
Section titled “Element reference”| field | meaning |
|---|---|
w |
width in px — text truncates with …; omit for auto width |
s |
font size: 12 / 14 / 16 / 18 / 22 / 38 (nearest bundled Montserrat) |
a |
align within w: "c" center, "r" right (default left) |
text |
static text, used when there’s no bind |
bind + fmt |
live text, e.g. "fmt": "{v}°C" |
scroll |
(v1.2) 1 = circular ticker for long text; needs w |
{ "t": "label", "x": 10, "y": 22, "w": 120, "s": 38, "c": "FFFFFF", "bind": "cpu_load", "fmt": "{v}%" }| field | meaning |
|---|---|
w h |
size (default 100×8); make h greater than w for a vertical bar (fills bottom-up) |
c / bgc |
fill / track colors |
r |
corner radius (default h/2) |
bind min max |
value binding; the value is parsed as an integer and clamped to [min, max] |
{ "t": "bar", "x": 10, "y": 72, "w": 130, "h": 8, "c": "1DB954", "bgc": "2C2C2E", "bind": "cpu_load", "min": 0, "max": 100 }arc (ring gauge)
Section titled “arc (ring gauge)”| field | meaning |
|---|---|
d |
diameter (default 64) |
w |
ring thickness (default 6) |
c / bgc |
indicator / track colors |
bind min max |
value binding |
fmt ls lc |
optional built-in centered value label — auto-centered in the ring, font size ls, color lc. Uses one extra element slot. Prefer this over a manually positioned label, which can’t truly center |
a0 a1 rot |
(v1.2) background sweep in degrees, plus rotation — for speedometer-style partial gauges, e.g. "a0":0,"a1":270,"rot":135 |
{ "t": "arc", "x": 156, "y": 12, "d": 86, "w": 8, "c": "FF9F0A", "bgc": "2C2C2E", "bind": "mem_load", "min": 0, "max": 100, "fmt": "{v}%", "ls": 18 }| field | meaning |
|---|---|
src |
filename inside the widget’s own folder, e.g. "logo.png" (RGB565-friendly PNG) |
bind |
(v1.2) the data value picks which .png from the widget folder shows — weather icons, mode badges |
{ "t": "img", "x": 20, "y": 20, "src": "logo.png" }panel (rounded rectangle)
Section titled “panel (rounded rectangle)”| field | meaning |
|---|---|
w h r |
size + corner radius |
c o |
fill color + opacity, 0-255 |
led (status light)
Section titled “led (status light)”| field | meaning |
|---|---|
d |
diameter (default 14) |
c / offc |
ON / OFF colors |
bind |
a hex value sets that exact color; a truthy/falsy value maps to on/off |
line (divider)
Section titled “line (divider)”| field | meaning |
|---|---|
w h |
size (default 100×2) |
c o r |
color, opacity, radius |
qr (QR code — LVGL built-in)
Section titled “qr (QR code — LVGL built-in)”| field | meaning |
|---|---|
d |
size (default 90 — mind the 140px panel height) |
c / bgc |
dark / light module colors |
text or bind |
content; a bound key re-renders the code live |
Interactive elements
Section titled “Interactive elements”btn, slider, and toggle are the only elements that can fire events back
to the PC. Each requires a unique id, and each can carry a macro field so
a button/toggle press can run a saved PC macro directly — zero-code path,
no plugin needed. See Data & events
for the full event/protocol picture and Macros
for authoring macros.
btn (momentary button)
Section titled “btn (momentary button)”| field | meaning |
|---|---|
id |
required — event id sent to the PC |
w h r |
size + radius (default 80×30, r 8) |
c tc s |
background color, text color, font size |
text or bind+fmt |
label — bindable, so the PC can relabel it live |
hold |
1 = a 600 ms long-press fires a hold event instead of press |
macro |
PC macro name to run on press |
page |
widget page to jump to on press (local, instant) |
Tap → pressed visual → WEVENT,<widget>,<id>,press on release.
{ "t": "btn", "id": "ping", "x": 20, "y": 100, "w": 90, "h": 30, "c": "2C2C2E", "tc": "FFFFFF", "text": "Ping" }slider (drag input)
Section titled “slider (drag input)”| field | meaning |
|---|---|
id |
required — event id |
w h |
size (default 140×12, horizontal) |
c bgc kc |
fill / track / knob colors |
min max val |
range + initial value |
bind |
PC-authoritative value between drags (reconcile pattern) |
macro |
PC macro run on release |
Dragging sends a live WEVENT,<widget>,<id>,value=<n> (throttled ~150 ms)
plus a final one on release. A drag owns the touch gesture — it never
triggers a widget-swipe.
toggle (switch)
Section titled “toggle (switch)”| field | meaning |
|---|---|
id |
required — event id |
w h |
size (default 46×26) |
c bgc |
checked color / track color |
bind |
state key — the PC pushes 1/0 and wins over the local flip |
macro |
PC macro name run on every flip |
Tap flips instantly (optimistic) and sends
WEVENT,<widget>,<id>,on or off. If the toggle is bound, the plugin
should echo the authoritative state back with push_widget_data — the same
reconcile pattern used by slider.
Layers — show
Section titled “Layers — show”"show": "<key>" makes an element visible only while that data key is
truthy (truthy = non-empty and not 0 / off / false). Flip one WDATA
key to swap whole element groups in or out — state layers, alert overlays,
fake tabs — without touching pages.
{ "t": "label", "x": 10, "y": 40, "c": "FF3B30", "text": "LOW BATTERY", "show": "low_batt" }Pages — clock-style sub-screens
Section titled “Pages — clock-style sub-screens”A widget can define up to 6 pages; the 48-element budget is shared across all of them.
{ "mixw": 1, "name": "X", "bg": "101018", "pages": [ { "name": "Dash", "elements": [ /* ... */ ] }, { "name": "Controls", "elements": [ /* ... */ ] }, { "name": "My Menu", "hidden": true, "elements": [ /* ... */ ] }] }- Single-page specs need no
pageskey at all — just a top-levelelementsarray. - Tapping empty widget space opens an overlay menu of page names (like the clock’s sub-mode menu); tapping a row switches pages.
btnelements can jump pages directly via theirpagefield, bypassing the overlay.- Mark a page
"hidden": trueto keep it out of the built-in tap menu while it stays reachable viabtnpagejumps orWPAGE— handy for hand-built menu pages and sub-screens. - Every switch announces
WEVENT,<widget>,_page,<name>so plugins can push page-specific data, and the PC can force a page withWPAGE,<name>.
Menu theming — top-level menu block
Section titled “Menu theming — top-level menu block”The built-in tap-menu overlay can be restyled from a top-level "menu"
block. All fields are optional — anything omitted keeps the stock look.
"menu": { "w": 180, "h": 32, "gap": 6, "r": 16, "s": 16, "c": "26263A", "selc": "9A9AFF", "tc": "FFFFFF", "bgc": "000000", "o": 190 }| field | meaning | field | meaning |
|---|---|---|---|
w h |
row size (clamped 60-236 / 18-60) |
c |
row color |
gap |
space between rows | selc |
current-selection color |
r |
row corner radius | tc |
row text color |
s |
row font size | bgc / o |
veil color / opacity, 0-255 |
Next steps
Section titled “Next steps”- Widget spec — file layout, top-level fields, and the serial protocol
- Data & events —
push_widget_data,on_widget_event, pairing and namespacing - Widget builder — build specs visually instead of hand-writing JSON