Any notebook served: authoring and sharing reusable interactive widgets
1Introduction¶
Computational notebooks combine live code, equations, prose, visualizations,
and other media within a single environment. The Jupyter project
Kluyver et al., 2016Granger & Pérez, 2021 has been instrumental the success of
notebooks, which have become the tool of choice for interactive computing in
data science, research, and education. Key to Jupyter’s widespread adoption is
its modular architecture and standardization of interacting components, which
have fostered an extensive ecosystem of tools that reuse these elements. For
example, the programs responsible for executing code written in notebooks,
called kernels, can be implemented by following the Jupyter Messaging
Protocol Jupyter Client documentation, n.d.. This design allows users to install kernels for
various different languages and types of computation. Similarly, Jupyter’s
open-standard notebook format (.ipynb
) ensures that notebooks can be shared
and interpreted across different platforms nbformat documentation, n.d..
Jupyter’s modular architecture has also supported innovation in notebook front ends — the user interfaces (UIs) for editing and executing code, as well as inspecting kernel outputs. The success of the classic Jupyter Notebook Kluyver et al., 2016 spurred the development of several similar Jupyter-compatible platforms (JCPs), such as JupyterLab, Google Colab, and Visual Studio Code. These platforms provide unique UIs and editing features while reusing Jupyter’s other standardized components. This interoperability allows users to choose the platform that best suits their needs, while retaining a familiar interactive computing experience with the ability to share notebooks. Furthermore, the separation of computation from UI offers users a wide selection of both front ends and kernels. However, the proliferation of JCPs has led to significant challenges for Jupyter Widgets, a key component of interactive user interfaces in Jupyter.
Jupyter Widgets extend notebook outputs with interactive views and controls for objects residing in the kernel Jupyter documentation, n.d.. For instance, the ipywidgets library, besides defining the widget communication protocol, provides basic form elements like buttons, sliders, and dropdowns to adjust individual variables. Other community projects offer interactive visualizations for domain-specific needs, such as 3D volume rendering (ipyvolume), biological data exploration Manz et al., 2023Manz et al., 2022Keller et al., 2021, and mapping (ipyleaflet, pydeck, lonboard), which users can update by executing other code cells or interact with in the UI to update properties in the kernel.
Widgets are unique among Jupyter components in that they consist of two separate programs — kernel-side code and front-end code — that communicate directly via custom messages Figure 1, rather than through a mediating Jupyter process. With widgets, communication is bidirectional: a kernel action (e.g., the execution of a notebook cell) can update the UI, such as causing a slider to move, while a user interaction (e.g., dragging a slider), can drive changes in the kernel, like updating a variable. This two-way communication distinguishes widgets from other interactive elements in notebook outputs, such as HTML displays, which cannot communicate back and forth with the kernel.
Widgets are intended to be pluggable components, similar to kernels. However, only the protocol for communication between kernel and front-end widget code, known as the Jupyter Widgets Message Protocol, is standardized. Critical components, such as the distribution format for front-end modules and methods for discovering, loading, and executing these modules, remain unspecified. As a result, JCPs have adopted diverse third-party module formats, installation procedures, and execution models to support widgets. These inconsistencies place the onus on widget authors to ensure cross-JCP compatibility.
JCPs load front-end widget code by searching in various external sources, such as local file systems or Content Distribution Networks (CDNs) while kernel-side (Python) code loads and runs in the kernel Figure 2. These access patterns split the distribution of custom widgets between Python and JavaScript package registries, complicating releases and requiring widget authors to understand both packaging ecosystems. This division creates challenges, especially in shared, multi-user environments like JupyterHub. Since kernels cannot host static assets, users cannot independently install widgets or manage versions. Instead, widget front-end code must be pre-installed on the Jupyter webserver, typically by an administrator. Consequently, users are restricted to administrator-installed widgets and versions, unable to upgrade or add new ones independently.
These limitations make widget development complex and time-consuming, demanding expertise in multiple domains. They make user experiences across JCPs frustrating and unreliable. The high barrier to entry discourages new developers and domain scientists from contributing to widgets, limiting growth and diversity in the ecosystem. This leaves a small group of authors responsible for adapting their code for cross-JCP compatibility, hindering widget reliability and maintainability.
2Methodology¶
The anywidget project simplifies the authoring, sharing, and distribution of Jupyter Widgets by (i) introducing a standard for widget front-end code based on the web browser’s native module system, (ii) loading these modules from the kernel, and (iii) providing the necessary “glue code” to adapt existing JCPs to load and execute these components Figure 3. This separation of concerns allows widget authors to write portable code that runs consistently across JCPs without manual installation steps.
Packaging custom Jupyter Widgets is complex due to the need to adapt JavaScript source code for various module systems used by JCPs. Initially, JavaScript lacked a built-in module system, leading JCPs to adopt diverse third-party solutions. Without a standardized widget front-end format, authors transform their code for each JCP. In the context of Jupyter Notebook and JupyterLab, this problem is described in the Jupyter Widgets documentation Low Level Widget Explanation, n.d. as follows:
Because the API of any given widget must exist in the kernel, the kernel is the natural place for widgets to be installed. However, kernels, as of now, don’t host static assets. Instead, static assets are hosted by the webserver, which is the entity that sits between the kernel and the front-end. This is a problem because it means widgets have components that need to be installed both in the webserver and the kernel. The kernel components are easy to install, because you can rely on the language’s built-in tools. The static assets for the webserver complicate things, because an extra step is required to let the webserver know where the assets are.
ECMAScript (ES) modules, introduced in 2015, are an official standard for packaging JavaScript code for reuse Shu-Guo et al., 2023. While most JCPs predate its standardization, ES modules are universally supported by browsers today. By adopting ES modules, anywidget is able to use the browser’s native import mechanism to load and execute widget front-end code from the Jupyter kernel, thereby bypassing those used by JCPs and eliminating third-party dependencies. This approach not only overcomes many development challenges, but also eliminates installation procedures for front-end code. Consequently, developers can prototype and share widgets directly within notebooks, making them more reliable and easier to use.
An anywidget front-end module (AFM) is an ES module with a default
export
defining widget behavior. This export includes lifecycle methods, or “hooks,”
for managing a widget’s lifecycle stages: initialization, rendering, and
destruction Figure 4. AFM lifecycle methods receive the interfaces required
for kernel communication and notebook output modifications as arguments, rather
than creating them internally or relying on global variables provided by the
JCP. This practice, known as dependency injection Fowler, 2004, improves AFM
portability by making integration interfaces explicit. New runtimes can support
AFMs by implementing the required APIs, and existing JCPs can refactor their
internals without breaking existing (any)widgets. For projects that want to take
advantage of advanced front-end tooling, anywidget also provides authoring
utilities to write AFMs such as “bridge” libraries for popular web frameworks
Manz et al., 2024.
Widget authorship is particularly challenging due to the need to integrate front-end code that communicates with kernel code in a heterogeneous set of environments. The anywidget project addresses these challenges by focusing on the standardization, development, and distribution of widget front-end modules, including the associated API to communicate with a computational kernel. The anywidget Python package serves as an adapter library that turns each JCP into an AFM-compatible host environment. Finally, the anywidget project provides additional tooling to help developers evolve their prototypes into mature packages Manz et al., 2024.
An additional goal for an interactive computing paradigm like widgets is composability. For example, ipywidgets provides utilities to link widgets together or lay out grids of widgets on the page. These primitives allow widgets to derive from others, enabling reuse of front-end and kernel integrations in new ways. Importantly, Jupyter Widgets authored with the anywidget Python package extend from ipywidgets, making them interoperable with core ipywidgets and traditional custom widgets. By aligning with ipywidgets, anywidget promotes composability and prevents fragmentation of the ecosystem.
3Features¶
Adhering to predictable standards benefits both developers and end users in many other ways beyond JCP interoperability, such as...
3.1Web Over Libraries¶
Front-end libraries change rapidly and often introduce breaking changes, whereas the web platform remains more backward-compatible. Traditional Jupyter Widgets require extensions from UI libraries provided by JCPs, coupling widget implementations to third-party frameworks. In contrast, AFM defines a minimal set of essential interfaces focused on (1) communicating with the kernel and (2) modifying notebook output cells, without dictating models for state or UI. It allows widgets to be defined without dependencies, reducing boilerplate and preventing lock-in. Authors may still use third-party JavaScript libraries or tooling, but these are no longer necessary for JCP compatibility, publishing, or user installation.
3.2Rapid Iteration¶
The web ecosystem’s adoption of ES modules has led to new technologies that enhance developer experience and enable rapid prototyping. One such innovation is hot module replacement (HMR), a method that uses the browser’s module graph to dynamically update applications without reloading the page or losing state. Since traditional Jupyter Widgets rely on legacy module systems, they cannot benefit from HMR and instead require full page clearance, reload, and re-execution to see changes during development. By contrast, anywidget is able to provide opt-in HMR, implemented through the Jupyter messaging protocol, in order to support live development of custom widgets without any front-end tooling. For example, adjusting a widget’s appearance, such as a chart’s color scheme, updates the view instantly without re-executing cells or refreshing the page.
3.3Progressive Development¶
Anywidget makes it possible to prototype widgets directly within a notebook since all widget code is loaded from the kernel. Custom widgets can start as a few code cells and transition to separate files, gradually evolving into standalone scripts or packages – just like kernel-side programs Figure 3. In contrast, developing traditional Jupyter Widgets is a cumbersome process limited to the Jupyter Notebook and JupyterLab platforms. It involves using a project generator widget-cookiecutter, n.d.widget-ts-cookiecutter, n.d. to bootstrap a project with over 50 files, creating and installing a local Python package, bundling JavaScript code, and manually linking build outputs to install extensions Anywidget documentation cookiecutter comparison, n.d.. By removing these barriers, anywidget accelerates development and allows prototypes to grow into robust tools over time. In fact, as of January 2024, the JavaScript cookiecutter widget-cookiecutter, n.d. project has been deprecated and directs users to use anywidget instead.
3.4Simplified Publishing¶
Serving AFMs and other static assets from the kernel removes the need to publish widget kernel-side and front-end code separately and coordinate their releases. For example, many JCPs retrieve traditional widget Javascript code from npm, misusing the registry for distributing specialized programs rather than reusable JavaScript modules. Instead, with anywidget, developers can publish a widget (kernel-side module, AFM, and stylesheets) as a unified package to the distribution channels relevant to the kernel language, such as the Python Package Index. Consolidating the distribution process this way greatly simplifies publishing and discovery.
4Impact and Outlook¶
Anywidget fills in the specification gaps for Jupyter Widgets by embracing open
standards and carefully separating developer concerns. It defines an API for
authoring portable and reusable widget components that decouples widget
authorship from JCPs, resulting in multiple downstream benefits. First,
anywidget—not widget authors—ensures compatibility and interoperability across
existing JCPs, and authors can focus on important features rather than wrestle
with build configuration and tooling. Anywidget’s adapter layer simply loads
and executes standardized front-end modules, meaning that the compatibility
afforded does not introduce overhead to the runtime performance of a widget
itself. A widget authored with anywidget has the same performance
characteristics as a widget tediously (and correctly) authored using the
traditional Jupyter Widgets system. Second, by circumventing bespoke JCP import
systems and loading web-standard ES modules from the kernel, anywidget does
away with manual installation steps and delivers an improved developer
experience during widget authorship. Third, anywidget unifies and simplifies
widget distribution. Widgets can be prototyped and shared as notebooks, or
mature into pip
-installable packages and distributed like other tools in the
Python data science ecosystem. End users benefit from standardization because
widgets are easy to install and behave consistently across platforms.
Since its release, anywidget has led to a proliferation of widgets and a more diverse widget ecosystem Figure 5. New widgets range from educational tools for experimenting with toy datasets (e.g., DrawData) to high-performance data visualization libraries (e.g., Lonboard, Jupyter-Scatter Lekschas & Manz, 2024, Mosaic Heer & Moritz, 2024) and research projects enhancing notebook interactivity (e.g., Persist Gadhave et al., 2023, cev Manz et al., 2024). Many of these tools use anywidget’s binary data transport to enable efficient interactive visualization with minimal overhead by avoiding JSON serialization. Existing widget projects have also migrated to anywidget (e.g., higlass-python, ipyaladin) and other libraries have introduced or refactored existing widget functionality to use anywidget (e.g., Altair VanderPlas et al., 2018) due to the simplified distribution and authoring capabilities.
The portable widget standard also extends the anywidget ecosystem to platforms beyond Jupyter. Popular web frameworks and dashboarding libraries such as Voila, Panel, Shiny for Python, and Solara support Jupyter Widgets, and therefore also allow users to embed anywidgets in standalone web applications. Efforts are underway to add more specialized, built-in support for AFM as well. For example, marimo, a new reactive notebook for Python, is standardizing its third-party plugin API on AFM, allowing anywidgets to run natively without additional “glue code.” Developers of the Panel web-application framework are also exploring deeper integration with AFM to enable reuse with their kernel-side reactivity systems.
AFM’s standardization of widget front-end code extends reusability beyond the Python ecosystem. Its API structure is intentionally agnostic to backend processing, enabling creative and dynamic ways of hosting AFM-based widgets. Many simple widgets can function without any compute backend, while more complex ones can be upgraded with a computational backend when necessary. This flexibility allows for innovative hosting solutions across diverse platforms, from fully static web pages to full-stack web applications. By decoupling front-end interactivity from backend complexity, AFM empowers developers to create scalable, platform-independent scientific visualizations and tools, adaptable to a wide range of computational requirements and user scenarios.
A current limitation of anywidget is that using relative file imports for local dependencies requires bundling into a single ES module, which introduces a build step and prevents shipping AFM source code directly. This build step can be avoided by using a single file and loading third-party dependencies via URLs, though larger projects may still benefit from bundling. Notably, traditional widgets always require a build step and mandate using a specific bundler (Webpack) with a bespoke multi-JCP configuration. In contrast, anywidget makes bundling optional and targets a single standard ES module, which can be produced easily from a variety of bundlers. Future browser support for importmaps may enable local dependencies without bundling, simplifying anywidget development further as web standards evolve.
Tools for data visualization and interactivity have greater impact when compatible with more platforms, but achieving compatibility involves trade-offs Wang et al., 2024. The full capabilities of the widget system, such as bidirectional communication, are often inaccessible to authors due to development difficulty and maintenance efforts. Adopting standards can minimize these impediments, enabling both broad compatibility and advanced capabilities for users. A recent article enumerates these challenges and advocates for standardized solutions to democratize the creation of notebook visualization tools across notebook platforms Wang et al., 2024. Anywidget addresses this by introducing a standard that removes friction in widget development and sharing, making authorship practical and accessible.
Acknowledgments¶
We thank Talley Lambert for his contributions to the project, and David Kouril for his suggestions on the manuscript and figures. We also thank the anywidget community as well as members of the Abdennur and HIDIVE labs for helpful discussions.
Funding
TM, NG, and NA acknowledge funding from the National Institutes of Health (UM1 HG011536, OT2 OD033758, R33 CA263666, R01 HG011773).
- Kluyver, T., Ragan-Kelley, B., Pérez, F., Granger, B., Bussonnier, M., Frederic, J., Kelley, K., Hamrick, J., Grout, J., Corlay, S., Ivanov, P., Avila, D., Abdalla, S., & Willing, C. (2016). Jupyter Notebooks – a publishing format for reproducible computational workflows. In F. Loizides & B. Scmidt (Eds.), Positioning and Power in Academic Publishing: Players, Agents and Agendas (pp. 87–90). IOS Press. 10.3233/978-1-61499-649-1-87
- Granger, B. E., & Pérez, F. (2021). Jupyter: Thinking and Storytelling With Code and Data. Comput. Sci. Eng., 23(2), 7–14. 10.1109/MCSE.2021.3059263
- Jupyter Client documentation. (n.d.). https://jupyter-client.readthedocs.io/en/stable/
- nbformat documentation. (n.d.). https://nbformat.readthedocs.io/en/stable/format_description.html
- Jupyter documentation. (n.d.). https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html