indah
A Python UI framework for ephemeral cloud notebooks (Colab, Runpod). Reactive, single-port, no Node required.
Build an interactive UI from a single Python file and launch it straight from a Colab or Runpod cell. It keeps Streamlit's zero-config, single-port startup, adds the async performance of a real full-stack app, and ships its frontend pre-built so there is no Node, npm, or bun anywhere at install or runtime.
See it running in your browser - the full demo gallery, live, no install and no clone.
Status: Milestone 1 components shipped. On top of the MVP (reactive core, SSE transport, streaming, custom-component seam) it adds the full input set and layout containers, data-driven lists (chat/gallery), per-session state, file upload/download, and charting (server-PNG
Plotplus client-sideChart,Heatmap, an interactiveTable,Statcards, andImageOverlay). Install withpip install indah. Start withdocs/PLAN.mdfor the plan anddocs/SLICES.mdfor what is built.
Why another one
| Pain | indah's answer |
|---|---|
| Streamlit reruns the whole script on every interaction | Reactive signals: only the affected components update (ADR-0003) |
| Gradio's layout and state model get awkward past a demo | Plain Python components bound to state, custom layouts |
| Reflex needs a Node build step that breaks in transient containers | Frontend ships pre-built in the wheel; zero runtime Node (ADR-0004) |
| Colab's proxy does not support WebSockets | SSE + HTTP POST transport that passes the proxy (ADR-0002) |
How it works
flowchart LR
subgraph Cell["Colab / Runpod cell"]
PY["Your Python app<br/>(reactive signals)"]
end
subgraph ASGI["Single ASGI app, one port"]
CORE["Reactive core<br/>signals to JSON patches"]
API["/api: SSE + POST"]
STATIC["Pre-built Svelte shell<br/>(static assets in the wheel)"]
end
Browser["Browser via platform proxy"]
PY --> CORE --> API
STATIC -- served over HTTP --> Browser
API -- SSE patches --> Browser
Browser -- POST events --> API
One port, standard HTTP plus Server-Sent Events, so it works through the network proxies of Colab and Runpod without a tunnel or a local JavaScript toolchain.
Try it
Run the built-in demo from a clone:
git clone https://github.com/leejianrong/indah && cd indah
uv sync --extra dev
make demo # prints a URL; binds the first free port from 8000
The demo streams a mock LLM token by token into a StreamText; below it, a Select
switches a live DataFrame and a colour picker (a registered custom component)
two-way binds a signal - all updating over SSE, with no WebSocket and no Node. Only
the components that depend on a changed value are patched; there is no full-script
rerun.
Other ways to run it:
make demo-notebook # inline in a local JupyterLab cell
make demo-docker # in Docker on an auto-picked free port
Prefer Docker with a stable http://indah.localhost/ hostname (via a machine-wide
Traefik proxy)? make demo-traefik - see docs/DEV-DOCKER.md.
Your app
An app is a tree of components bound to reactive signals. Mutate a signal and only the components that read it update - no full-script rerun:
import indah
from indah import Signal, computed, Column, Slider, Text, Session
a, b = Signal(2), Signal(3)
total = computed(lambda: f"a + b = {a.value + b.value}")
page = Column(
children=[
Slider(a, min=0, max=10, label="a"),
Slider(b, min=0, max=10, label="b"),
Text(total),
]
)
indah.launch(
indah.create_app(session=Session(page))
) # prints the URL; embeds inline in Colab/Runpod
The frontend is a pre-built Svelte shell bundled in the wheel; no Node runs at
install or runtime. See docs/SLICES.md for what's next.
Components
The starter set covers a typical AI demo (input, run, streamed output):
| Component | Use |
|---|---|
Text |
a label bound to a signal, computed, or string |
Button |
an on_click handler (sync or async def) |
Slider / TextInput / Select |
inputs two-way bound to a signal |
Image / Plot / DataFrame |
display a URL/bytes, a Matplotlib figure, or a table |
StreamText |
a container that grows token by token over SSE |
Column |
a vertical layout container |
That is the starter subset. 0.2.0 also ships layout containers (Row, Grid, Card,
Tabs, Sidebar, Expander), more inputs (Checkbox, Number, Radio,
MultiSelect, Date), charting (Chart, Heatmap, Table, Stat, ImageOverlay),
data-driven List / Chat / Gallery, and file Upload / Download - see the full
Components reference.
Need something the set does not cover? Register a custom component against the public JSON protocol, no framework fork and no Node build:
import indah
indah.register_component(
"colorpicker",
render={
"tag": "input",
"attrs": {"type": "color"},
"bind": {"value": "value"}, # element value <- signal
"on": {"input": {"event": "input", "prop": "value"}}, # UI change -> signal
},
)
colour = indah.Signal("#ff8800")
picker = indah.custom("colorpicker", value=colour) # two-way, like a built-in
The pre-built shell renders it from that declarative spec at runtime. The protocol
is a documented, versioned public contract: see docs/protocol.md.
Examples
Every demo is live in the demo gallery (no install, no clone), opens in one click in Colab, and is a single Python file you can run as a script (ADR-0023).
| Demo | Live | Colab | Source |
|---|---|---|---|
| Streaming chatbot | Open | chatbot.py |
|
| Live training dashboard | Open | training_dashboard.py |
|
| Image generation | Open | diffusion.py |
|
| Poster generator | Open | poster.py |
|
| Image classifier | Open | upload_classify.py |
|
| Hybrid charting | Open | charts.py |
|
| Stock peer analysis | Open | stocks.py |
|
| Pretty map | Open | prettymap.py |
Also: examples/starter_components.py (a ~20-line
tour of the component set) and examples/demo.ipynb (the
built-in demo inline in a notebook). The chatbot is walked through step by step in
the docs: Build a chatbot.
The Colab notebooks are generated from the demo manifest by
deploy/colab/make_colab.py; regenerate them after
adding a demo.
Planning and design
| Doc | What |
|---|---|
docs/PLAN.md |
Problem, solution, scope, requirements, architecture |
docs/SLICES.md |
Vertical build increments with test plans |
docs/QUESTIONS.md |
Decision register |
docs/protocol.md |
The JSON UI protocol (public contract) |
docs/adr/ |
Architecture Decision Records |
Development
uv sync --extra dev
make check # lint + fast tests (the pre-push gate)
make help # list all targets
See AGENTS.md for repo conventions and docs/RELEASING.md
for the release process.
License
Release files for indah 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| indah-0.2.1.tar.gz | 1.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| indah-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.4 MB
Release files / indah-0.2.1.tar.gz
| Download URL | indah-0.2.1.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
47fd649e7d034891b3445f03ffe5cbb8b1ff3051bd7ed8c96c9d6696527e976c
|
|
BLAKE2b-256 checksum How to use checksums |
ef1dc91c422bb51f282d2a958ae8e67415675f0e2a6e20c832b43d1ad53b0585
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / indah-0.2.1-py3-none-any.whl
| Download URL | indah-0.2.1-py3-none-any.whl |
|---|---|
| Size | 212.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
42015503d0f65eacc615f58927553601ec2fee8b8add4d42de646b3bdff04a63
|
|
BLAKE2b-256 checksum How to use checksums |
5ef6f768030a77d716edb7a853d742c948435d563eff150c316d6fd17fb9d45a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency log