<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - anywidget</title><link href="https://jasongrout.github.io/medium-archive/pelican/" rel="alternate"/><link href="https://jasongrout.github.io/medium-archive/pelican/feeds/tag-anywidget.atom.xml" rel="self"/><id>https://jasongrout.github.io/medium-archive/pelican/</id><updated>2026-04-16T15:28:00+00:00</updated><subtitle>The Project Jupyter blog: news, releases, and community stories, archived from blog.jupyter.org.</subtitle><entry><title>The MyST AnyWidget Directive</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2026/the-myst-anywidget-directive/" rel="alternate"/><published>2026-04-16T15:28:00+00:00</published><updated>2026-04-16T15:28:00+00:00</updated><author><name>Steve Purves</name></author><id>tag:jasongrout.github.io,2026-04-16:/medium-archive/pelican/posts/2026/the-myst-anywidget-directive/</id><summary type="html">&lt;p&gt;Authors of MyST Markdown can now embed interactive JavaScript widgets directly in content using the new {anywidget} directive.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Authors of MyST Markdown can now embed interactive JavaScript widgets directly in content using the new &lt;code&gt;{anywidget}&lt;/code&gt; directive.&lt;/p&gt;
&lt;h2 id="what-this-means"&gt;What this means&lt;/h2&gt;
&lt;p&gt;You can now add any javascript interactivity to your MyST Markdown website or Jupyter Book.&lt;/p&gt;
&lt;p&gt;Here’s an example that creates a clickable button 👇 to see an interactive version check out &lt;a href="https://jupyterbook.org/blog/posts/2026/the-myst-anywidget#how-we-got-here"&gt;the post on the Jupyter Book blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2026/the-myst-anywidget-directive/images/001-1_TksTxGw_ahJkRxlsIVU5_g.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="how-we-got-here"&gt;How we got here&lt;/h2&gt;
&lt;p&gt;The idea of a portable widget interface for interactive computing isn’t new: &lt;a href="https://anywidget.dev"&gt;anywidget&lt;/a&gt; has been an emerging standard in the Jupyter community for a while, leveraging modern Javascript principles (via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"&gt;ESM Modules&lt;/a&gt;), and giving you a simple &lt;code&gt;initialize({ model }); render({ model, el })&lt;/code&gt;&lt;a href="https://jupyterbook.org/blog/posts/2026/the-myst-anywidget#fn-model"&gt;[1]&lt;/a&gt; contract so that widgets can be written once and reused across notebooks. AnyWidget in Jupyter interfaces supports tight integration with the kernel, allowing interactions similar to &lt;code&gt;ipywidgets&lt;/code&gt;&lt;a href="https://jupyterbook.org/blog/posts/2026/the-myst-anywidget#fn-jupyter"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From the MyST Markdown and Jupyter Book point of view, we’ve focussed on supporting the &lt;code&gt;render()&lt;/code&gt; side of the AnyWidget interface (see &lt;a href="https://anywidget.dev/en/afm/#what-is-afm"&gt;AnyWidget Frontend Modules&lt;/a&gt;) to allow MyST users to bring any kind of javascript-based interactivity into their articles and books &lt;em&gt;without having to lean on Jupyter at all&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That part of the story started at SciPy 2024 with &lt;a href="https://github.com/manzt"&gt;Trevor Mantz&lt;/a&gt; and Steve Purves hacking through a proof of concept during the sprints. Curvenote built working support for &lt;a href="https://www.npmjs.com/package/@curvenote/any-widget"&gt;AnyWidgets as a MyST Markdown extension&lt;/a&gt; while working with researchers to support domain specific visualizations, and then upstreamed the implementation to create the &lt;code&gt;{anywidget}&lt;/code&gt; directive that has just been released in &lt;code&gt;mystmd&lt;/code&gt; and &lt;code&gt;jb2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The new directive in &lt;code&gt;mystmd&lt;/code&gt; and the respective supporting package in &lt;code&gt;@myst-theme/anywidget&lt;/code&gt; evolved with input from the JupyterBook team. Here’s how it is currently structured:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;anywidget&lt;/code&gt; is a new node in the MyST AST, meaning first-class support for this capability outside of notebooks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mystmd&lt;/code&gt; will bundle ESM and CSS modules at build time, ensuring dependencies are packaged with the book/article when it is published or deployed.&lt;/li&gt;
&lt;li&gt;The ESM and CSS modules can either be (a) hosted remotely (which makes sense for shared widgets that many people are using or collaborating around) or (b) added as local files (which makes sense for widgets specific to a single book/website).&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;NodeRenderer&lt;/code&gt; part of the release is an independent package &lt;a href="https://www.npmjs.com/package/@myst-theme/anywidget"&gt;@myst-theme/anywidget&lt;/a&gt; that can be optionally adopted by theme developers (it’s already built into the core themes).&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;mystmd&lt;/code&gt; and Jupyter Book users, to upgrade to the latest theme. Run &lt;code&gt;myst clean --templates&lt;/code&gt; before you next start your server and the latest version will be downloaded.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="usage"&gt;Usage&lt;/h2&gt;
&lt;p&gt;From an author’s perspective, point the directive at an ESM module (either a URL to a hosted script or a local path) and the widget runs in the page with its own state and DOM. You may optionally pass in a CSS URL/path and a JSON body of props to initialize the widget model.&lt;/p&gt;
&lt;p&gt;To understand how to build your own widgets, the &lt;a href="https://mystmd.org/guide/widgets"&gt;MyST widgets guide&lt;/a&gt; walks through the &lt;code&gt;render({ model, el })&lt;/code&gt; signature, styling with Shadow DOM, and cleanup on unmount. It’s the same mental model as &lt;code&gt;anywidget&lt;/code&gt; in Jupyter, so if you’ve written or seen Jupyter AnyWidgets, you’re already most of the way there.&lt;/p&gt;
&lt;p&gt;If you want to try it, the &lt;a href="https://github.com/jupyter-book/example-widgets"&gt;example-widgets repo&lt;/a&gt; has small demos (confetti, div-map, etc. Contributions are welcome!), and the &lt;a href="https://opensci.dev/"&gt;opensci.dev blog&lt;/a&gt; has some live examples using scientific datasets.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;
&lt;p&gt;Widget support in MyST is still marked experimental so the details may evolve. There are still some things on the roadmap for &lt;code&gt;{anywidget}&lt;/code&gt; which will no doubt expand as we hear of new requirements from widget creators, but we expect the following to be close to the top of the stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;model&lt;/code&gt; part of the interface, which will allow multiple instances of a widget to communicate on the page&lt;a href="https://jupyterbook.org/blog/posts/2026/the-myst-anywidget#fn-model"&gt;[1]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Shipping additional dependencies like static files that the widgets may need&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The JupyterBook team is actively working on improving and evolving the widget model and integrating with Jupyter. We look forward to seeing what the community builds — visit the &lt;a href="https://discord.mystmd.org/"&gt;discord&lt;/a&gt; to showcase what you have built!&lt;/p&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;A shout out to my co-contributors on this work; to &lt;a href="https://github.com/manzt"&gt;@mantz&lt;/a&gt; for the original hacking, &lt;a href="https://github.com/rowanc1"&gt;@rowanc1&lt;/a&gt; and &lt;a href="https://github.com/fwkoch"&gt;@fwkoch&lt;/a&gt; for iterating on the downstream code, &lt;a href="https://github.com/agoose77"&gt;@agoose77&lt;/a&gt; for working with me on the upstream changes, &lt;a href="https://github.com/choldgraf"&gt;@choldgraph&lt;/a&gt; for insightful reviews and a docs! and &lt;a href="https://github.com/jupyter-book"&gt;the broader Jupyter Book Team&lt;/a&gt; for all the reviews and feedback.&lt;/p&gt;
</content><category term="anywidget"/><category term="Jupyter Book"/><category term="MyST"/></entry><entry><title>anywidget: Jupyter Widgets Made Easy</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/" rel="alternate"/><published>2023-07-11T23:09:00+00:00</published><updated>2023-07-11T23:09:00+00:00</updated><author><name>Trevor Manz</name></author><id>tag:jasongrout.github.io,2023-07-11:/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/</id><summary type="html">&lt;p&gt;anywidget is a Python library that makes it simple and enjoyable to create custom Jupyter Widgets that run in classic Jupyter notebooks…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/manzt/anywidget"&gt;&lt;em&gt;&lt;strong&gt;anywidget&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt; &lt;em&gt;is a Python library that makes it simple and enjoyable to create custom Jupyter Widgets that run in classic Jupyter notebooks, JupyterLite, JupyterLab, Google Colab, VS Code, and more. It focuses on:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Prototyping&lt;/strong&gt;&lt;/em&gt;*: Create custom widgets right within your notebook.*&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Portability&lt;/strong&gt;&lt;/em&gt;*: Share widgets as Python scripts or pip-installable packages.*&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Productivity&lt;/strong&gt;&lt;/em&gt;: &lt;em&gt;Enjoy a modern front-end developer experience that supports real-time updates and removes manual project setup and other boilerplate.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img alt="Realt-time widget developement in JupyterLab with anywidget. Changes to JavaScript source code are immediately reflected in the front end without requiring a page reload or clearing model state." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/001-1_VCG-57YX8IrfTyeby3Ybjg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;strong&gt;anywidget&lt;/strong&gt; enables real-time widget development entirely from within JupyterLab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;Skip ahead to see an&lt;/em&gt; &lt;a href="#d575"&gt;&lt;em&gt;example&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="why-making-widgets-is-hard"&gt;Why making widgets is hard&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Basics.html"&gt;Jupyter Widgets&lt;/a&gt; enrich notebooks with interactive JavaScript-based views and controls for Python objects in the Jupyter kernel. They enable a wide range of users, from students to professionals, to tailor their programming environment with custom or ready-made tools to interact with their programs and explore data. Consider a machine learning researcher adjusting model parameters with a slider or a computational biologist navigating a genome browser programmatically.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="A Jupyter Widget is comprised of two components: a JavaScript front-end and a Python object in the Jupyter kernel." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/002-0_k0vqkYqq7F0MaF7q.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Jupyter Widgets link JavaScript-based views and controls with objects in the Jupyter kernel&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A useful feature of widgets is that they may be packaged and distributed as pip-installable modules, offering reusable components to address both general and specialized needs. For example, the &lt;a href="https://ipywidgets.readthedocs.io/en/stable/"&gt;ipywidgets&lt;/a&gt; project provides basic elements like form controls and layout containers, while numerous community projects offer custom widgets for domain-specific tasks (e.g., &lt;a href="https://github.com/bqplot/bqplot"&gt;bqplot&lt;/a&gt;, &lt;a href="https://ipyvolume.readthedocs.io/en/latest/"&gt;ipyvolume&lt;/a&gt;, &lt;a href="https://ipyleaflet.readthedocs.io/en/latest/"&gt;ipyleaflet&lt;/a&gt;).&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Side-by-Side comparison of two large genomic interaction matrices in Jupyter notebook using the interactive HiGlass browser." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/003-1_vSfRgAQN2UnfDlYmhjruSg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Comparing two massive genomic contract matrices within Jupyter using a custom widget from &lt;a href="https://github.com/higlass/higlass-python"&gt;higlass-python&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;However, the growing number of Jupyter environments supporting &lt;code&gt;.ipynb&lt;/code&gt; files complicates custom widget creation and sharing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers must individually adapt front-end code to meet subtle, often undocumented, requirements of each notebook platform.&lt;/li&gt;
&lt;li&gt;Basic prototyping involves setting up a local Python package and manually installing extensions.&lt;/li&gt;
&lt;li&gt;Proper widget distribution requires deep understanding of both Python and JavaScript packaging.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ve expanded on these challenges &lt;a href="https://anywidget.dev/blog/introducing-anywidget/#the-multi-platform-problem"&gt;previously&lt;/a&gt;, but to put it simply: traditional widget development has a steep learning curve and maintenance is both error-prone and tedious.&lt;/p&gt;
&lt;h2 id="a-universal-widget-adapter"&gt;A universal widget adapter&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;anywidget&lt;/strong&gt; introduces a fresh approach for creating and sharing custom widgets. It lets you avoid traditional development complexities, simplifying the process and making it easier than ever to start building widgets.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Development without anywidget" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/004-0_W8GArznb6ZRmcxQ9.jpg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Development &lt;strong&gt;without&lt;/strong&gt; anywidget&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;anywidget&lt;/strong&gt; is &lt;em&gt;&lt;strong&gt;not&lt;/strong&gt;&lt;/em&gt; a new framework, but rather a compatibility layer around traditional Jupyter Widgets. It utilizes the standard module system now found in web browsers to let widget developers write front-end code that executes universally. Think of &lt;strong&gt;anywidget&lt;/strong&gt; as an adapter that runs your widget’s JavaScript across various notebook environments.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Development with anywidget" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/005-0_be82R4Wuyn-zpc_7.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Development &lt;strong&gt;with&lt;/strong&gt; anywidget&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With &lt;strong&gt;anywidget,&lt;/strong&gt; a custom widget consists of two components: a Python class and an &lt;a href="https://nodejs.org/api/esm.html#modules-ecmascript-modules"&gt;ECMAScript module&lt;/a&gt; (ESM) — or web-standard JavaScript. You just write ESM and &lt;strong&gt;anywidget&lt;/strong&gt; handles the platform-specifics quirks.&lt;/p&gt;
&lt;p&gt;It takes less than 20 lines of code to &lt;a href="https://github.com/jupyter-widgets/widget-cookiecutter/tree/master/%7B%7Bcookiecutter.github_project_name%7D%7D"&gt;recreate&lt;/a&gt; a simple “Hello World” widget.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;anywidget&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;traitlets&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ExampleWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anywidget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnyWidget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_esm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    export function render({ model, el }) {&lt;/span&gt;
&lt;span class="s2"&gt;        el.classList.add(&amp;quot;custom-widget&amp;quot;);&lt;/span&gt;
&lt;span class="s2"&gt;        function valueChanged() {&lt;/span&gt;
&lt;span class="s2"&gt;            el.textContent = model.get(&amp;quot;value&amp;quot;);&lt;/span&gt;
&lt;span class="s2"&gt;        }&lt;/span&gt;
&lt;span class="s2"&gt;        valueChanged();&lt;/span&gt;
&lt;span class="s2"&gt;        model.on(&amp;quot;change:value&amp;quot;, valueChanged);&lt;/span&gt;
&lt;span class="s2"&gt;    }&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;_css&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    .custom-widget {&lt;/span&gt;
&lt;span class="s2"&gt;        background-color: lightseagreen;&lt;/span&gt;
&lt;span class="s2"&gt;        padding: 0px 2px;&lt;/span&gt;
&lt;span class="s2"&gt;    }&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traitlets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ExampleWidget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can &lt;strong&gt;copy and paste&lt;/strong&gt; this code directly into Jupyter notebooks, JupyterLite, JupyterLab, Google Colab, or VS Code and it just works.&lt;/p&gt;
&lt;p&gt;No installation, build configuration, or bundlers.&lt;/p&gt;
&lt;p&gt;By comparison, creating an identical &lt;code&gt;ExampleWidget&lt;/code&gt; the traditional way involves forking a Python repo template (including ~50 files), building JavaScript source code with Node.js and webpack, and manually installing the local extensions in classic notebooks or JupyterLab (not compatible with Google Colab or VS Code).&lt;/p&gt;
&lt;h2 id="a-realistic-example"&gt;A realistic example&lt;/h2&gt;
&lt;p&gt;This tutorial demonstrates using &lt;strong&gt;anywidget&lt;/strong&gt; to address a &lt;a href="https://github.com/altair-viz/altair/issues/1153"&gt;long-standing issue&lt;/a&gt; in the &lt;a href="https://github.com/altair-viz/altair"&gt;Altair&lt;/a&gt;: retrieving data from a brush selection back into Python. You can either follow along here or execute the notebook yourself &lt;a href="https://colab.research.google.com/gist/manzt/2b83d3461c40ac7b43fde29515cb8077/extending_altair_with_anywidget.ipynb"&gt;in Colab&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’ll begin with an interactive scatterplot example from the Altair &lt;a href="https://github.com/altair-viz/altair#example"&gt;docs&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;altair&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;alt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;vega_datasets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;brush&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selection_interval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark_point&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Horsepower&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Miles_per_Gallon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lightgray&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;brush&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark_bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;count(Origin)&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;brush&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/006-1_KIB7LU5YpN90zEh0-Czilg.mp4" alt="An interactive scatterplot displaying cars. Miles per gallon by horsepower. Individual points (cars) are colored by their country of origin." loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Notice how the brush selection on the scatter plot filters the data in the linked bar chart. Neat, but unfortunately we are unable inspect the selected points because the selection is processed in JavaScript, and Altair lacks a mechanism to communicate back to Python.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sounds like a job for a widget!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Behind the scenes, Altair produces JSON that follows the &lt;a href="https://vega.github.io/vega-lite/"&gt;Vega-Lite&lt;/a&gt; visualization grammar. With &lt;strong&gt;anywidget&lt;/strong&gt;, we can create a custom widget to render this validated JSON independently and additionally relay the JavaScript-based selections back to Python.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;anywidget&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;traitlets&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ChartWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anywidget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnyWidget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_esm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;    import embed from &amp;quot;https://cdn.jsdelivr.net/npm/vega-embed@6/+esm&amp;quot;;&lt;/span&gt;
&lt;span class="s2"&gt;    &lt;/span&gt;
&lt;span class="s2"&gt;    export async function render({ model, el }) {&lt;/span&gt;
&lt;span class="s2"&gt;        let spec = JSON.parse(model.get(&amp;quot;spec&amp;quot;));&lt;/span&gt;
&lt;span class="s2"&gt;        let api = await embed(el, spec);&lt;/span&gt;
&lt;span class="s2"&gt;        api.view.addSignalListener(spec.params[0].name, (_, update) =&amp;gt; {&lt;/span&gt;
&lt;span class="s2"&gt;            console.log(update);&lt;/span&gt;
&lt;span class="s2"&gt;            model.set(&amp;quot;selection&amp;quot;, update);&lt;/span&gt;
&lt;span class="s2"&gt;            model.save_changes();&lt;/span&gt;
&lt;span class="s2"&gt;        });&lt;/span&gt;
&lt;span class="s2"&gt;    }&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traitlets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unicode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traitlets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What’s happening here? Our custom &lt;code&gt;ChartWidget&lt;/code&gt; is defined by subclassing &lt;code&gt;anywidget.AnyWidget&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_esm&lt;/code&gt; (line 6) defines the ECMAScript module for the widget. It imports necessary rendering dependencies (&lt;a href="https://github.com/vega/vega-embed"&gt;vega-embed&lt;/a&gt;) and exports &lt;code&gt;render&lt;/code&gt;: a function to displays the Vega-Lite chart and (importantly) links the JavaScript-based selection to Python.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec&lt;/code&gt; and &lt;code&gt;selection&lt;/code&gt; (lines 19–20) define stateful properties via &lt;a href="https://github.com/ipython/traitlets"&gt;traitlets&lt;/a&gt; that are accessible by both client JavaScript and Python. These represent the Vega-Lite JSON specification and the JavaScript-based brush selection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now our ESM takes care of rendering instead of Altair.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;chart_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChartWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# Prints updates log console (JupyterLab: View &amp;gt; Show Log Console)&lt;/span&gt;
&lt;span class="n"&gt;chart_widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;selection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;chart_widget&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/007-1_OsSPlHcgfeF8ehIPGtFmpw.mp4" alt="The same scatterplot as before except the JavaScript-based brush selection is printed to the (Python) Jupyter console any time it changes." loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The original cross-filtering behavior stays the same, but now we have access to the JavaScript selection &lt;em&gt;in Python&lt;/em&gt; via &lt;code&gt;chart_widget.selection&lt;/code&gt;. The Python callback (line 4) prints the synchronized selection in the JupyterLab log console any time it changes.&lt;/p&gt;
&lt;p&gt;Finally, we can present this data more effectively using a second widget that displays our selection as &lt;code&gt;pd.DataFrame&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ipywidgets&lt;/span&gt;

&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipywidgets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clear_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;on_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;
    &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chart_widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;on_change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;selection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;ipywidgets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VBox&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;chart_widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/008-1_0vn1t-SlN8LvAKLrVAiHoA.mp4" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;on_change&lt;/code&gt; callback (line 6) is invoked whenever the &lt;code&gt;chart_widget.selection&lt;/code&gt; changes (line 13). It filters the original data based on the selection bounds and displays the given subset as a table within &lt;code&gt;output&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With just a few lines of code, we enhanced Altair with new functionality using &lt;strong&gt;anywidget&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ChartWidget&lt;/code&gt; can be shared in its current state. However, if we add more features, we could transition the JavaScript code from inline strings to separate files, gradually evolving the widget into a fully-fledged Python package. This incremental development is a feature of &lt;strong&gt;anywidget&lt;/strong&gt;, allowing prototypes to grow into robust tools over time.&lt;/p&gt;
&lt;h2 id="modern-web-development-meets-jupyter"&gt;Modern web development meets Jupyter&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;anywidget&lt;/strong&gt; further embraces modern JavaScript to reduce friction and make developing widgets more accessible and &lt;em&gt;fun.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Because &lt;strong&gt;anywidget&lt;/strong&gt; takes care of all the necessary plumbing, you no longer need to setup up a local Python package or manually install extensions to start building. Instead, you can prototype and share widget ideas directly from notebooks — just like regular Python scripts.&lt;/p&gt;
&lt;p&gt;Since v0.2, &lt;strong&gt;anywidget&lt;/strong&gt; allows you to use a file path to define your widget’s front-end code (i.e., the &lt;code&gt;_esm&lt;/code&gt; and &lt;code&gt;_css&lt;/code&gt; attributes). During development, &lt;strong&gt;anywidget&lt;/strong&gt; will monitor for changes and immediately refresh the UI without requiring a full page reload or resetting widget model state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anywidget&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;traitlets&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ExampleWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anywidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnyWidget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_esm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;index.js&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_css&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;styles.css&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;traitlets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This &lt;a href="https://anywidget.dev/blog/anywidget-02/#native-hot-module-replacement-hmr"&gt;feature&lt;/a&gt; has been popularized by modern web frameworks, but &lt;strong&gt;anywidget&lt;/strong&gt; introduces it for the first time to Jupyter. See this real-time development workflow it &lt;a href="https://www.youtube.com/watch?v=600PU6E4Srw&amp;amp;t=305s"&gt;in action&lt;/a&gt; or try it out yourself!&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Real-time widget development with anywidget entirely from within JupyterLab. Changes to widget front-end source code are displayed instantly in the UI as changes are made." src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/anywidget-jupyter-widgets-made-easy/images/001-1_VCG-57YX8IrfTyeby3Ybjg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Real-time widget development entirely from within JupyterLab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="try-it-out"&gt;Try it out!&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;anywidget&lt;/strong&gt; is available on &lt;a href="https://github.com/manzt/anywidget"&gt;GitHub&lt;/a&gt; and &lt;a href="https://pypi.org/project/anywidget/"&gt;PyPI&lt;/a&gt; and may be installed via &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anywidget[dev]&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I hope using &lt;strong&gt;anywidget&lt;/strong&gt; is simple and enjoyable. I have found it valuable in my work as a biomedical visualization researcher, and it’s been exciting to see the positive reception from the wider Jupyter community.&lt;/p&gt;
&lt;p&gt;Since its release a few months ago, &lt;strong&gt;anywidget&lt;/strong&gt; already been adopted by several notable projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/uwdata/mosaic"&gt;Mosaic&lt;/a&gt;: an extensible framework for linking interactive views to databases for scalable data processing.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/flekschas/jupyter-scatter"&gt;jupyter-scatter&lt;/a&gt;: interactive 2D scatter plots that scale to millions of points and support view linking.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/opengeos/mapwidget"&gt;Mapwidget&lt;/a&gt;: interactive 2D/3D maps using popular JavaScript libraries with bidirectional communication, such as Cesium, Mapbox, MapLibre, Leaflet, and OpenLayers&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/juba/pyobsplot"&gt;pyobsplot&lt;/a&gt;: a Python interface for &lt;a href="https://observablehq.com/plot/"&gt;Observable Plot&lt;/a&gt; that supports Pandas and Polars dataframes.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/widgetti/ipyreact"&gt;ipyreact&lt;/a&gt;: a Python library for authoring Jupyter Widgets with React components.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;… demonstrating its current viability as an alternative to traditional widget development. I am committed to maintaining the simplicity that defines &lt;strong&gt;anywidget&lt;/strong&gt; while exploring ideas to further modernize widgets like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A simple decorator-based API&lt;/li&gt;
&lt;li&gt;Using builtin dataclasses or libraries like &lt;a href="https://pydantic.dev/"&gt;Pydantic&lt;/a&gt; or &lt;a href="https://jcristharif.com/msgspec/"&gt;msgspec&lt;/a&gt; to define widget models and serialization logic&lt;/li&gt;
&lt;li&gt;Handling TypeScript and JSX for front-end code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re curious about custom widgets or facing difficulties in widget development, please give &lt;strong&gt;anywidget&lt;/strong&gt; a try and &lt;a href="https://github.com/manzt/anywidget/issues"&gt;share your experience&lt;/a&gt;. Happy coding!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/manzt/anywidget"&gt;GitHub - anywidget: custom jupyter widgets made easy&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’d like to extend my gratitude to the numerous contributors to our documentation and specifically &lt;a href="https://github.com/tlambert03"&gt;Talley Lambert&lt;/a&gt; for his work towards modernizing widgets from a Python perspective. He’s played a key role in developing our &lt;a href="https://anywidget.dev/en/experimental/"&gt;&lt;code&gt;experimental&lt;/code&gt;&lt;/a&gt; API, which allows more flexible communication between JavaScript and Python without requiring ipywidgets.&lt;/p&gt;
&lt;/blockquote&gt;
</content><category term="anywidget"/><category term="JavaScript"/><category term="widgets"/></entry></feed>