anywidget
website·Indexed 34 days ago
https://anywidget.dev/A tool for creating custom, reusable interactive widgets for Jupyter and other computational notebooks using front-end JavaScript and stateful properties accessible by both Python and JavaScript. It includes the Anywidget Front-End Module (AFM) specification for portable widget code and supports native Hot Module Replacement (HMR).
What's inside anywidget
- anywidget is a specification and toolset for authoring reusable web-based widgets for interactive computing environments. It defines a standard for widget front-end code (Anywidget Front-End Module or AFM) and provides a Python library to simplify the creation and publishing of custom Jupyter Widgets. It supports environments including Jupyter, JupyterLab, Google Colab, VSCode, and marimo.
Overview of anywidget
anywidget is a Python library that simplifies the creation of custom Jupyter Widgets by acting as an abstraction layer over the standard Jupyter Widgets framework. It leverages ECMAScript Modules (ESM) to solve the multi-platform packaging problem, allowing developers to define ESM and an associated Python class without needing to create a full Python package or manage complex front-end build tooling (like Webpack) before prototyping. This enables custom widgets to be developed directly within notebooks and work across different notebook environments, including those that typically require npm publishing like Google Colab or VS Code.Explore anywidget community projects and implementations
The anywidget ecosystem includes a wide variety of third-party widgets and integrations for Jupyter notebooks. These projects demonstrate the framework's ability to handle complex tasks such as WASM sideloading, bidirectional communication with JS libraries, and specialized scientific visualizations.Understand when to use a bundler with anywidget
anywidget does not require bundling if you use web-standard ECMAScript modules (ESM) that browsers can interpret directly. However, a bundler is necessary if your project uses:
- Local dependencies (npm packages)
- Non-standard syntax (TypeScript, JSX)
- Front-end frameworks (React, Vue, Solid, Svelte)
To avoid a build step, it is recommended to import dependencies from CDNs like esm.sh or jsDelivr and use JSDoc comments for type-checking instead of TypeScript.
Understand Jupyter widget communication fundamentals
To display a JavaScript-backed widget for a Python object in Jupyter, three components are required:
- Communication: A
Commobject (likecomm.base_comm.BaseComm) to exchange JSON messages between the Python kernel and the browser. - Python Representation: An object with a
_repr_mimebundle_method that returns theapplication/vnd.jupyter.widget-view+jsonMIME type, containing amodel_idthat matches theCommobject. The object must also support JSON serialization and an observer pattern to trigger messages on state change. - JavaScript Representation: A JS object aware of the
model_idthat can update the view or send messages back to the Python kernel (typically using@jupyter-widgets/base).
{ "application/vnd.jupyter.widget-view+json": { "model_id": "some-uuid" } }- Communication: A
Define anywidget lifecycle hooks using default exports
In anywidget v0.9, the preferred way to define a widget's front-end behavior is using a
defaultobject export. This allows developers to hook into two distinct stages of the widget lifecycle:initialize: Executed once per widget instance during model initialization. Use this to set up non-view event handlers or state shared across multiple views. It has access to themodel.render: Executed once per view during rendering. Use this to set up view-specific event handlers or access the DOM. It has access to bothmodeland theelDOM element.
Alternatively, the
defaultexport can be a function that returns this interface, which is useful for initializing front-end specific state for the widget's lifetime.// Basic usage export default { initialize({ model }) { /* setup shared state or global handlers */ }, render({ model, el }) { /* setup view-specific DOM elements */ }, }; // Advanced usage: using a function to maintain state export default () => { let valueHistory = []; return { initialize({ model }) { model.on("change:value", () => valueHistory.push(model.get("value"))); }, render({ model, el }) { el.innerText = `The history is ${valueHistory}`; model.on("change:value", () => { el.innerText = `The history is ${valueHistory}`; }); }, }; };Migrate named render exports to default exports in anywidget v0.9
anywidget v0.9 deprecates namedrenderexports. To migrate, move therenderfunction into adefaultobject export.// OLD (Deprecated) export function render({ model, el }) { /* ... */ } // NEW (Preferred) function render({ model, el }) { /* ... */ } export default { render };Synchronize Python and JavaScript state using Traitlets
Traitlets are the primary way to sync data between Python and the front end. In Python, define a trait with
sync=True. In JavaScript, use themodelobject to get, set, or listen for changes to that trait.Important: When calling
model.set()in JavaScript, you must callmodel.save_changes()to push the update back to the Python kernel.import anywidget import traitlets class CustomWidget(anywidget.AnyWidget): _esm = "index.js" my_value = traitlets.Int(0).tag(sync=True)// index.js function render({ model, el }) { // 1. Get value let val = model.get("my_value"); // 2. Set value model.set("my_value", 42); model.save_changes(); // 3. Listen for changes model.on("change:my_value", () => { console.log(`Value changed to: ${model.get("my_value")}`); }); } export default { render };Send custom messages from Python to JavaScript
For communication that doesn't fit a state-sync model, use custom messages. In JavaScript, listen for the
msg:customevent on the model. In Python, use the.send()method on the widget instance.Caveat: Custom messages are only received if the widget is already displayed in the notebook (the front-end code must have executed and registered the listener) before
.send()is called.import anywidget class CustomMessageWidget(anywidget.AnyWidget): _esm = """ function render({ model, el }) { model.on("msg:custom", msg => { console.log(`new message: ${JSON.stringify(msg)}`); }); } export default { render }; """ widget = CustomMessageWidget() widget # Must display the widget first widget.send({ "type": "my-event", "foo": "bar" })Install anywidget
Install the anywidget library using pip.pip install anywidgetPass widget references from Python to JavaScript
In Python, you can pass one widget directly into another. The serialization layer automatically detects objects that extend
anywidget.AnyWidgetor implement the anywidget protocol and converts them to a wire-format string ("anywidget:<model_id>").Use
anywidget.WidgetTraitto validate that a trait is assigned an anywidget-compatible object. For type annotations in dataclasses or Pydantic models, useanywidget.Widget.import anywidget import traitlets class Slider(anywidget.AnyWidget): _esm = "slider.js" value = traitlets.Float(0.0).tag(sync=True) class Dashboard(anywidget.AnyWidget): _esm = "dashboard.js" # Use WidgetTrait to validate the reference control = anywidget.WidgetTrait().tag(sync=True) slider = Slider(value=50) # Pass the widget instance directly Dashboard(control=slider)Install anywidget
Install the anywidget library using pip to start creating reusable interactive widgets.pip install anywidget