🏠 back to Observable

Sticky sidebar in cell

I was trying to have a sticky sidebar inside a cell, that is a sidebar that sticks to the top of either the viewport or cell and stops at the bottom of the cell - like here, just for a cell. I tried all sort of CSS things (e.g. position sticky) and sticky sidebar npm and jquery modules, but failed. Here’s a small template. Any help appreciated.

Hi Kevin, I think this has been disabled for notebooks hosted on https://observablehq.com on purpose:

You might be able to pull off something that accomplishes this with the IntersectionObserver API (?), but as far as I know doing it with CSS won’t work unless you embed the notebook on another site.

Edit: I just found this neat demo of IntersectionObserver by @mike:

1 Like

I think my use case is a bit different: I do not need the sidebar to stick at the top for the whole notebook, but just relatively to one cell. More specifically:

  1. Cell is visible: stick to the top of the visible part of the cell
  2. Cell is disappearing from viewport: sticky sidebar will also disappear (since it can only move at most to the bottom of the cell).

I’ll check out the intersection observer API.

Ah, I see. Still, as far as I know, elements of cross-origin iframes do not have any other easy way to find out whether / how much they are in view.

I experimented with the IntersectionObserver, but sadly, if the cell is occupying the full screen, the callback function will not be called (since the intersection ratio will stay the same); hence, there is no way to update the position of the sticky element this way. It would be fixable if there is some way to listen to the main page’s scroll events, but that also does not seem possible since the scroll event is triggered outside the iframe of the notebook (if I am not mistaken?).

I’d really love to see this working - exporting my notebooks is not quite my favourite solution to this problem.

Is the sidebar supposed to be interactive?

Yes - it would contain a toggle button that changes the text content of the sidebar

Here’s an inelegant hack using a whole bunch of divs which I think achieves something like what you want:

Maybe one can use CSS transitions to make the movement smoother / use fewer divs?


Hah, that’s a clever workaround that I think I can make use of! I’ll post an example if it works :slight_smile:

Some observations from my own tests:

  • You can get the precise viewport height by attaching an element with 100vh height and then checking its intersectionRect.
  • Appending an element to the DOM will almost immediately trigger an IntersectionObserverEntry, even before the next requestAnimationFrame.

Here’s a generic implementation:


Here’s a variation on makeSticky which only attaches a probe when the parent is visible:

I keep thinking there might be a way to do this without requiring a “blinking” probe at all (maybe by placing probes just outside the viewport?) but I haven’t managed to get anything to work yet.

There might be a bug in your implementation, because the element does not stick until one has scrolled past the entire parent and scrolled up again.

I don’t see an alternative to the “blinking”, because:

  • To obtain an accurate viewport height the element must intersect the entire viewport,
  • and to spawn new intersection events the ratio needs to change.

However, the probe interval could perhaps be reduced, e.g. by using setTimeout to restore the initial display instead of relying on the next intersection event.

I would very much like to see a different solution, too, because an element that spans the entire notebook height is a performance hog, especially if it needs to be redrawn constantly. But the problem with any element of lesser height is that a user can jump to any offset of the page, skipping any narrower probes that might have been placed above or below.

Edit: Another problem with dynamically placed elements is that output cell positions have a dynamic bottom margin that changes based on editor sizes.

1 Like

Indeed, I should have tested this in Chrome / Safari (it seems to work fine in Firefox).

Thank you so much @mootari and @bgchen ! I am using mootari’s sticky positioning notebook and it seems to work great :slight_smile:


Nice work, @mootari and @bgchen - I wasn’t even sure offhand if such a thing was possible :slight_smile:

1 Like

One thing I realised is that if you edit a cell in the notebook, the observable header at the top of the page will become sticky too and hide some part of the sidebar. Are you aware of some way to fix this?

No, I don’t think there is one. You’d have to do something very hacky like intercepting worker messages and guessing when the header might appear (as well as its height).

Fortunately, the sticky header is only visible to you. I’ve often been annoyed by it as well (for instance, it gets in the way when clicking on anchor links), so I’ve turned it off locally with some custom CSS in Stylus:

.sticky {
    position: relative;

I’ve added an option to set a custom offset, but I’m fighting with some weird performance decrease in Chrome whenever the checkbox is toggled. Here’s the fork, if someone wants to chime in:

Edit: Wow, the overall performance in Firefox is exceptionally bad.