AnyLumino

Composition guide

LayoutWidget

LayoutWidget is the public base class for anylumino layouts. TabPanel, SplitPanel, VBox, GridPanel, and the other layout widgets all share its composition API: keyed children, child lookup, dynamic mutation, and method forwarding.

The goal is to make bigger widgets from existing widgets. A composed widget can combine anywidgets, Astryx components, charts, tables, and outputs into one reusable object. Because that object is still an anywidget, it can be nested again inside tabs, split panes, dock panels, or another domain-specific component.

This makes notebook interfaces easier to structure: child widgets are addressable by key, callbacks can mutate sibling widgets, and application state can live on a composed object such as FigurePanel, SampleReview, or LabDashboard instead of in notebook-global variables.

Child widgets

Every LayoutWidget accepts children either as a sequence or as a mapping. A mapping is preferred for application-style notebooks because it gives each child a stable key:

from anylumino import TabPanel
from anylumino import astryx as ax

tabs = TabPanel(
    {
        "status": ax.Stack(
            {
                "connection": ax.Stack(
                    {
                        "dot": ax.StatusDot("Connected", variant="success", pulsing=True),
                        "text": ax.Text("Streaming ESM24 quotes"),
                    },
                    direction="horizontal",
                    align="center",
                    gap=2,
                ),
                "fills": ax.ProgressBar(3, label="Fills", max=10, value_label=True),
                "summary": ax.Text("1 working order, 3 fills", text_type="supporting", color="secondary"),
            },
            gap=3,
        ),
        "logs": ax.List(
            [
                {"label": "Session started", "description": "13:30:00"},
                {"label": "Subscribed ESM24", "description": "13:30:02"},
                {"label": "First fill", "description": "13:31:14"},
            ],
            dividers=True,
            density="compact",
        ),
    },
    titles={"status": "Status", "logs": "Logs"},
    height=220,
)

Child widgets example rendered in JupyterLab

If titles is omitted for a mapping, the child keys are used as the titles.

Accessing children

Layout widgets keep both a renderable widget and an owner object. This is useful when a domain wrapper exposes a .widget view but also has methods that should be called from Python.

tabs = TabPanel({"trend": trend_panel})

tabs.get_widget("trend")      # rendered child, usually trend_panel.widget
tabs.get_owner("trend")       # original wrapper object
tabs["trend"]                 # same as get_owner("trend")
tabs.call_owner("trend", "refresh")

Adding methods to composed widgets

Add methods directly to an anylumino layout by subclassing one of the public layout widgets. The subclass is still an anywidget, so it can be nested inside another layout.

import anylumino as al
import anylumino.astryx as ax

class FigurePanel(al.VBox):
    def __init__(self, figure):
        self.figure = figure
        toolbar = ax.Stack(
            {
                "title": ax.Heading("Figure", level=3),
                "refresh": ax.Button(
                    "Refresh",
                    variant="primary",
                    callbacks=[lambda _button: self.refresh()],
                ),
            },
            direction="horizontal",
            align="center",
            gap=2,
        )
        super().__init__(
            {"toolbar": toolbar, "figure": figure},
            stretches=[0, 1],
            child_min_height=56,
            height=520,
        )

    def refresh(self):
        update_figure(self.figure)

    def set_data(self, data):
        self.figure.data = data

tabs = al.TabPanel({"trend": FigurePanel(figure)})
tabs["trend"].refresh()

When a wrapper only adds Python behavior around an existing widget, it does not need to subclass anywidget.AnyWidget. Expose the renderable child as .widget; anylumino renders that view and keeps the wrapper as the keyed owner.

class FigureWrapper:
    def __init__(self, figure):
        self.figure = figure
        self.widget = figure

    def refresh(self):
        update_figure(self.figure)

tabs = TabPanel({"trend": FigureWrapper(figure)})

tabs.get_widget("trend")  # figure
tabs["trend"].refresh()   # wrapper.refresh()
tabs.call_owner("trend", "refresh")

Use a LayoutWidget subclass when the wrapper is itself UI or layout. Use a plain .widget owner wrapper when the wrapper only coordinates Python methods.

Mutating layouts after display

Children can be added, renamed, removed, and selected after the parent widget is already displayed:

tabs.add("figure-2", figure, title="Figure 2", select=True)
tabs.rename_widget("figure-2", "Recent sample")
tabs.select_key("figure-2")
removed = tabs.remove_widget("figure-2")

Scroll behavior

HBox, VBox, and ScrollBox can expose scrollbars inside notebook output. The scroll=True convenience follows the natural axis:

from anylumino import HBox, ScrollBox

wide = HBox(children, scroll=True, child_min_width=180, spacing=12, height=160)
vertical = ScrollBox(children, height=420)

Scroll behavior example rendered in JupyterLab

For explicit overflow control:

HBox(
    children,
    scroll_x=True,
    scroll_y=False,
    spacing=12,
    child_min_width=220,
    height=160,
)

Scroll behavior example rendered in JupyterLab

spacing controls the gap between children. child_min_width and child_min_height reserve enough room for each child, and scroll=True lets a row scroll instead of compressing controls until labels or inputs clip.

Resizing

Composed layouts are resizable by drag and drop by default. The resize handle is shown in the bottom-right corner of the layout host.

from anylumino import SplitPanel

SplitPanel({"left": controls, "right": chart}, sizes=[0.3, 0.7])
SplitPanel({"left": controls, "right": chart}, resizable=False)

Resizing example rendered in JupyterLab

SplitPanel uses Lumino split handles between child panes. The layout host resize handle controls the overall notebook footprint. When panes are adjusted with Lumino split handles, the current relative pane sizes are synced back to the sizes trait so Python can inspect or reuse them.

Actions and callbacks

Actions are plain dictionaries keyed by an id. The matching callback receives the action id.

import anylumino as al
import anylumino.astryx as ax

toolbar = ax.Stack(
    {
        "refresh": ax.Button(
            "Refresh",
            variant="primary",
            callbacks=[lambda _button: figure_panel.refresh()],
        ),
        "clear": ax.Button(
            "Clear",
            callbacks=[lambda _button: figure_panel.set_data([])],
        ),
    },
    direction="horizontal",
    gap=2,
)

al.SplitPanel(
    {"toolbar": toolbar, "panel": figure_panel},
    orientation="vertical",
    sizes=[0.1, 0.9],
    height=600,
)

Actions and callbacks example rendered in JupyterLab

For compact application chrome, the Lumino-style Toolbar, MenuBar, and CommandPalette remain available and dispatch action ids to Python callbacks. For notebook content, prefer Astryx controls such as Button, CommandPalette, and SegmentedControl because they share the same visual system as forms, tables, and status surfaces.

Lumino, Astryx, and the bundled workflow icon set are compiled into anylumino's frontend bundles, so notebooks do not need CDN access at runtime.

On this page