Bring the Python libraries you already know (plotly, altair, etc) into a reactive web app with one decorator. Build rich and delightful experiences with user interaction and performant updates.
Each group opens a set of guides. Every guide shows the widget rendering, the user's gestures flowing back to Python, and the widget updating in place.
11plotly · altair · bokeh · matplotlib …
09lonboard · maplibre · ipyleaflet · kepler.gl …
04k3d · pythreejs · pyvista · ipyvolume
06nglview · niivue · vizarr · mols2grid …
04itables · reactable · perspective · ipydatagrid
04embedding-atlas · mosaic · quak · pygwalker
02ipycytoscape · ipysigma
03drawdata · ipycanvas · ipyfilechooser
Pick a widget. The highlighted call is how your app reads what the user did; flip between before and after the gesture.
Every example runs as-is: shiny run app.py. See all the guides →
Learn them once with one widget and they carry over to all the others. Each one is a page in the guide.

Return a figure, map, or scene from a decorated function. It fills its card like any Shiny output.
@render_plotly def plot(): return px.scatter(df, ...)

Every gesture is an accessor. Call it from any reactive context, the way you'd call input.x().
@render.data_frame def picked(): return plot.selected_rows(df)

Change the live widget from Python. Here a summary of the selection is written onto the chart, and the lasso and zoom stay put.
@reactive.effect def _summarize(): rows = plot.selected_rows(df) plot.widget.layout.annotations = [ dict(text=f"{len(rows)} countries…", ...) ]
Real data, several views driving each other, a hundred or two lines of Python. Every one is open source and runs with shiny run app.py.

Draw on the GPU, aggregate on the server, or query only what's visible. Three approaches, measured.
Large data & performance →
Let a chat filter your data, read a selection, or steer a map. The LLM sees the same accessors you do.
AI exploration →import anywidget, traitlets class Counter(anywidget.AnyWidget): _esm = "…" count = traitlets.Int(0).tag(sync=True) @render_widget def counter(): return Counter() counter.read("count")
Any ipywidget or anywidget renders with @render_widget, including one you write yourself.