Jupyter Widgets
Build notebook UIs with fully customizable widget components
Why nteract-elements widgets?
Traditional Jupyter widget frontends (@jupyter-widgets/html-manager) are designed for JupyterLab. They use Backbone.js for state management, have JupyterLab-specific styling, and are difficult to customize for standalone applications.
nteract-elements takes a different approach:
| Traditional | nteract-elements |
|---|---|
| Backbone.js models | Pure React with useSyncExternalStore |
| JupyterLab styling | shadcn/ui primitives (fully customizable) |
| Monolithic manager | Modular: pick only what you need |
| Extension-focused | App-focused |
The result: widgets that look like your app, not JupyterLab.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Your React App │
├─────────────────────────────────────────────────────────────┤
│ WidgetStoreProvider │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Widget Store │ │
│ │ • Manages all widget model state │ │
│ │ • Handles comm_open / comm_msg / comm_close │ │
│ │ • Fine-grained subscriptions per model/key │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │WidgetView│ │WidgetView│ │WidgetView│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────▼─────┐ ┌─────▼────┐ ┌─────▼─────┐ │
│ │ IntSlider│ │AnyWidget │ │ VBox │ │
│ │(built-in)│ │ (ESM) │ │(container)│ │
│ └──────────┘ └──────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼ sendMessage()
┌──────────────┐
│ Jupyter Kernel│
└──────────────┘Components
Widget Store
The foundation. A pure React store that manages widget model state with:
- Concurrent-safe state via
useSyncExternalStore - Fine-grained subscriptions (subscribe to a single key, not the whole model)
- IPY_MODEL_ reference resolution
Widget View
The router. Given a modelId, it renders the right component for each widget:
| Prop | Type | Description |
|---|---|---|
modelId | string | The comm_id of the widget model |
className | string | Additional CSS classes for the container |
Model closed? (e.g. tqdm with leave=False)
├── Yes → Render nothing
└── No → Model exists?
├── No → Show loading state
└── Yes → Has _esm field?
├── Yes → Render with AnyWidgetView
└── No → Has registered component?
├── Yes → Render built-in component
└── No → Show unsupported widget fallbackAnyWidget View
ESM widget loader implementing the AFM interface:
- Dynamic ESM loading (inline code or remote URLs)
- CSS injection with automatic cleanup
- Full two-way binding support
Built-in Controls
24 shadcn-backed widget components for standard ipywidgets:
- Sliders: IntSlider, FloatSlider, IntRangeSlider, FloatRangeSlider
- Progress: IntProgress, FloatProgress
- Text: Text, Textarea, HTML
- Selection: Dropdown, RadioButtons, ToggleButtons, SelectMultiple
- Boolean: Checkbox, ToggleButton
- Other: Button, ColorPicker
- Containers: VBox, HBox, Box, GridBox, Accordion, Tab
Quick Start
# Install everything
npx shadcn@latest add @nteract/widget-view
npx shadcn@latest add @nteract/widget-controlsNew to @nteract? pnpm dlx shadcn@latest registry add @nteract
import { WidgetStoreProvider, useWidgetStoreRequired } from "@/components/widgets/widget-store-context"
import { WidgetView } from "@/components/widgets/widget-view"
import "@/components/widgets/controls" // Register built-in widgets
function NotebookApp({ kernel }) {
return (
<WidgetStoreProvider sendMessage={(msg) => kernel.send(msg)}>
<KernelBridge kernel={kernel} />
{/* Render widgets by their comm_id */}
<WidgetView modelId="widget-123" />
</WidgetStoreProvider>
)
}
// Must be inside WidgetStoreProvider to access the store
function KernelBridge({ kernel }) {
const { handleMessage } = useWidgetStoreRequired()
useEffect(() => {
kernel.onMessage(handleMessage)
}, [kernel, handleMessage])
return null
}For a complete integration example, see the runtimed sidecar which uses nteract-elements for full Jupyter widget support.
Customization
Every built-in widget uses shadcn/ui primitives. Customize them by:
- Theming - Modify CSS variables in your
tailwind.config.js - Override components - Register your own component for any model name
- Fork and modify - Copy a widget file and adjust as needed
import { registerWidget } from "@/components/widgets/widget-registry"
// Replace the default IntSlider with your custom version
registerWidget("IntSliderModel", MyCustomSlider)