{"id":3872,"library":"anywidget","title":"Anywidget: Custom Jupyter Widgets Made Easy","description":"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.","status":"active","version":"0.10.0","language":"en","source_language":"en","source_url":"https://github.com/manzt/anywidget","tags":["jupyter","widgets","frontend","javascript","interactive","notebook"],"install":[{"cmd":"pip install \"anywidget[dev]\"","lang":"bash","label":"For development with HMR"},{"cmd":"pip install anywidget","lang":"bash","label":"Minimal installation"},{"cmd":"conda install -c conda-forge anywidget","lang":"bash","label":"Conda installation"}],"dependencies":[{"reason":"Used for defining stateful properties synchronized between Python and JavaScript.","package":"traitlets","optional":false},{"reason":"While anywidget abstracts away much of ipywidgets, it's built on top of the Jupyter Widgets framework. The `[dev]` extra often pulls in ipywidgets for a complete Jupyter environment.","package":"ipywidgets","optional":true}],"imports":[{"symbol":"AnyWidget","correct":"from anywidget import AnyWidget"},{"note":"Used for defining synchronized state between Python and JavaScript.","symbol":"Int","correct":"import traitlets\nclass MyWidget(AnyWidget):\n    value = traitlets.Int(0).tag(sync=True)"},{"note":"Used for defining synchronized string state.","symbol":"Unicode","correct":"import traitlets\nclass MyWidget(AnyWidget):\n    text = traitlets.Unicode('Hello').tag(sync=True)"}],"quickstart":{"code":"import anywidget\nimport traitlets\nfrom IPython.display import display\n\nclass CounterWidget(anywidget.AnyWidget):\n    _esm = \"\"\"\n    export default {\n        initialize({ model }) {\n            // Optional: run once per widget instance\n            console.log('Widget initialized');\n        },\n        render({ model, el }) {\n            let getCount = () => model.get('count');\n            let button = document.createElement('button');\n            button.innerHTML = `count is ${getCount()}`;\n\n            button.addEventListener('click', () => {\n                model.set('count', getCount() + 1);\n                model.save_changes();\n            });\n\n            model.on('change:count', () => {\n                button.innerHTML = `count is ${getCount()}`;\n            });\n            el.appendChild(button);\n\n            return () => {\n                // Optional: cleanup on view removal\n                button.removeEventListener('click', () => {});\n            };\n        }\n    };\n    \"\"\"\n    _css = \"\"\" button { padding: 5px 10px; border-radius: 5px; background-color: #f0f0f0; } \"\"\"\n    count = traitlets.Int(0).tag(sync=True)\n\nwidget = CounterWidget()\ndisplay(widget)\n# You can also interact with the widget from Python\n# widget.count = 5 # This will update the frontend\n","lang":"python","description":"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."},"warnings":[{"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 }`.","message":"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.","severity":"breaking","affected_versions":"0.9.0 and later"},{"fix":"Update your Vite configuration (e.g., `vite.config.mjs`) to `import anywidget from \"@anywidget/vite\";`.","message":"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.","severity":"breaking","affected_versions":"0.7.0 and later"},{"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.","message":"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.","severity":"gotcha","affected_versions":"0.2.0 and later"},{"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`).","message":"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`.","severity":"gotcha","affected_versions":"0.9.19 and later"},{"fix":"Ensure that the `AnyWidget` instance is displayed in the notebook or environment before sending any custom messages from the Python kernel.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"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`.","message":"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.","severity":"breaking","affected_versions":"Specific versions of `@anywidget/react` and `@anywidget/svelte` (e.g., `0.0.1` for `@anywidget/svelte`)"}],"env_vars":null,"last_verified":"2026-04-11T00:00:00.000Z","next_check":"2026-07-10T00:00:00.000Z"}