Anywidget: Custom Jupyter Widgets Made Easy

raw JSON →
0.10.0 verified Sat Apr 25 auth: no python

Anywidget is a Python library that dramatically simplifies the process of creating custom interactive widgets for computational notebooks, running in environments like Jupyter, JupyterLab, Google Colab, VS Code, and Marimo. It's both a specification and a toolkit, allowing developers to define widget front-end code using standard ECMAScript modules (ESM) within a Python class. Currently at version 0.10.0, it features a rapid release cadence with frequent updates.

pip install "anywidget[dev]"
error ModuleNotFoundError: No module named 'anywidget'
cause The 'anywidget' package is not installed in the Python environment.
fix
Install the package using pip: 'pip install anywidget'.
error ImportError: cannot import name 'AnyWidget' from 'anywidget'
cause The 'AnyWidget' class is not available in the 'anywidget' module, possibly due to an incorrect import statement or version mismatch.
fix
Ensure you are using the correct import statement: 'from anywidget import AnyWidget'.
error Failed to load model class 'AnyModel' from module 'anywidget' Error: No version of module anywidget is registered
cause The JavaScript code for 'anywidget' is not available, possibly due to installation issues or the need to reload the Jupyter environment.
fix
Reload the browser tab running Jupyter or ensure 'anywidget' is installed before launching Jupyter.
error Failed to load model class 'AnyModel' from module 'anywidget' Error: No version of module anywidget is registered.
cause This error typically occurs when anywidget's JavaScript frontend code is not properly loaded or activated in the Jupyter environment, often happening if anywidget was installed after Jupyter was launched or if there's an environment mismatch (e.g., in VS Code).
fix
Restart your Jupyter server or reload the browser tab (not just the kernel). Ensure anywidget is installed in the correct environment where Jupyter is running, and if using anywidget[dev], that your environment variables are correctly set for Hot Module Replacement (HMR).
error Uncaught (in promise) Error: [anywidget] Failed to initialize model.
cause This specific error arises due to an incompatibility between `anywidget` and `websockets` versions 16.0 or higher, affecting the WebSocket communication layer `anywidget` relies on.
fix
Pin your websockets dependency to a version less than 16.0 in your requirements.txt or conda environment.yml, for example: websockets<16.0.
breaking Since v0.9, the preferred way to define front-end widget code has shifted to using lifecycle hooks (`initialize`, `render`) exported as a default object from the `_esm` module, replacing the direct `export function render(view)` pattern.
fix Refactor your `_esm` JavaScript to `export default { initialize({ model }), render({ model, el }) { ... } };`. The `render` function's argument `view` is now split into `{ model, el }`.
breaking If you are using the Vite plugin for `anywidget`, the import path for it changed from `anywidget/vite` to `@anywidget/vite` to allow for independent versioning.
fix Update your Vite configuration (e.g., `vite.config.mjs`) to `import anywidget from "@anywidget/vite";`.
gotcha Hot Module Replacement (HMR) for live development requires opting in by either setting the `ANYWIDGET_HMR` environment variable to `1` or, preferably, referencing frontend code via `pathlib.Path` for `_esm` and `_css` attributes.
fix For in-notebook prototyping, define `_esm` and `_css` as `pathlib.Path('index.js')` (or similar) to external files, and ensure the `ANYWIDGET_HMR` environment variable is set if you expect HMR without explicit file paths.
gotcha The `__repr__` method for `AnyWidget` subclasses was overridden to provide a less verbose, `object.__repr__` like output, instead of the full serialization of all trait values inherited from `ipywidgets.Widget`.
fix Be aware that inspecting `AnyWidget` instances directly might no longer show a full dump of all synchronized traits. For trait values, access them directly (e.g., `widget.count`).
gotcha Custom messages sent from Python (`widget.send()`) will only be received by the frontend if the widget's view has already been rendered (i.e., the widget has been `display()`ed). Sending messages before the widget is displayed will result in them being lost.
fix Ensure that the `AnyWidget` instance is displayed in the notebook or environment before sending any custom messages from the Python kernel.
breaking For `anywidget` framework bridges like `@anywidget/react` and `@anywidget/svelte`, there have been breaking changes to align with newer versions of their respective frameworks (React 18's `useSyncExternalStore` and Svelte 5's runes reactivity). This primarily affects users building widgets with these specific frontend frameworks.
fix Refer to the `anywidget` release notes and the specific framework bridge documentation for migration guides if you are using `@anywidget/react` or `@anywidget/svelte`.
pip install anywidget
conda install -c conda-forge anywidget
runtime variant status import time mem disk
3.10-alpine dev 1.74s 28.3MB 63.2M
3.10-alpine default 1.81s 28.3MB 59.8M
3.10-slim dev 1.35s 28.3MB 64M
3.10-slim default 1.19s 28.3MB 60M
3.11-alpine dev 2.41s 32.6MB 68.0M
3.11-alpine default 2.44s 32.6MB 64.3M
3.11-slim dev 2.14s 32.6MB 68M
3.11-slim default 2.76s 32.6MB 65M
3.12-alpine dev 2.47s 31.7MB 59.1M
3.12-alpine default 2.43s 31.7MB 55.6M
3.12-slim dev 2.75s 31.7MB 59M
3.12-slim default 3.04s 31.7MB 56M
3.13-alpine dev 2.38s 33.3MB 58.8M
3.13-alpine default 2.21s 33.3MB 55.2M
3.13-slim dev 2.62s 33.3MB 59M
3.13-slim default 2.40s 33.3MB 56M
3.9-alpine dev 1.68s 28.6MB 62.2M
3.9-alpine default 1.61s 28.6MB 58.9M
3.9-slim dev 1.62s 28.6MB 63M
3.9-slim default 1.44s 28.6MB 60M

This example defines a simple counter widget using `anywidget.AnyWidget`. It includes both Python (`traitlets.Int`) and JavaScript (`_esm`) components. The JavaScript code defines how the widget renders and interacts with the Python backend, including handling button clicks and updating a synchronized `count` traitlet. The `initialize` and `render` lifecycle hooks are the preferred way to define the frontend logic since v0.9.

import anywidget
import traitlets
from IPython.display import display

class CounterWidget(anywidget.AnyWidget):
    _esm = """
    export default {
        initialize({ model }) {
            // Optional: run once per widget instance
            console.log('Widget initialized');
        },
        render({ model, el }) {
            let getCount = () => model.get('count');
            let button = document.createElement('button');
            button.innerHTML = `count is ${getCount()}`;

            button.addEventListener('click', () => {
                model.set('count', getCount() + 1);
                model.save_changes();
            });

            model.on('change:count', () => {
                button.innerHTML = `count is ${getCount()}`;
            });
            el.appendChild(button);

            return () => {
                // Optional: cleanup on view removal
                button.removeEventListener('click', () => {});
            };
        }
    };
    """
    _css = """ button { padding: 5px 10px; border-radius: 5px; background-color: #f0f0f0; } """
    count = traitlets.Int(0).tag(sync=True)

widget = CounterWidget()
display(widget)
# You can also interact with the widget from Python
# widget.count = 5 # This will update the frontend