Skip to content

Components

Every component is a Python object you place in a tree and bind to a signal. The sections below walk through the common ones with runnable snippets; this is the full set shipped in 0.2.0.

Text and display

Component Use
Text a label bound to a signal, computed, or string
Image a URL, data: URI, or raw PNG bytes
ImageOverlay an image with read-only vector overlays (boxes, masks, keypoints)
Audio a URL, data: URI, or raw audio bytes, played with native browser controls

Inputs (two-way bound to a signal)

Component Use
Slider a numeric input on a track
Number a plain numeric input with optional bounds
TextInput a single-line text box
Select a single-choice dropdown
MultiSelect a multi-choice select
Radio a radio group
Checkbox a boolean checkbox
Date a date picker (ISO YYYY-MM-DD)

Actions

Component Use
Button an on_click handler (sync or async def)

Layout

Component Use
Column a vertical stack
Row a horizontal flex row that wraps
Grid an N-column grid that collapses to one column on phones
Card a titled surface panel
Tabs a tabbed container, one panel at a time
Sidebar a persistent side region plus a main region
Expander a collapsible labelled section

Charts and data

Component Use
Plot a Matplotlib figure rasterised to a PNG on the Python side
Chart an interactive client-side chart (zoom, hover, live points)
Heatmap a client-side heatmap / 2-D field
Map an interactive client-side map (Leaflet): pan/zoom, markers, polygons, polylines
DataFrame a pandas frame, a {columns, rows} dict, or record dicts
Table an interactive table: sort, page, and select
Stat a metric / KPI card: a big value with a label and optional delta

Streaming and data-driven lists

Component Use
StreamText a string that grows token by token over SSE
List a data-driven list bound to a Signal[list]
Chat a chat transcript of role bubbles bound to a Signal[list]
Gallery an image grid bound to a Signal[list]

Files and feedback

Component Use
Upload a file input (image / audio / any file)
Download a link that hands the user a file back
Progress a progress bar bound to a source
Spinner an indeterminate busy indicator

Display

from indah import Text, Image, DataFrame, Column, Signal, computed

name = Signal("Ada")
page = Column(
    children=[
        Text("a static label"),
        Text(computed(lambda: f"hello, {name.value}")),  # reactive
        Image("https://example.com/logo.png", alt="logo"),
        DataFrame({"columns": ["n", "n^2"], "rows": [[k, k * k] for k in range(1, 6)]}),
    ]
)

Text accepts a string, a Signal, a computed, or a plain lambda - anything that reads signals reactively. DataFrame duck-types its input: a pandas frame (via to_dict(orient="split")), a {columns, rows} dict, or a list of record dicts. Neither pandas nor Matplotlib is a hard dependency; each is only touched if you pass it.

Plot takes anything with a savefig method (a Matplotlib figure) and serialises to an image, so the browser needs nothing extra to show it.

Inputs

Inputs are two-way bound: the UI reflects the signal, and a user change writes back to the signal.

from indah import Slider, TextInput, Select, Signal, Column

n = Signal(3)
who = Signal("world")
choice = Signal("squares")

page = Column(
    children=[
        Slider(n, min=0, max=10, step=1, label="n"),
        TextInput(who, placeholder="your name", label="Name"),
        Select(choice, options=[("squares", "Squares"), ("primes", "Primes")], label="Dataset"),
    ]
)

Select options are (value, label) pairs, or plain strings when the value and label are the same. Read the current value anywhere with .value; it stays in sync with what the user picked.

TextInput(value, password=True) masks the field (an <input type="password">) for secrets like an API key. It is only display masking - the value still round-trips like any input, so keep it in per-session state and never log it.

Buttons and async handlers

from indah import Button, Signal, Text, Column

count = Signal(0)


def bump():
    count.set(count.value + 1)


page = Column(
    children=[
        Button("Add one", on_click=bump),
        Text(lambda: f"count = {count.value}"),
    ]
)

An on_click handler may be async def. It runs in the background, so a slow handler (a model call, an HTTP request) never blocks the event loop or other viewers, and the rest of the UI stays responsive while it runs.

Streaming

StreamText holds an accumulating string. feed(token) appends a delta over SSE - only the new text crosses the wire, not the whole string - and reset() clears it.

from indah import StreamText

out = StreamText(label="Response")
out.feed("Hello ")
out.feed("world")  # the shell appends, token by token
out.reset()  # start over

Pair it with an async button handler (see Quickstart) to stream a model's output into the page.

Beyond the starter set

If the set does not cover what you need, register a custom component against the public protocol - no framework fork, no Node build. See Custom components.