Widgets Styling Examples - is this conventional or a hack?

Please consider this notebook found on github:

There are four styling examples. The first three work. For the first three I would like to know if this is ‘legal’ widget code. I.e. is this likely to break in future versions, or is this the way it is supposed to be done?

In the first example, a class is created inside style tags, and then is used within a single string constant that is given to a single ipywidgets.HTML widget. If anything is going to work, it seems this would. And it does.

In the second example there are two ipywidgets.HTML widgets. Both are children of the same Box. In the first one the class is defined. In the second one it is used. This works. Hence it appears that a style definition that occurs within style tags is available to all children of a Box. … but wait there is more.

In the third example, a new Box is created that does not contain any class definitions. Instead it has a single HTML widget child, and that child successfully makes use of the prior two class definitions. So apparently what is in the style tags applies to all the HTML widgets … and there is more yet.

Within the style tags there is styling for h1 tags. The markdown in the jupyter notebook itself is changed by that. Hence, the stuff in style tags affects the whole notebook, no matter where those widgets are anchored as children.

This is actually nice. A programmer can even just include an single HTML block with all the style and class definitions at the top for the notebook, including the jupyter notebook markdown cells, just like a person might do with the header of an html page.

Putting style at the top of the notebook. Can define classes to use etc. … this example will make h1 purple, even in the markdown cells:

import ipywidgets as W
display( W.HTML( '''
<style>
   h1 {
   color: purple;
   }
</style>
'''))

But is this going to still work in future versions? Is this the way it is supposed to work?

As for the 4th example, the Box has three HTML children. The first opens a div. The second has the contents for the div. The third then closes the div. This does not work. So apparently each HTML widget is contained in a div of its own. We are not just laying down HTML as we run across it. But that means our style tags are within a div. HTML will still consider those to be global to the page, which is why this is working. I guess.

Oh gee, I guess script tags will also work.

Yep, Notebook Classic, JupyterLab, nbconvert HTML, and nbviewer put user-generated content right into the same DOM as the UI, and it’s possible to modify/ruin the outer application to your heart’s content. You can even tease out the slight differences between being hosted on nbviewer, etc. to do really wacky things. Some other Jupyter frontends do not allow this, and every cell’s output is in an isolated iframe.

These approaches are not scalable, for sure, as there’s almost no way to ensure they don’t conflict with each other. In general, published libraries should not rely on these hacks, or if they must, they should do their utmost to ensure that their styles don’t leak out: a common approach might be to make an enforceably-unique CSS prefix (e.g. the comm_id of a widget), and always ensure it gets shown in the same output as the thing in question.

In JupyterLab, there are some changes coming down the pipe for (initially opt-in) “virtual” rendering of Very Large Notebooks… I don’t know if CSS outputs, etc. will get a pass, or if that just won’t be compatible.

I guess, as a rule of thumb in UI/accessibility desigh: don’t rely entirely on style (e.g. red is bad, green is good). But for your notebook telling your story to your audience… do what makes you happy, and tells your message today.

1 Like
  1. styling HTML in the Jupyter markdown cells

For this application that is not very interesting. I wish that the css sheet for the frontend was published somewhere, but that has no bearing on the immediate problem.

  1. styling for Box in a custom widget

This is the current problem. It will also need some script to make it work. There appear to be two ways.

One way is to make style specific to the Box parent. Then the object ID can be a suffix to class names. It isn’t very often that style is object specific. It is more common that it is specific to a python class defining a widget group.

The other way is that there is display(ipywidget.HTML(" …")) at the top of the document. The module name will be prepended to css class names. Then identical class with object id numbers is not reproduced in every object.

The Layout object approach is constrained to specific styles. It looks like quite a programming chore for the Layout class implementer to make that fully functional. It is also nice to be able to use the existing style paradigm directly by putting style in the HTML widgets.

  1. actually, script is more important than style …

I have been assuming that if the style tags can be made to work, then so can script tags. As I described before, I have an accordion with data that is filled out asynchronously, and it is a shell game for the user to know if something is in each of the accordion options. I will add a marker on the banner as to whether something would be seen when the option is expanded out. I am reading about the interactive architecture now, but as a hack it looks like one can append and HTML child with script calls to add the needed info.

You can use class-based inheritance, if you like, to create CustomStyleBox, e.g.

  • CustomStyleBox
    • children[] =
      • HTML(f'<style>.custom-{self.id}</style>)
      • Box()
        • children

As for your accordion: using script tags to hack widget children almost certainly will break, and I can’t really recommend that. To get a feel for what is actually changing, have a good long look at what’s happening over the WebSocket (or use jupyter-kernel-spy). There has to be something in there you can use.

1 Like