How to create a custom ipywidget in python and not in javascipt?

Hi, I’m trying to create an image carousel.

import ipywidgets as widgets
from ipywidgets import register

@register
class ImageCarousel(widgets.DOMWidget):
    def __init__(self, images, image_width=500):
        self._images = images
        self._index = 0
        self._btn_left = widgets.Button(
            icon="caret-left",
            layout=widgets.Layout(
                width='30px',
                height='100%',
            )
        )
        self._btn_right = widgets.Button(
            icon="caret-right",
            layout=widgets.Layout(
                width='30px',
                height='100%',
            )
        )
        self._btn_left.on_click(self._on_click)
        self._btn_right.on_click(self._on_click)
        self._image = widgets.Image(
            width=image_width,
        )
        self.panel = widgets.HBox(
            children=[self._btn_left, self._image, self._btn_right], 
            layout=widgets.Layout(
                height='100%',
            )
        )
        self._change_image(0)
    
    def _on_click(self, button):
        if button == self._btn_left:
            self._change_image(-1)
        elif button == self._btn_right:
            self._change_image(1)
        
    def _change_image(self, increment):
        if not self._images:
            self._btn_left.disabled = True
            self._btn_right.disabled = True
            return
        n = len(self._images)
        self._index = (self._index + increment) % n
        self._btn_left.disabled = self._index <= 0
        self._btn_right.disabled = self._index >= n-1
        self._image.set_value_from_file(images[self._index])
        
images = [
    './images/max-heap-1.webp',
    './images/max-heap-2.webp',
    './images/min-heap-1.webp',
    './images/min-heap-2.webp',
]

ImageCarousel(images).panel

It works fine, but I’m unable to display it directly. I need to display the panel member.
Is there a way to make ImageCarousel directly “displayable”?

One could perhaps try subclassing widgets.HBox (or VBox). The widgets that are on its children traitlet will be displayed, but otherwise doesn’t have any style opinons. In this case, it might be a layout like:

HBox([
  Button(),
  HBox([
     # ... the images
  ]),
  Button(),
])

The more things that are declared as instance-level traits, the more interactive and validated things can be. For example, the images could be a traitlets.Tuple, which could in turn get its list from another widget.

Hi @SergeWeinstock!
I recently built a library called pywidget that aims to create widgets entirely in Python, without having to write JS. Install with pip install pywidget.

It builds on top of anywidget, which handles ESM delivery, widget lifecycle, the model API, and cross-platform compatibility. pywidget adds a JS bridge that loads Pyodide (CPython compiled to WebAssembly) in the browser and delegates rendering to Python code you write as regular methods on your class.

I reimplemented your image carousel as a pywidget example: Open on notebook.link Open in Colab

  • The widget is directly displayable — no need to access .panel
  • Button clicks don’t round-trip to the kernel — event handlers run locally in the browser via Pyodide, so interaction feels instant
  • State syncs bidirectionally — setting carousel.index = 2 from another cell updates the view

:warning: Note: pywidget is under active development and the API may change.

3 Likes