Views and Events

The tree

Every piece of UI is a View. app.build() sets the root; containers hold children in controls=; views that wrap a single view take it in content=.

app.build(
    nib.VStack(
        controls=[
            nib.Text("Hello", font=nib.Font.TITLE),
            nib.HStack(
                controls=[
                    nib.Button("OK", action=confirm),
                    nib.Button(content=nib.Label("Cancel", icon="xmark"), action=cancel),
                ],
                spacing=8,
            ),
        ],
        spacing=12,
        padding=16,
    )
)

The tree is serialised as a flat list of nodes (id, type, props, modifiers, parent and child ids) and may be at most 100 levels deep; a deeper tree raises an error naming the depth, which in practice only happens with accidental recursion.

Styling is constructor arguments

There is no method chaining. Every view accepts the same modifier arguments, and each one is also a reactive property:

Group Arguments
Layout width, height, min_width, min_height, max_width, max_height ("infinity" fills), padding, margin, offset
Appearance background (colour or view), foreground_color, fill, stroke, stroke_width (shapes), opacity, corner_radius, clip_shape, blend_mode, scale
Typography font, font_weight
Effects shadow_color, shadow_radius, shadow_x, shadow_y, border_color, border_width
Animation animation, content_transition, transition
Composition overlay, context_menu, tooltip, visible, key, ref
Interaction on_drop, on_hover, on_click

padding and margin take a number or a dict with any of top, bottom, leading, trailing, horizontal, vertical. background, overlay and context_menu may hold other views; they travel as separate nodes attached to their owner. The Modifiers reference documents each argument.

visible=False removes the view from the tree entirely; opacity=0 keeps its space.

Identity

Nodes are identified by their position: the root is "0", its second child "0.1", and so on. The runtime uses the id to match nodes between frames and to route events. When children are reordered or inserted, pass key="..." so a view keeps its identity (and its runtime state, such as a text field's cursor) instead of taking over the slot of a sibling. Two views in one tree must never share an id; the render fails loudly if they do.

To reach a view declared inline in app.build(...) from a handler, create a nib.Ref() before building, pass it as ref=, and read ref.current.

Events

Callbacks are constructor arguments. The runtime sends the node id, an event name and a typed value; the SDK calls the matching function on the event thread.

View Argument Signature
Button, MenuItem action handler()
TextField, SecureField, TextEditor on_change, on_submit handler(text: str)
Toggle on_change handler(is_on: bool)
Slider on_change handler(value: float), continuously while dragging
Picker on_change handler(value: str)
DisclosureGroup on_expand handler(is_expanded: bool)
NavigationStack on_navigate handler(route: str)
Table on_select handler(selected_rows: list)
Map on_region_change handler(latitude, longitude, zoom)
WebView on_load, on_navigate, on_error handler(), handler(url: str), handler(message: str)
Canvas on_pan_start, on_pan_update, on_pan_end, on_hover handler(event: PanEvent) with event.x, event.y
any view on_drop handler(paths: list[str])
any view on_hover handler(is_hovering: bool)
any view on_click handler()
App on_appear, on_disappear, on_quit handler()
App.on_hotkey(shortcut, handler) handler()

on_appear and on_disappear fire when the popover opens and closes. on_quit fires once when the app quits and, under nib run, when the app is replaced by a reload.

A callback that raises is logged with its traceback and the app keeps running.