You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What every element takes besides its children and its own options. A property is a value, a state holding one, a function returning one (run again when the state it reads changes), or a binding: Reactive<T>. Literal values apply once; anything that can change shares one reactive style per node.
-- Each style property, as elements take it:w: Reactive<Length>?bg: Reactive<Color>?-- …and as `hover`, `active`, `focus`, `disabled` and `context_menu_style` take it:hover: Styles?
focus is CSS's :focus-visible: it applies when focus came from the keyboard (or a script, before any click), on a text field however it got focus, and once a key goes to the focused node; a clicked button takes focus without it.
The types: Color is "#rgb", "#rrggbb", "#rrggbbaa", "rgb(…)", "rgba(…)", "hsl(…)", a few names, or a number 0xrrggbb. Length is points or a string: "50%" (of the containing block), "12px", and "auto" for margins. Pair is { a, b } or one number for both. Unless a row says otherwise a property has no default: the node has none of it.
its inset from the start or end of a line (CSS's inset-inline-start/-end): left and right left to right, right and left with dir = "rtl"
dir
"ltr" | "rtl"
its parent's ("ltr" at the root)
which way lines run in it and what it holds (HTML's dir): "rtl" runs rows from the right, starts a column's children and text at the right, and turns the ps/pe, ms/me, inset_start/inset_end edges round; pl, ml and left stay on the left
Spacing
Property
Type
Default
p, px, py, pt, pr, pb, pl
Length
0 (a button: px = 12, py = 8)
padding; later keys refine earlier ones, a percentage counts from the containing block's width
ps, pe
Length
padding at the start and end of a line (CSS's padding-inline-start/-end), which follow dir
m, mx, my, mt, mr, mb, ml
Length
0
margins; "auto" takes the remaining space
ms, me
Length
margins at the start and end of a line (margin-inline-start/-end), which follow dir
{ "#f80", "#08f" } or { angle = 135, stops = { … } }; stops are colors (evenly spaced) or { offset, color }; CSS angles: 0 runs bottom to top, 90 left to right, 180 (the default) top to bottom
color
Color
#e5edf7
text, and a progress bar's fill and a slider's thumb; inherited
rounded
number
0 (controls: 6)
corner radius
corners
Corners
{ tl, tr, br, bl }, as a list or keyed
rounded_tl, rounded_tr, rounded_br, rounded_bl
number
one corner, refining rounded or corners
border
number
0
width, inside the box
border_sides
Sides
{ top, right, bottom, left } widths: a dock open at the bottom is { 1, 1, 0, 1 }
border_color
Color
border_over
boolean
false
paint the border over the children, taking no room (a ring)
border_dash
Pair
solid
{ length, gap }
shadow
Shadow
none
{ x, y, blur, spread, color } (CSS box-shadow; color#0000005a by default), a list of them, or false
outline, outline_color, outline_offset
number, Color, number
none, black, 0
an outline around the box (CSS's): its width, colour, and where it starts outside the box; a negative offset puts it over the edge (an inset focus ring is outline = 1, outline_offset = -1)
inset_shadow
Shadow
none
shadows inside the box (CSS's inset), over its fill and under its border and children: the box less itself moved by x, y and shrunk by spread, cut to its corners; drawn sharp (blur is not applied). A 1-point top highlight is { x = 0, y = 1, color = "#ffffff1f" }
opacity
number
1
0 to 1
brightness, contrast, saturate
number
1
CSS filter functions over the node and its subtree
blur
number
0
blurs the node's content (CSS filter: blur())
backdrop_blur
number
0
blurs what is behind it: frosted glass
fade
Pair
a clipping view's top and bottom edges fade over those widths
draws an image dithered in dots this many points square (see Images and canvas)
cursor
Cursor
"default"
"pointer", "text", "vertical_text", "crosshair", "move", "grab", "grabbing", "not_allowed", "wait", "progress", "help", "context_menu", "copy", "alias", "no_drop", "all_scroll", "col_resize", "row_resize", "ew_resize", "ns_resize", "nesw_resize", "nwse_resize", "zoom_in", "zoom_out"; CSS's spellings ("nwse-resize") too. Unstated, a link shows "pointer" and a text field "text"; buttons, checkboxes, menu items and sliders keep the arrow, as Chrome's and AppKit's do
the content shows through ui.bitmap's alpha, drawn at w×h (the bitmap's size) from (x, y); animate maskX, maskY, or sweep = { x, y, duration, frame_rate } to have the engine move it by (x, y) every duration ms, over and over, with no script per step
bg_shader
Shader
ui.shader(wgsl, { fps }) fills the box per pixel (see Images and canvas)
shader_params
{ Color | { number } }
zeros
up to four vec4s: colors (0 to 1) or lists of numbers; animate shaderTime, shaderParam0 to shaderParam15
clip_inset
Sides
paints only inside the box shrunk by { top, right, bottom, left } (CSS clip-path: inset()); animate clipTop, clipRight, clipBottom, clipLeft
clip_feather
number
0
where it clips (overflow = "hidden"), its content fades out over this many points inside its rounded edge rather than being cut there, in the fragment shader quads already run (no layer, no mask). Its corners follow its clip inset, held to half the inset box: with rounded = 9999 and the clip inset animated from the middle out, a soft circle opens, compositing alone
"height" (or true), "width", "size": the space the node takes eases to its new size, while it lays out at that size at once; "position": it slides when layout moves it
layout_id
string
a node mounting while (or as) another with the same id is mounted starts at that one's box and moves to its own; ids are per script
layout_ease
Easing
a spring, 0.4 s, bounce 0.1
layout_duration
number
the ease's
ms
layout_enter
boolean
false
grow from nothing when it mounts
layout_clip
boolean
false
clip the content to the animated space
Dynamic content covers each's animate, which slides rows the same way.
Text
Property
Type
Default
text_size
number
16
inherited, as all of these
weight
number
400
bold = true is 700
bold, italic
boolean
false
align
"start" | "left" | "center" | "right"
"start"
font
string
the system's
"mono" ("monospace"), "sans" ("sans-serif"), "serif", or a family name the host registered
wrap
boolean
false
wrap at the box's width
truncate
boolean
false
one line ending in "…"
lines
number
all
clamp to that many lines
line_height
number
the font's
points
letter_spacing
number
0
points
tabular
boolean
false
tabular figures: every digit one width, so a changing number holds still
underline
boolean
false
underlined text, inherited (CSS's text-decoration): hover = { underline = true } underlines a link button's label
underline_offset
number
a tenth of the text size
points below the baseline the underline sits; inherited
Transforms
x, y (number, points), scale, scale_x, scale_y (number, 1), rotate, skew, skew_x, skew_y (number, degrees) and origin (Pair, fractions of the node, { 0.5, 0.5 }). They apply after layout: the node keeps its place in the flow, and hit testing follows the transform. Rotation, scale and skew paint the subtree into a cached layer.
Pseudo-states, window dragging, identity
Property
Type
Default
hover, active, focus
Styles
(controls: a hover color and an accent focus ring)
styles while the pointer is over it, while it is pressed, while it has keyboard focus; focus = { border = 0 } turns a control's ring off
disabled
boolean | state | function | Styles
false
a condition disables the node (and what is inside); a table is the styles it shows while disabled
selectable
boolean
false
the pointer selects the text in it and in everything it holds, across nodes, as on a web page (CSS user-select: text); false keeps a part of a selectable node out. See selectable text
selection_color
Color | Readable<Color> | (() -> Color)
$selection
a selectable node's highlight (CSS ::selection { background }), in either window state
selected_text_color
Color | Readable<Color> | (() -> Color)
$selection.text
the selected glyphs' colour (CSS ::selection { color }); transparent keeps their own
window_drag
boolean
false
a press on it, or on what inside takes no input, moves the window; a double click zooms it (CSS -webkit-app-region: drag)
tooltip
string or a node
none
shown over it while the pointer rests on it: after 300 ms, at once within a second of another tooltip; gone when the pointer leaves or presses, or a key is pressed anywhere (as a browser's and the system's do). Text takes the theme's look (an overlay card, small text); a node is the app's own content. It fades in over 140 ms and out over 100 ms, at once when it takes over from another; one shows at a time. It takes no focus and lets the pointer through
provide
{ [string]: any }
none
values the components and rows built in its subtree read with ui.use_context(name), as React's context gives them (components)
tooltip_placement
Placement
above, centred, 6 points off
where the tooltip sits (as a popover's placement)
tooltip_delay
number
300
milliseconds the pointer rests before it shows. Keyboard focus shows it too (not a click's), and a text tip describes the node to assistive technology, which doesn't see the tip itself
tooltip_interactive
boolean
false
a hover card the pointer can move into and click in: it stays while the pointer is over the node or the card (a moment's grace to cross between them); typing goes to its fields, and Escape closes it
"none", "auto", boolean, or a state or function of one
"auto"
"none" or false lets the pointer pass through to what is under the node (presses, hover, the wheel, drops, and a web view or terminal, even where the node paints over it); descendants with their own handlers still receive input
Accessibility
What assistive technology (VoiceOver, Narrator, Orca) reads for a node beyond what it is: controls say their role, name and state themselves, and these say what a plain box means. They cost nothing on nodes that don't set them. A label given as a state or function costs nothing either while no screen reader is on: it is worked out only while assistive technology reads the window (from the state as it is, when one starts), not each time what it reads changes.
Property
Type
Default
label
string | state | function
its text
the name read for a box (ARIA's aria-label): an icon button, a group. A state or function is followed, for a recycled list row's name made from what it shows now ("Unread, Grace Hopper, Lunch?, Oct 28"). Widgets take their own label
what it is taken for (ARIA's role), keeping what it does: a clickable row as an "option" in a "listbox", a row of buttons as a "toolbar". Options, tabs, list items and headings are named by their text
checked, expanded
boolean | Readable<boolean> | (() -> boolean)
none
what a control of the app's own says of its state: a switch's or a radio's checked (aria-checked, a checked radio is where Tab lands in its group), what it opens expanded (aria-expanded)
heading
number
a section title of that level, 1 to 6 (h1…h6): screen readers list headings and move between them (VoiceOver's rotor)
selected
boolean | state | function
whether an option, tab or item is the chosen one (aria-selected), followed
live
"polite" | "assertive" | "off"
"off" (role = "status": "polite")
announce changes to the text in it (aria-live): a count, a "Sent" note, a message arriving; "assertive" interrupts
description
string
read after the name, as a hint (aria-description)
error
string | state | function
what is wrong with its value, followed; nil while fine: assistive technology hears it invalid (aria-invalid), and the message after its description. field gives a form's
aria_hidden
boolean
false
leaves it and what it holds out of the accessibility tree (aria-hidden): decoration such as an unread dot, or what is said elsewhere
focus_group
"vertical" | "horizontal" | "both"
role = "listbox": "vertical"; "toolbar", "tablist": "horizontal"
one Tab stop for the focusable things inside, as ARIA's composite widgets are: Tab lands on the one last focused there (else the selected one, else the first), and the arrows along the axis, Home and End move among them. A field inside keeps its own arrows
An absolute = true node leaves the flow and positions against its containing block: the nearest ancestor with relative = true, searched from its parent up but not past an absolute ancestor (which contains its own absolute descendants); with none, its parent. left, top, right and bottom count from that block's padding box, inside its border; percentages are of its size; both insets on an axis without a size stretch the node between them; an axis without insets keeps it at its parent's content origin (plus ml/mt). show, switch, each and presence wrap their content in a node of their own, so mark the ancestor you mean relative rather than relying on being its direct child. Transforms and scroll offsets never change the containing block: an absolute node moves with them, and still paints in its parent's place, under its clips.
Example
localliked=ui.state("liked", false)
returnui.column {
p=24,
ui.column {
relative=true,
w=280,
p=16,
gap=8,
rounded=14,
gradient= { angle=135, stops= { "#232e40", "#1a2230" } },
border=1,
border_color="#ffffff1a",
shadow= { y=8, blur=24, color="#00000066" },
ui.text { "Ada Lovelace", text_size=18, weight=600 },
ui.text {
"Wrote the first program for a machine that did not yet exist.",
wrap=true,
lines=2,
color="#8b95a5",
line_height=20,
},
ui.button {
self="end",
rounded=999,
bg=function()
returnifliked:get() then"#e5534b" else"#2f4058"end,
hover= { brightness=1.15 },
active= { scale=0.96 },
on_click=ui.toggle(liked),
"Like",
},
-- Pinned to the card's corner, whatever comes before it.ui.div { absolute=true, top=10, right=10, w=8, h=8, rounded=4, bg="#3fb950" },
},
}
Gotchas
A state or function whose value does not fit the property (a string for w that is not a length, a boolean for bg) is ignored at run time; a literal that does not fit is an error. In the editor both are type errors.
hover, active, focus, disabled and context_menu_style take plain values: a state, function or binding in them is an error. To animate on hover, animate the base style from on_hover.
Children stretch across their container (items = "stretch") and shrink in one too small for them (shrink = 1), as CSS's flexbox; text does not wrap (wrap = false) unless asked. A child that shrinks stops at its content's narrowest (CSS's min-width: auto: a text's longest word), unless it clips (overflow, a scroll) or sets min_w = 0; shrink = 0 holds it.
gap is points only; gap_x and gap_y take percentages.
A transparent border still takes its width; a ring that must not move content is border_over = true.
bg_fit and the shape of mask, bg_shader and shader_params are read once, as literals.