nteract elements

AnyWidget View

Render anywidget ESM modules with the AFM interface

Counter Widget

Demonstrates state sync, custom messages, and the full AFM interface.

Confetti Widget

Demonstrates remote ESM imports from CDN (esm.sh/canvas-confetti).

Factory Pattern Widget

Demonstrates the factory pattern (like quak) where default export is a function returning { initialize, render }.

Kernel Log

Messages between widgets and the simulated kernel. The kernel auto-responds to "ping" with "pong".

No messages yet. Click "Send to Kernel" in a widget.

React component for rendering anywidget ESM modules. Implements the AFM (AnyWidget Frontend Module) interface for seamless widget integration.

Features

  • Dynamic ESM loading - Loads inline code or remote URLs
  • CSS injection - Injects widget styles with automatic cleanup
  • AFM interface - Full get, set, on, off, save_changes, send support
  • Two-way binding - State syncs between widget and kernel
  • Cleanup on unmount - Properly disposes widget and styles

Installation

npx shadcn@latest add @nteract/anywidget-view

New to @nteract? pnpm dlx shadcn@latest registry add @nteract

Copy the file to your project from the registry source.

Requires: Widget Store

Usage

Basic Usage

import { AnyWidgetView } from "@/components/widgets/anywidget-view"

function WidgetOutput({ modelId }: { modelId: string }) {
  return <AnyWidgetView modelId={modelId} />
}

With Widget Store

The component requires WidgetStoreProvider to be present in the component tree:

import { WidgetStoreProvider } from "@/components/widgets/widget-store-context"
import { AnyWidgetView } from "@/components/widgets/anywidget-view"

function App() {
  return (
    <WidgetStoreProvider sendMessage={(msg) => kernel.send(msg)}>
      <AnyWidgetView modelId="comm-id-123" />
    </WidgetStoreProvider>
  )
}

Props

PropTypeDefaultDescription
modelIdstringThe comm_id of the widget model
classNamestring""Additional CSS classes for the container

How anywidgets Work

anywidget sends widget code in the model state:

# Python (kernel side)
import anywidget
import traitlets

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

The frontend receives this as a comm_open message with _esm and optional _css in the state.

AFM Model Interface

Widget ESM code receives a model object with these methods:

interface AnyWidgetModel {
  // Get current state value
  get(key: string): unknown

  // Set state locally (buffered until save_changes)
  set(key: string, value: unknown): void

  // Send buffered changes to kernel
  save_changes(): void

  // Subscribe to state changes
  on(event: string, callback: Function): void
  off(event: string, callback?: Function): void

  // Send custom message to kernel
  send(content: object, callbacks?: object, buffers?: ArrayBuffer[]): void

  // Access other widget models
  widget_manager: {
    get_model(modelId: string): Promise<AnyWidgetModel>
  }
}

Event Types

EventDescription
change:keyFires when a specific key changes
changeFires on any state change

Example: Slider Widget

export default {
  render({ model, el }) {
    const slider = document.createElement("input");
    slider.type = "range";
    slider.min = model.get("min") ?? 0;
    slider.max = model.get("max") ?? 100;
    slider.value = model.get("value") ?? 50;

    const label = document.createElement("span");
    label.textContent = slider.value;

    slider.oninput = () => {
      label.textContent = slider.value;
      model.set("value", Number(slider.value));
      model.save_changes();
    };

    model.on("change:value", () => {
      slider.value = model.get("value");
      label.textContent = slider.value;
    });

    el.append(slider, label);
  }
}

Detecting anywidgets

Use isAnyWidget to check if a model is an anywidget:

import { isAnyWidget } from "@/components/widgets/anywidget-view"

function WidgetRouter({ model }) {
  if (isAnyWidget(model)) {
    return <AnyWidgetView modelId={model.id} />
  }
  // Handle other widget types...
}

ESM Loading

The component handles two ESM formats:

FormatExampleBehavior
Inline codeexport default { render() {} }Creates blob URL for import
Remote URLhttps://cdn.example.com/widget.jsDirect dynamic import

Remote ESM URLs must be CORS-enabled and serve valid ES modules.

CSS Injection

When _css is present in the model state, styles are injected into <head> with a data-widget-id attribute for debugging. Styles are automatically removed when the component unmounts.

<!-- Injected style element -->
<style data-widget-id="comm-id-123">
  .my-widget { color: blue; }
</style>

On this page