Style
Overview
This page explains the semantics of the style fields. Public JSON uses camelCase.
This page covers only style. For control semantics see View; for field-level props see Props.
Style Composition Order
style is an optional object. The final style merge order is currently fixed as:
built-in default
theme.styles.allbuilt-in default
theme.styles.<node type>current Runtime theme
styles.allcurrent Runtime theme
styles.<node type>named styles referenced by
styleRefsin the current Runtime themethe node's explicit
stylethe backend's minimal technical fallback
Where:
gui_interfacealways applies a built-in default theme firstthe Runtime external theme is an optional overlay, not a source of defaults
the node's explicit
stylealways overrides the built-in default theme and the external theme field by fieldfields not set by any theme or by the node fall back, last, to the backend's minimal technical fallback
Each time the backend applies a style it maps the final ResolvedStyle to its own native style object. An omitted
style field does not equal the backend default; most formal defaults come from the built-in default theme, and the backend only provides an implementation-level safety fallback.
Runtime view debug overlays the backend debug outline:
this visualization is not
style.marginit does not participate in layout and does not change
placement,layout, the real margin, or the final frameits goal is to help observe control size and position, not to express formal UI style
Field Table
Key |
Type |
Default |
UI Effect |
Backend Behavior / Limits |
|---|---|---|---|---|
|
string, |
|
main background color |
applies the background color when non-empty and parsable; transparent when finally empty |
|
string, |
|
main background transition color |
works with |
|
enum |
|
background gradient direction |
allowed values: |
|
integer, |
backend default |
main background color stop |
LVGL background gradient stop; meaningful only when a gradient is configured |
|
integer, |
backend default |
transition color stop |
LVGL background gradient stop; meaningful only when a gradient is configured |
|
integer, |
|
transition color opacity |
LVGL background gradient opacity |
|
string, |
|
text color |
applies the text color |
|
string enum, |
|
horizontal text alignment |
maps to backend text alignment |
|
string, |
|
border color |
if |
|
string, |
|
line control line color |
applies the line color |
|
string, |
|
arc line color |
applied to the LVGL arc style; the indicator is usually written in |
|
string, |
|
arc line transition color |
the LVGL backend approximates a two-color gradient for arc using segmented drawing |
|
string, must be |
|
text font resource |
the parser expands it to a font id; the runtime resolves it to a backend-usable font resource; when the font set has no match, |
|
number px or |
|
text size |
when the field exists, |
|
number px or |
the imageFont first-tier size |
imageFont glyph size selection |
effective only for |
|
number px or |
|
border width |
overrides the theme when the field is explicitly present; the backend falls back to |
|
number px or |
|
corner radius |
overrides the theme when the field is explicitly present; the backend falls back to |
|
number px or |
|
padding on all four sides |
used as the four-side default; per-side fields can override it |
|
number px or |
|
left padding |
if unset, falls back to |
|
number px or |
|
right padding |
if unset, falls back to |
|
number px or |
|
top padding |
if unset, falls back to |
|
number px or |
|
bottom padding |
if unset, falls back to |
|
number px or |
|
margin on all four sides |
used as the four-side default; per-side fields can override it |
|
number px or |
|
left margin |
if unset, falls back to |
|
number px or |
|
right margin |
if unset, falls back to |
|
number px or |
|
top margin |
if unset, falls back to |
|
number px or |
|
bottom margin |
if unset, falls back to |
|
number px or |
|
shadow size |
inherits the theme layer when omitted on the node |
|
number px or |
|
shadow X offset |
applies the shadow X offset |
|
number px or |
|
shadow Y offset |
applies the shadow Y offset |
|
string, |
|
shadow color |
applied when the final color is non-empty and parsable |
|
integer, |
|
overall opacity |
overrides the theme when the field is explicitly present; the backend falls back to |
|
number px or |
|
line control line width |
applies the line width |
|
integer, |
|
image draw opacity |
overrides the theme when the field is explicitly present; the backend falls back to |
|
string, |
|
image draw recolor |
an empty string disables style-layer recoloring |
|
integer, |
|
image recolor blend strength |
has a visual effect only when |
|
number px or |
backend default |
arc line width |
may be written in |
|
integer, |
|
arc line opacity |
may be written in |
|
integer, |
|
number of arc gradient segments |
more segments give a smoother result at a higher drawing cost |
|
boolean |
backend default |
arc endpoint rounding |
maps to LVGL arc rounded |
|
boolean |
backend default |
whether to clip child content to the corner radius |
maps to LVGL clip corner; usually used with |
State Styles
A node can use stateStyles to provide local style overrides for a single interaction state. stateStyles follows the same field rules as style,
but does not support font / fontSize / imageFontSize, to avoid recreating the font cache on state changes.
{
"type": "button",
"id": "open",
"styleRefs": ["app.card"],
"stateStyles": {
"pressed": {"bgColor": "#e2e8f0"},
"disabled": {"opacity": 120},
"checked": {"borderColor": "#2563eb", "borderWidth": "2dp"}
}
}
Supported state names:
State |
Meaning |
|---|---|
|
being pressed |
|
checked; e.g. switch / checkbox, or a control with a manually set checked state |
|
focused |
|
keyboard/arrow-key focus |
|
editing state |
|
pointer hover |
|
scrolling |
|
disabled state |
|
reserved user states |
Composition rules:
the composition order of the base
styleis unchanged.each state's override style is composed at the same layers: built-in theme, current theme,
styleRefs, the node's ownstateStyles.a state style writes only explicit fields; unset fields inherit the final style of the normal state.
only a single-state selector is supported; combined states like
pressed+checkedare not.binding / event effects can write
stateStyles.<state>.<styleField>, for examplestateStyles.pressed.bgColor.
Part Styles
partStyles sets styles for a control's internal parts. The existing style always refers to the main part; partStyles
refers to other parts of the same view. Currently supported:
Part |
Common controls |
Meaning |
|---|---|---|
|
|
filled progress, the slider's selected track, the arc indicator |
|
|
drag handle |
Both a direct style shorthand and style + stateStyles are supported:
{
"type": "slider",
"id": "brightness",
"partStyles": {
"indicator": {
"bgColor": "#2563eb",
"bgGradientColor": "#38bdf8",
"bgGradientDirection": "horizontal"
},
"knob": {
"style": {
"bgColor": "#ffffff",
"radius": "14dp"
},
"stateStyles": {
"pressed": {"bgColor": "#e0f2fe"}
}
}
}
}
Composition rules:
styleis still composed as themainpart.each part's style is composed at the same layers: built-in theme, current theme,
styleRefs, the node's ownpartStyles.partStyles.<part>.stateStylesalso supports state overrides.partStylesdoes not supportfont/fontSize/imageFontSize.binding / event effects can write fields such as
partStyles.indicator.bgGradientColorandpartStyles.knob.stateStyles.pressed.bgColor.
Range Gradient
The partStyles.indicator of progressBar and slider uses an LVGL background gradient, suitable for horizontal/vertical progress colors:
"partStyles": {
"indicator": {
"bgColor": "#22c55e",
"bgGradientColor": "#3b82f6",
"bgGradientDirection": "horizontal"
}
}
arc has no native arc gradient; the LVGL backend approximates one with segmented drawing when partStyles.indicator.arcGradientColor is present:
"partStyles": {
"indicator": {
"arcColor": "#22c55e",
"arcGradientColor": "#3b82f6",
"arcWidth": "10dp",
"arcRounded": true,
"arcGradientSegments": 48
}
}
Theme Default Style Layer
The runtime always applies a built-in default theme before resolving a node's final style. If the Runtime has additionally loaded a global theme and selected the current theme via set_theme(...), it also continues to auto-apply:
styles.allstyles.<node type>
Example:
{
"id": "dark",
"styles": {
"all": {
"bgColor": "#020617"
},
"label": {
"textColor": "#f8fafc"
}
}
}
If a label node then explicitly writes:
"style": {
"textColor": "#ef4444"
}
then the final textColor uses the node's explicit value.
A color field accepts two forms: a literal #RRGGBB, or a color reference ${color.<path>} (resolved from the current theme's colors).
${color.*} is allowed only on color style fields such as bgColor, textColor, borderColor, lineColor, arcColor,
shadowColor, imageRecolor and their corresponding stateStyles / partStyles fields. An empty string means the color is not set;
illegal colors or other CSS-style forms raise an error in the document validator stage and never reach the backend application flow.
Font and Font Size
font must use a resource reference:
{
"style": {
"font": "${font.title}",
"fontSize": "${constant.metrics.titleFont}",
"textColor": "${constant.colors.primaryText}"
}
}
Resource reference rules:
font: "${font.title}"is kept as a font resource reference in the parser and is not substituted like an ordinary constant.the runtime merges JSON font resources and backend dynamic font resources.
if both JSON and backend provide a font with the same name, the backend dynamic resource wins.
font: "title",${font:title}, or writing a font file path directly intostyle.fontare not supported.
Font size rules:
fontSizeshould use"Nsp"or an"Nsp"resolved from${constant.*}.the
spformula isround(N * Environment.density * Environment.font_scale).a bare number is an already-converted px and is not affected by
font_scale.when
fontSizeis not set explicitly, the built-in default theme provides16.if a font must be applied but the final
fontSizeis0, the backend font selection logic uses the default size16.imageFontSizeis recommended for nodes whosestyle.fontpoints tokind=imageFont, such as inline icons in a label. After convertingspto px it only selects the imageFont glyph image size; ordinary characters are still controlled byfontSize.
Backend font priority:
the native font in
ResolvedFontSpec.native_fontsclosest to the requested size.the
primary_srcof the JSON/backend font resource, creating FreeType fonts along the fallback chain.the built-in Montserrat font; it also falls back to the built-in font when FreeType or the file is unavailable.
Size and Spacing
Size-class style fields accept a bare number px or "Ndp":
{
"style": {
"padding": "${constant.metrics.sectionPadding}",
"borderWidth": "1dp",
"radius": "${constant.radii.card}",
"shadowWidth": "8dp",
"shadowOffsetY": "2dp"
}
}
Current rules:
each style layer overrides the previous one only when the field is explicitly present; a field omitted on the node inherits the final merged value of the built-in theme or the current Runtime theme.
the
alllayer of the built-in default theme provides numeric baselines such asborderWidth/radius/padding/margin/shadowWidth/lineWidth.when
paddingand per-sidepadding*both exist, the backend uses the finalpaddingas the fallback and then overrides it per side.marginand per-sidemargin*work the same way.color and font ids still keep the semantics that an empty string means no specific resource/color is set.
Transparency
{
"style": {
"opacity": 220,
"imageOpacity": 180,
"imageRecolor": "#0f172a",
"imageRecolorOpacity": 255
}
}
Notes:
opacityaffects the overall opacity of the object.imageOpacityaffects image draw opacity.imageRecolor/imageRecolorOpacityaffect the image draw color and can be placed in a theme style so icons adapt to the theme uniformly.the built-in default theme defaults are all
255, meaning fully opaque; an external theme or an explicit node field can override this.the current parser only requires an integer; the validator further restricts it to
0..255; values out of range fail document validation.
Example
The example below shows typical style and resource-reference usage, keeping resource references in strict namespaces:
{
"type": "container",
"id": "content",
"style": {
"padding": "${constant.metrics.sectionPadding}",
"bgColor": "${constant.colors.cardBg}",
"borderColor": "${constant.colors.border}",
"borderWidth": "1dp",
"radius": "${constant.radii.card}"
},
"children": [
{
"type": "label",
"id": "title",
"labelProps": {
"text": "Controls Gallery"
},
"style": {
"font": "${font.title}",
"fontSize": "${constant.metrics.titleFont}",
"textColor": "${constant.colors.primaryText}"
}
},
{
"type": "line",
"id": "line",
"style": {
"lineColor": "${constant.colors.accent}",
"lineWidth": "3dp"
}
}
]
}