Skip to main content
This guide covers ipywidgets and anywidget support in nteract Desktop.

Quick Reference

Supported

CategoryExamplesStatus
ipywidgets coreIntSlider, Button, VBox, Dropdown✅ 49 widget types
anywidgetquak, drawdata, tqdm✅ Full AFM support
ipycanvasCanvas, MultiCanvas✅ Custom implementation (tested with 0.14.3)
Display outputsPlotly, Vega-Lite, HTML, images✅ Via display

Unsupported

WidgetWhyAlternative
JupyterLab extensionsnteract Desktop is not JupyterLab
jupyterlab-sidecarJupyterLab extensionUse notebook outputs
bqplotExtends IPython’s DOMWidgetPlotly, Altair, Vega-Lite

What Works

Built-in ipywidgets

All standard @jupyter-widgets/controls widgets are implemented:
  • IntSlider, FloatSlider, FloatLogSlider
  • IntRangeSlider, FloatRangeSlider
  • SelectionSlider, SelectionRangeSlider
  • IntProgress, FloatProgress
  • IntText, FloatText, BoundedIntText, BoundedFloatText
  • Text, Textarea, Password
  • Checkbox, ToggleButton
  • ColorPicker, DatePicker, TimePicker, Datetime
  • TagsInput, ColorsInput, IntsInput, FloatsInput
  • FileUpload
  • Dropdown, Select, SelectMultiple
  • RadioButtons, ToggleButtons, Combobox
  • VBox, HBox, Box, GridBox
  • Accordion, Tab, Stack
  • HTML, HTMLMath, Label
  • Image, Audio, Video
  • Button, Valid
  • Play (animation control)
  • Controller (Gamepad API)
  • Link, DirectionalLink (property sync)
  • Output (nested outputs)

ipycanvas

ipycanvas has a custom implementation (tested with v0.14.3). This is a from-scratch implementation of the canvas widget protocol, not using ipycanvas’s original frontend code.
This implementation may be brittle with future ipycanvas versions.

anywidget

nteract Desktop fully implements the AFM (AnyWidget Frontend Module) spec. Any widget following this spec will work. Tested widgets:
  • quak — DataFrame viewer (custom messages work)
  • drawdata — Drawing tool (dark mode works)
  • tqdm — Progress bars (leave=False cleanup works)
Supported features:
  • ESM loading (inline code and remote URLs)
  • CSS injection
  • Custom messages (model.send())
  • Binary buffers

Standard Outputs

Rich display outputs work via the display protocol (not as widgets):
  • Plotly, Vega-Lite, Vega
  • HTML, Markdown, LaTeX
  • Images (PNG, JPEG, SVG, GIF)
  • JSON, GeoJSON

What Doesn’t Work

JupyterLab Extensions

nteract Desktop is NOT JupyterLab. Anything requiring @jupyterlab/* APIs won’t work.
Examples of incompatible widgets:
  • jupyterlab-sidecar — Creates JupyterLab panels, requires @jupyterlab/application
  • Any widget that imports from @jupyterlab/services, @jupyterlab/apputils, etc.

IPython DOMWidget Extensions

Some widgets extend IPython’s DOMWidget class instead of the standard @jupyter-widgets/base. These use different internal APIs we don’t implement. Known incompatible:
  • bqplot — Uses IPython DOMWidget internals
  • ipyleaflet — Uses IPython DOMWidget internals

Why These Limitations?

nteract Desktop runs widgets in isolated iframes for security. The architecture is:
Parent Window (Tauri app)
├── WidgetStore (manages state)
├── CommBridgeManager (routes messages)
└── PostMessage ↔ Iframe

Isolated Iframe (blob: URL, sandboxed)
├── Widget rendering
└── No access to Tauri APIs
This means:
  1. We implement widget rendering from scratch, not via JupyterLab
  2. JupyterLab-specific APIs don’t exist
  3. Custom widget classes need explicit support

Recommendations

For Charts and Plots

Use display outputs instead of widget-based libraries:
import plotly.express as px

df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
fig.show()  # Works via display output
Alternatives to bqplot: Plotly, Altair, Vega-Lite, Matplotlib

For Custom Interactive Widgets

Use anywidget:
import anywidget
import traitlets

class Counter(anywidget.AnyWidget):
    _esm = """
    export default {
      render({ model, el }) {
        const btn = document.createElement("button");
        btn.innerHTML = `Count: ${model.get("count")}`;
        btn.onclick = () => model.set("count", model.get("count") + 1);
        model.on("change:count", () => {
          btn.innerHTML = `Count: ${model.get("count")}`;
        });
        el.appendChild(btn);
      }
    }
    """
    count = traitlets.Int(0).tag(sync=True)

counter = Counter()
counter
anywidget is the modern, portable approach that works across Jupyter environments.

For Side Panels

Instead of jupyterlab-sidecar, use regular notebook outputs. Output appears inline in cells.

Example: Using ipywidgets

Here’s a complete example using built-in ipywidgets:
import ipywidgets as widgets
from IPython.display import display

# Create widgets
slider = widgets.IntSlider(
    value=50,
    min=0,
    max=100,
    step=1,
    description='Value:'
)

output = widgets.Output()

def on_value_change(change):
    with output:
        output.clear_output()
        print(f"Slider value: {change['new']}")

slider.observe(on_value_change, names='value')

# Display widgets
display(widgets.VBox([slider, output]))

Example: Using anywidget

Create a simple interactive widget:
import anywidget
import traitlets

class ColorPicker(anywidget.AnyWidget):
    _esm = """
    export default {
      render({ model, el }) {
        const input = document.createElement("input");
        input.type = "color";
        input.value = model.get("color");
        input.oninput = (e) => model.set("color", e.target.value);
        
        const display = document.createElement("div");
        display.style.padding = "20px";
        display.style.backgroundColor = model.get("color");
        display.innerHTML = model.get("color");
        
        model.on("change:color", () => {
          display.style.backgroundColor = model.get("color");
          display.innerHTML = model.get("color");
        });
        
        el.appendChild(input);
        el.appendChild(display);
      }
    }
    """
    color = traitlets.Unicode("#3498db").tag(sync=True)

picker = ColorPicker()
picker

See Also

Build docs developers (and LLMs) love