A composable set of anywidget-based widgets for data
analysis and geospatial work in notebooks. Widgets are self-contained, link and
compose with each other, work well with lonboard,
and are authored to render statically (no kernel) when a notebook is exported
with the myst-anywidget-static-export
MyST plugin — while remaining ordinary anywidgets in a live kernel.
Status: early but feature-complete for v1 —
Chart, input controls (Slider,RangeSlider,Dropdown,Toggle,Button,NumberInput), value displays (Stat,NumberDisplay,Text), layout containers (Row,Column,Grid), theBinderlinking primitive, and optional lonboard interop (LayerToggle,FilterBinder,LayerFilter).
pip install manywidgets
# optional lonboard interop widgets:
pip install "manywidgets[lonboard]"from manywidgets import Chart, Slider, Binder
import numpy as np
chart = Chart(title="Demo", x_label="x", y_label="y", height=320)
chart.add_series(x=np.linspace(0, 10, 100), y=np.sin(np.linspace(0, 10, 100)), name="sin")
chartfrom manywidgets import (
Chart, # Chart.js charts
Slider, RangeSlider, Dropdown, Toggle, Button, NumberInput, # input controls
Stat, NumberDisplay, Text, # value displays
Row, Column, Grid, # layout (arrange + keep children linked)
Binder, # linking with transforms / nested paths
)
# Optional lonboard interop (pip install "manywidgets[lonboard]")
from manywidgets.lonboard import LayerToggle, FilterBinder, LayerFilterTwo complementary tools — see docs/guides/linking.md:
from ipywidgets import jsdlink
# 1. jslink / jsdlink — the canonical, kernel-free pass-through link.
slider = Slider(label="Amplitude", min=0, max=5, value=1)
jsdlink((slider, "value"), (chart, "title")) # browser-side, works live + static
# 2. Binder — for transforms / nested paths jslink can't express.
Binder(source=slider, source_field="value",
target=chart, target_field="height",
multiplier=100, offset=200)Every widget extends a thin BaseWidget (auto-assigns a stable widget_id) and
follows the TypeScript "golden example" structure: src/index.ts bundled by
esbuild into dist/widget.js, styling via the _css trait. Cross-widget
resolution and the static-export safety rules live once in a shared
@manywidgets/core TypeScript module that esbuild inlines into each widget.
This package has no code dependency on the static-export plugin — it just
follows the rules the plugin needs (wrap save_changes, never inject <link>
into the mount element, vanilla DOM, buffer-free core widgets, link via
jslink/Binder). The docs site references the plugin by release URL only.
npm install
npm run build # esbuild every widget src/index.ts -> dist/widget.js
npm run typecheck # tsc --noEmit
npm test # vitest (JS unit tests)
pip install -e ".[dev]"
pytestEach widget owns its docs in src/manywidgets/<name>/doc.md (prose + a
{code-cell} example + an {api-table} placeholder). npm run docs:gen builds
docs/widgets/<name>.ipynb from those — auto-generating the API table from trait
introspection — so the per-widget pages are generated build artifacts
(gitignored), not hand-maintained. Build and view:
# lonboard + geopandas + pyarrow are only needed for the lonboard interop example
pip install -e ".[numpy,lonboard]" ipykernel nbconvert nbformat geopandas pyarrow
python -m ipykernel install --user --name manywidgets-venv
npm run docs:gen # generate docs/widgets/*.ipynb from each widget's doc.md
jupyter nbconvert --to notebook --execute --inplace docs/examples/*.ipynb docs/widgets/*.ipynb
cd docs && npx myst build --html && cd ..
npm run serve # serve over HTTP at http://localhost:8000 (NOT file://)Serve the built site over HTTP — opening the HTML via
file://breaks asset and widget loading (absolute paths + dynamicimport()).
Releases are cut by publishing a GitHub Release (CI builds the wheel — with JS compiled by the build hook — and publishes to PyPI). The JS bundles are gitignored and rebuilt by the build; they ship inside the wheel.
MIT — see LICENSE.