Appearance
Custom UI Language
Lua scripts can pass the language directly to neon.register_draggable_ui_feature, provide values for bindings, find nodes by ID, change node properties, and connect click handlers.
Complete Lua example
lua
local ui_source = [[
box {
width: 100%;
height: 100%;
rect {
id: "draggable-root";
draggable: true;
width: 180px;
color: rgba(15, 15, 20, 0.92);
corner-radii: 8;
layout: vertical;
text {
text: #title;
color: white;
font-size: 10;
margin: 7;
}
for row in #rows {
box {
layout: horizontal;
justify: space-between;
padding: 4 7;
text { text: #row.name; color: gray; font-size: 8; }
text { text: #row.value; color: #accent; font-size: 8; }
}
}
rect {
id: "button";
height: 20px;
margin: 7;
color: rgba(255, 255, 255, 0.08);
hover-color: rgba(255, 255, 255, 0.16);
corner-radii: 5;
align: center;
justify: center;
text { text: #button-label; color: white; font-size: 8; }
}
}
}
]]
local feature = neon.register_draggable_ui_feature(
"UI Example",
"A script-provided draggable UI",
ui_source
)
local clicks = 0
feature.provide_property("title", "Lua UI")
feature.provide_property("accent", types.color.new(214, 65, 112))
feature.provide_property("button-label", function()
return "Clicked " .. clicks .. " time(s)"
end)
feature.provide_property("rows", function()
return {
{ name = "Player", value = "Example Name" },
{ name = "Clicks", value = tostring(clicks) }
}
end)
feature.connect("button", function(node)
clicks = clicks + 1
node.set_property("opacity", clicks % 2 == 0 and 1.0 or 0.8)
end)The source is parsed when the feature is registered. Invalid syntax or a missing draggable-root makes registration fail.
Registering a draggable UI feature
neon.register_draggable_ui_feature
lua
local feature = neon.register_draggable_ui_feature(name, description, ui_source)| Argument | Type | Meaning |
|---|---|---|
name | string | Feature name shown in Neon. |
description | string | Feature description. |
ui_source | string | Complete custom UI language source text. |
The returned object includes all normal types.feature methods and the UI-specific methods below.
Every draggable feature must contain exactly one node with:
text
id: "draggable-root";The feature automatically adds X Position and Y Position settings. The root can be dragged while the Minecraft chat screen is open.
feature.provide_property
lua
feature.provide_property(name, value)
feature.provide_property(name, function()
return current_value
end)Provides data used by #name, ${name}, expressions, and for blocks. A function is called once per render binding pass and should take no arguments.
Supported Lua values are:
| Lua value | UI value |
|---|---|
nil | Missing value |
boolean | Boolean |
number | Number |
string | String |
types.color | Color |
| Sequential table | List, usable by for |
| Key/value table | Map, usable as #item.field inside for |
| Userdata | Original Java object, used for values such as textures |
feature.get_node
lua
local node = feature.get_node("button")Returns a types.ui_node for a parsed node ID. IDs should be unique in a scene. Nodes generated inside a for block are dynamic and should not be looked up by ID.
feature.connect
lua
feature.connect("button", function(node)
print("Clicked " .. node.id)
end)Connects a click handler by ID. The callback receives the clicked node.
For Lua callbacks, prefer feature.connect or node.on_click over the DSL on-click property.
Lua UI node API
The object returned by feature.get_node and click callbacks exposes these read-only fields:
| Field | Type | Meaning |
|---|---|---|
id | string or nil | DSL node ID. |
x, y | number | Position relative to the parent. |
width, height | number | Current bounds. |
absolute_x, absolute_y | number | Current screen position. |
hovered | boolean | Whether the node is currently hovered. |
active | boolean | Whether the node is currently pressed/active. |
focused | boolean | Whether the node owns keyboard focus. |
node.set_property
lua
node.set_property("color", types.color.RED)
node.set_property("width", "75%")
node.set_property("display", "none")Applies any property supported by that node type. Use strings for values with units or keywords. Lua booleans and numbers can be passed directly.
node.on_click
lua
local button = feature.get_node("button")
button.on_click(function(node)
node.set_property("opacity", 0.5)
end)Connects a click handler directly to the node.
Language syntax
Nodes and properties
text
node-type {
property-name: value;
child-type {
property-name: value;
}
}Supported node types are box, rect, image, circle, text, and text-input. image is an alias of rect.
Properties end with ;. Braces delimit children. Property and node names are case-insensitive where their value parser says so, but lowercase is recommended. The parser supports line comments:
text
// This comment continues to the end of the line.Strings use double quotes. Quotes do not currently have an escape syntax, so a literal " cannot be embedded in a quoted value.
Sizes
Size and position properties accept:
text
width: 120; // fixed UI units
width: 120px; // same as 120
width: 75%; // percentage of parent
width: auto; // content/layout controlled; width and height onlymin-width and min-height accept fixed or percentage values. Numeric spacing properties such as padding and margin do not accept % or px.
Edge shorthand
padding and margin accept one, two, or four numbers:
text
padding: 8; // all sides
padding: 6 10; // vertical, horizontal
padding: 4 6 8 10; // top, right, bottom, leftColors
text
color: white;
color: transparent;
color: #f4a;
color: #ff44aa;
color: #80ff44aa;
color: rgb(255, 68, 170);
color: rgba(255, 68, 170, 0.5);
color: hsb(0.92, 0.73, 1.0);
color: hsba(0.92, 0.73, 1.0, 0.5);Named colors are white, black, red, green, blue, yellow, cyan, magenta, gray, orange, and transparent.
RGB channels use 0 through 255. HSB channels and alpha use 0.0 through 1.0. Eight-digit hex is ARGB, not RGBA.
Direct bindings
A value consisting only of #name preserves its provided type:
text
text: #status;
color: #accent;
display: #visibility;Nested values in loop maps use dot paths such as #player.name.
String interpolation
${name} inserts a provided value into surrounding text:
text
text: "Speed: ${speed} blocks/s";This ${...} syntax is also used for component parameter substitution. In component declarations it is parse-time substitution.
Expressions
Expressions support numbers, quoted strings, variables, parentheses, and:
| Operator | Meaning |
|---|---|
+ | Numeric addition, or string concatenation if either value is a string |
- | Numeric subtraction |
* | Numeric multiplication |
/ | Numeric division; division by zero produces 0 |
Standard precedence applies: multiplication and division run before addition and subtraction.
text
width: #health * 1.5 + 10;
height: (#rows * 12) + 8;
text: "Count: " + #count;
color: rgba(255, 255, 255, #opacity * 0.8);
color: hsba(#hue, 0.8, 1.0, #opacity);The expression color functions are rgb, rgba, hsb, and hsba.
Loops
Use a Lua sequential table as the loop source:
text
for item in #items {
box {
layout: horizontal;
text { text: #item.name; }
text { text: #item.value; }
}
}lua
feature.provide_property("items", function()
return {
{ name = "FPS", value = "144" },
{ name = "Ping", value = "32 ms" }
}
end)Reusable components
Declare a component before using it:
text
component Label {
text {
text: ${value};
color: ${color};
font-size: 9;
}
}
box {
Label { value: "First"; color: red; }
Label { value: "Second"; color: white; }
}Component arguments are token substitutions. They are not defaulted and are not Lua values unless the substituted result itself contains a binding.
Common properties
Every node type supports these properties:
| Property | Values | Description |
|---|---|---|
id | string | Unique lookup/connection ID. |
width, height | size | Fixed, %, px, or auto. |
min-width, min-height | size | Fixed or percentage minimum. |
aspect-ratio | number | Width-to-height ratio. |
position | relative, absolute | Yoga position mode; any value other than absolute is relative. |
left, top, right, bottom | size | Fixed or percentage position offset. |
layout, flex-direction | row, horizontal, column, vertical, row-reverse, column-reverse | Child layout direction. |
justify, justify-content | start, flex-start, center, end, flex-end, between, space-between, around, space-around | Main-axis child alignment. |
align, align-items | start, flex-start, center, end, flex-end, stretch | Cross-axis child alignment. On text, this controls text alignment instead. |
align-self | same as align | Overrides this node's cross-axis alignment. |
flex | number | Yoga flex shorthand. |
flex-grow | number | Remaining-space growth factor. |
flex-shrink | number | Shrink factor. |
gap | number | Gap between children. |
margin | edge shorthand | Outer spacing. |
margin-top, margin-right, margin-bottom, margin-left | number | Individual outer spacing. |
padding | edge shorthand | Inner spacing. |
padding-top, padding-right, padding-bottom, padding-left | number | Individual inner spacing. |
hover-color | color | Replaces the node's base color while hovered. |
active-color | color | Replaces the node's base color while active. |
draggable | boolean | Makes the node absolutely positioned and pointer-draggable. Script feature roots are only draggable in chat. |
focusable | boolean | Allows the node to own keyboard focus. text-input enables this automatically. |
overflow | visible, hidden, scroll | Child clipping or vertical wheel scrolling. Unknown values mean visible. |
display | none, any other value | Removes/adds the node from layout. |
opacity | number | Multiplies the rendered node color alpha; it does not recursively change child opacity. |
hover-exclusive | boolean | Stops click/hover propagation above this node. |
on-click | callback object | Lua should use connect or on_click. |
The default flex direction is vertical/column and the default cross-axis alignment is stretch.
box
box is a layout-only node. It supports all common properties and renders no shape of its own.
rect and image
| Property | Values | Default | Description |
|---|---|---|---|
color | color | white | Fill/tint color. |
background-blur | boolean or number | 0 | true means full blur (1); a number controls blur opacity. |
drop-shadow-color | color | none | Renders the rectangle into the shadow canvas. |
corner-radii | one or four numbers | 0 | All corners, or top-left/top-right/bottom-right/bottom-left. |
outline-thickness | number | 0 | Outline width. |
outline-color | color | black | Outline color. |
texture | resource path, texture userdata | none | Image/texture. Lua bindings can provide texture userdata. |
sampler | nearest, linear | renderer default | nearest selects nearest filtering; every other string selects linear. |
custom-uvs | four numbers | none | Custom texture UV/inset vector. |
circle
| Property | Values | Default | Description |
|---|---|---|---|
color | color | white | Fill/tint color. |
outline-thickness | number | 0 | Outline width. |
outline-color | color | black | Outline color. |
progress | number | 1 | Rendered circle progress/fraction. |
texture | resource path, texture userdata | none | Optional texture. |
The rendered diameter uses the node's width.
text
| Property | Values | Default | Description |
|---|---|---|---|
text | string | empty | Displayed text. |
color | color | white | First/base text color. |
gradient-color | color | none | Optional second gradient color. |
font-size | number | 10 | Text size. |
font-face | font ID | google_sans_flex_medium | Font selection. |
line-height | number | 1 | Line-spacing multiplier. |
baseline-offset | number | 0 | Vertical baseline adjustment. |
wrap | boolean | false | Enables word wrapping to available width. |
align, align-items | left, center, right | left | Horizontal text alignment. |
vertical-align | top, center, bottom | center | Vertical text alignment inside fixed height. |
scroll | boolean | false | Horizontally scrolls overflowing text. |
scroll-speed | number | 35 | Scrolling speed. |
scroll-gap | number | 15 | Gap before repeated scrolling text. |
shadow | boolean | false | Enables text shadow. |
shadow-offset | one or two numbers | 2.5 2.5 | Both axes, or X and Y offsets. |
shadow-offset-x, shadow-offset-y | number | 2.5 | Individual shadow offsets. |
Font IDs:
tahomastratum2museo_sans_500google_sans_flex_regulargoogle_sans_flex_mediumgoogle_sans_flex_semiboldnoto_sans_hebrewlucide_icons
An unknown font ID falls back to google_sans_flex_regular.
text-input
| Property | Values | Default | Description |
|---|---|---|---|
text | string | empty | Editable text. |
placeholder | string | empty | Text shown while empty. |
font-size | number | 10 | Text size. |
font-face | font ID | google_sans_flex_medium | Font selection. |
baseline-offset | number | 0 | Vertical baseline adjustment. |
color | bound types.color | white | Input text color. |
placeholder-color | bound types.color | gray | Placeholder color. |
cursor-color | bound types.color | white | Caret color. |
selection-color | bound types.color | translucent blue | Selection highlight color. |
text-input supports typing, selection, Backspace/Delete, arrows, Home/End, and Ctrl+A/C/V/X. Its color fields currently accept typed color bindings or node.set_property with types.color; static color strings are not applied.
Notes
- Keep IDs unique.
- Bind functions should be fast because they run during UI updates.
- Use a sequential Lua table for loops; sparse numeric tables are not supported.
- Dynamic loop nodes are rebuilt/reused by index. Do not keep their node IDs.
hover-color,active-color, andopacityoperate on the node's rendered color, not all descendants.- A
drop-shadow-colorhas an effect only when the owning feature participates in the shadow render pass, which draggable Lua features do automatically.
