Mapping JupyterLab’s user interface to source code#

Last updated on: January 18, 2022

Introduction#

The purpose of this document is to help orient frontend developers to the JupyterLab codebase.

Required reading for this document is the sequence of pages in the JupyterLab docs beginning with General Codebase Orientation.

Source code versions#

This document contains a large number of links to specific lines of code. Because JupyterLab is constantly evolving and changing, it’s important to peg the links to specific code versions.

Scope#

JupyterLab loads many front-end components into its default user interface. This document does not attempt to exhaustively map all parts of the interface to the source code. Rather it focuses on a subset of the UI.

Approach#

This document is organized by actual DOM nodes in the browser that JupyterLab creates. Each section below covers a single node. In general, as you go from beginning to end, the nodes become more and more deeply nested.

Lumino and JupyterLab#

JupyterLab is built on top of Lumino. In other words, Lumino is a dependency of JupyterLab, but JupyterLab is not a dependency of Lumino. Lumino provides a set of classes for creating common UI elements, such as tab bars, menus, panels, and more. Lumino also provides an architecture for plugins.

Every UI element in JupyterLab ultimately inherits from the Lumino Widget class. A widget in Lumino is essentially what is sometimes called a frontend component in other libraries. It’s a class that wraps a DOM node, providing a piece of the UI.

Point of entry#

It’s hard to say where exactly the point of entry is for an application as big and complex as JupyterLab, but for our purposes, it’s new JupyterLab().start() in lines 177-191 of jupyterlab/staging/index.js.

The JupyterLab class inherits from JupyterFrontEnd class, which in turn inherits from Lumino Application.

The JupyterLab class constructor creates a LabShell instance if one is not passed in. LabShell is defined in packages/application/src/shell.ts, which is a key file to understanding how the JupyterLab UI is laid out. The base Lumino Application class points this.shell to the LabShell instance, so lab.shell is the root Lumino widget in the hierarchy of Lumino widgets. Within the JupyterLab source code, you will often see it referred to as app.shell.

The start method is defined in the Application class. For each plugin that has been registered, it calls activate on that plugin, passing in the plugin’s declared requirements to the activate function. In JupyterLab, plugins are defined in packages that have -extension at the end of the name. For example, mainmenu-extension defines a plugin that adds the main menu bar to the JupyterLab interface.

DOM Nodes#

The rest of the document maps DOM nodes in the JupyterLab UI to related points of interest in the source code. Each section represents a single DOM node.

Application shell#

The application shell is the entire user interface of the application. It includes the top, bottom, and sidebars, as well as the main content area (and other areas not covered by this document).

Screenshot of the JupyterLab UI in which the entire UI is outlined because theshell node spans the entire visible UI

Node-to-node CSS selector path for this node:

html > body > div#main

Table of properties:

Name

Value

HTML tag

div

HTML id

main

CSS class

jp-LabShell

Controlling class

LabShell extends Widget

Steps to mount:

  1. Create new app shell

  2. Start app

  3. Within the start method, invoke the attach shell method

  4. Attach shell to DOM. In a default JupyterLab instance, the shell node will be appended to the end of the document body.

Top container#

The top area includes the Jupyter logo and the main menu bar (File, Edit, View, etc).

Screenshot on which the top container of the JupyterLab UI isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-top-panel

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-top-panel

HTML role

banner

Controlling class

PanelHandler, a private class that wraps Lumino Panel

Shell area

top

Steps to mount:

  1. Create handler for top panel. When the handler is created, a new Lumino Panel is created.

  2. Create layout for app shell widget

  3. Add top panel to layout

  4. Assign layout to shell widget. This invokes the Lumino Widget layout setter, which in turn invokes the parent setter for BoxLayout which it inherits from Layout. This in turn calls the initialization method of the BoxLayout, which it inherits from PanelLayout. The init method attaches the widget.

  5. Assigning the layout attaches the top panel to the DOM

Middle container#

The middle node contains everything (nearly) between the top and the bottom areas: the left sidebar, the main document area, and the right sidebar. (Not covered in this document: it also contains the Log Console when it’s displayed.)

Screenshot on which the middle container of the JupyterLab UI isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-main-content-panel

Controlling class

Lumino BoxPanel

Steps to mount:

  1. Create new box panel

  2. Add middle box panel to shell layout

The rest of the steps for mounting the middle container are like the steps in mounting the top container.

Bottom container#

The bottom area contains the status bar.

Screenshot on which the bottom container of the JupyterLab UI isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-bottom-panel

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-bottom-panel

HTML role

none

Controlling class

Lumino BoxPanel

Shell area

bottom

Steps to mount:

  1. Create new box panel

  2. Add bottom panel to shell layout

The rest of the steps for mounting the bottom container are like the steps in mounting the top and middle containers.

Top menu bar container#

The menu area contains the main menu bar (File, Edit, View, etc).

Screenshot on which the top menu bar container isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-top-panel > div#jp-menu-panel

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-menu-panel

HTML role

navigation

aria-label

main

Controlling class

PanelHandler, a private class that wraps Lumino Panel

Shell area

menu

Steps to mount:

  1. Create new handler for menu area

  2. Add menu area handler to top handler. Note: this applies only in multiple document mode, which is the default; we do not cover single document mode in this doc.

The menu area handler is then mounted with the top handler (see steps for mounting top container, above).

Main vertical split panel#

This node allows the area inside the UI that is surrounded by top, bottom, and sidebars to be divided into two top and bottom parts. This appears to be primarily for providing a place to display the Log Console, which this document does not go into.

Screenshot on which the main vertical split panel isouttlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel > div#jp-main-vsplit-panel

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-main-vsplit-panel

Controlling class

RestorableSplitPanel extends Lumino SplitPanel

Steps to mount:

  1. Create vertically split panel.

  2. Add the v. split panel to the middle container

This node gets attached to the DOM with the middle container (see steps to mount the middle container, above).

Status bar#

Screenshot on which the status bar isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-bottom-panel > div#jp-main-statusbar

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-main-statusbar

Controlling class

StatusBar extends Lumino Widget

Steps to mount:

  1. Create new status bar

  2. Ask shell to add status bar to bottom area

  3. The shell passes the status bar to its bottom panel

This node then gets attached to the DOM with the bottom container (see steps to mount the bottom container, above).

Main horizontal split panel#

The horizontal split panel contains the left area panel, the main area panel, and the right area panel.

Screenshot on which the main split panel isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel > div#jp-main-split-panel

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-main-split-panel

Controlling class

RestorableSplitPanel extends Lumino SplitPanel

Steps to mount:

  1. Create split panel.

  2. Add the h. split panel to the v. split panel

This node gets attached to the DOM with the vertical split panel (see steps to mount the vertical split container, above).

Status bar left panel#

The status bar has three sections: left, middle, and right. Here we cover the left section.

Screenshot on which the status bar left panel isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-bottom-panel > div#jp-main-statusbar >
div:first-child

Table of properties:

Name

Value

HTML tag

div

HTML id

none

Controlling class

Lumino Panel

Steps to mount:

  1. Create the status bar left panel.

  2. Add the panel to the status bar’s layout

This node will then get attached to the DOM when the status bar gets attached.

A tab in the left sidebar: file browser#

These are clickable icons in the sidebar that open a panel in the left area when clicked. By default these include: file browser, running terminal and kernels, table of contents, and extension manager.

Screenshot on which the file browser tab in the left sidebar isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div[aria-label="main sidebar"] >
ul[aria-label="main sidebar"] >
li[role="tab"]

Table of properties:

Name

Value

HTML tag

li

HTML id

tab-key-0 (for example)

HTML title

File Browser (⇧ ⌘ F) (for example)

tabindex

none

HTML role

tab

aria-selected

true/false

Controlling class

VirtualElement

Steps to mount:

Note that the steps to mount a tab in left sidebar are similar to the steps to mount a menu item in the main menu bar. We will use the file browser as a concrete example.

  1. Create the file browser widget

  2. Attach the folder icon to the widget title

  3. Ask the shell to add the file browser widget to the left area. The shell passes the widget to its left handler. The left handler passes the widget’s title to its Lumino TabBar instance.

  4. Insert tab object into tab bar. This calls update(), which in turn triggers onUpdateRequest().

  5. Render tab element from tab object. This creates an li.

  6. Add the li to the tab bar ul

When the tab bar gets attached to the dom with shell’s left handler, this tab will also get mounted.

Left tab panel container#

This is the point of attachment for tab panels that are opened and closed by the left sidebar.

Screenshot on which the left tab panel container isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-left-stack

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-left-stack

Controlling class

Lumino StackedPanel

Shell area

left

Steps to mount:

  1. Create stacked panel.

  2. Add stacked panel to the main horizontally split panel

This node gets attached to the DOM with the main horizontally split panel (see steps to mount the horizontally split container, above).

Main document area#

The main document area is the area in which notebooks and other documents are shown. The main area can be split vertically and then again horizontally for a 4-by-4 layout. Inside each split (whether there are no splits or 2 or 3 or 4), each document has a tab that can be dragged from one split area into another.

Screenshot on which the main document area isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-main-dock-panel

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-main-dock-panel

HTML role

main

Controlling class

DockPanelSvg extends Lumino DockPanel

Shell area

main

Steps to mount:

  1. Create main dock panel.

  2. Add main dock panel to the main horizontally split panel

This node gets attached to the DOM with the main horizontally split panel (see steps to mount the horizontally split container, above).

Right tab panel container#

See left tab panel container. The information is essentially the same, but the HTML id is jp-right-stack.

Screenshot on which the right tab panel container isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-right-stack

A tab in the right sidebar#

See left sidebar tab. The idea is essentially the same.

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div[aria-label="alternate sidebar"] >
ul[aria-label="alternate sidebar"] >
li[role="tab"]

The document mode switch in the status bar left panel#

This is the switch labeled “Simple” in the lower left of the UI, in the status bar.

Screenshot on which the document mode switch in the status bar isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-bottom-panel > div#jp-main-statusbar >
div:first-child >
div#jp-single-document-mode

Table of properties:

Name

Value

HTML tag

div

HTML id

jp-single-document-mode

Controlling class

Switch extends Lumino Widget

Steps to mount:

  1. Create switch

  2. Ask status bar to add switch to its left panel. The status bar passes the switch to its left panel, which delegates to the Lumino Panel addWidget method.

The switch is now in the tree of the status bar node, so when the status bar node get attached to the DOM, so will this node.

The file browser panel (in the left panel area)#

The file browser allows the user to interact with folders and files on their file system.

Screenshot on which the file browser panel in the left panel area isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-left-stack
div#filebrowser

Table of properties:

Name

Value

HTML tag

div

HTML id

filebrowser

CSS class

jp-FileBrowser

HTML role

region

aria-label

File Browser Section

Controlling class

FileBrowser extends Lumino Widget

Steps to mount:

  1. Create file browser widget

  2. Ask shell to add widget to left area. The shell passes the file browser widget to its left handler. The left handler passes the file browser widget to its stacked panel. The stacked panel delegates to the Lumino Panel insertWidget method, which in turn delegates to PanelLayout.

The file browser panel is now in the tree of the left tab panel container, so it will be attached to the DOM when its parent is (see steps to mount the left tab panel container, above).

After the file browser widget is mounted, it gets shown and hidden via a Lumino Signal from the TabBar. When TabBar’s currentIndex gets changed, it calls this._currentChanged.emit, which triggers shell._leftHandler._onCurrentChanged.

A tab bar of open documents in the main dock area#

A tab bar in the main area allows you switch between open documents within a particular split of the main dock panel.

Screenshot on which open documents tab bar of the main dock area isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-main-dock-panel >
div.lm-DockPanel-tabBar

Table of properties:

Name

Value

HTML tag

div

HTML id

none

CSS class

lm-DockPanel-tabBar

Controlling class

Lumino TabBar

The tab bar is created by the main Lumino DockPanel instance via its private \_createTabBar method. The Lumino DockLayout handles mounting the tab bar to the DOM.

Like the shell’s left sidebar handler, it connects a currentChanged handler to the tab bar, so that when the tab changes, it can change the corresponding tab panel.

Notebook Panel#

A notebook panel is rendered into the main area. It contains a toolbar and the notebook body.

Screenshot on which the notebook panel in the main area isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-main-dock-panel >
div.jp-NotebookPanel

Table of properties:

Name

Value

HTML tag

div

HTML id

a uuid

CSS class

jp-NotebookPanel

HTML role

tabpanel

aria-labelledby

tab-key-0 (for example)

Controlling class

NotebookPanel extends DocumentWidget extends MainAreaWidget extends Lumino Widget

Steps to mount:

There are multiple code paths to mount a notebook into the DOM. For example, using the open command from the file menu versus double clicking on a notebook file in the file browser extension.

  1. Double click on notebook in file browser

  2. Use the document manager to open the file

  3. The document manager maps the file to a widget factory based on the file’s extension. Using the factory, it creates the widget (in this case, a NotebookPanel widget).

  4. The doc manager then asks the shell to add the widget to the main area. The shell passes the widget to its main area handler (a dock panel).

  5. The dock panel (through its DockLayout member), attaches the notebook node to the DOM, by appending it to the main area container node (#jp-main-dock-panel).

Notebook toolbar#

The notebook toolbar is a strip of buttons rendered above the notebook to perform actions on the notebook, such as copy, paste, save, insert cell.

Screenshot on which the notebook toolbar isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-main-dock-panel >
div.jp-NotebookPanel >
div.jp-NotebookPanel-toolbar

Table of properties:

Name

Value

HTML tag

div

HTML id

none

CSS class

jp-NotebookPanel-toolbar

HTML role

navigation

aria-label

notebook actions

Controlling class

ReactiveToolbar extends Toolbar extends Lumino Widget

Steps to mount:

  1. When the NotebookPanel widget is created (see above), it creates its own toolbar.

  2. It then adds the toolbar to its layout (BoxLayout), so that when it gets attached to the DOM, so will the toolbar.

Notebook content#

The notebook content is where the actual document is rendered for display and editing.

Screenshot on which the notebook content isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-main-dock-panel >
div.jp-NotebookPanel >
div.jp-NotebookPanel-notebook

Table of properties:

Name

Value

HTML tag

div

HTML id

a uuid if no id is provided

CSS class

jp-NotebookPanel-notebook

HTML role

region

tabindex

0

aria-label

notebook content

Controlling class

Notebook extends StaticNotebook extends Lumino Widget

Steps to mount:

  1. When the doc manager uses the notebook widget factory to create a new notebook panel, it passes in a notebook instance to the notebook panel constructor.

  2. The notebook panel constructor (through the MainAreaWidget parent class) adds the notebook widget to its layout, just like the notebook toolbar, above, so when the notebook panel gets attached to the DOM so does the notebook body.

Notebook toolbar item: save button#

Screenshot on which the notebook toolbar item save button isoutlined

Node-to-node CSS selector path for this node:

div#main > div#jp-main-content-panel >
div#jp-main-vsplit-panel >
div#jp-main-split-panel >
div#jp-main-dock-panel >
div.jp-NotebookPanel >
div.jp-NotebookPanel-toolbar >
div.jp-Toolbar-item

Table of properties:

Name

Value

HTML tag

div

HTML id

none

CSS class

jp-Toolbar-item

Controlling class

ReactiveToolbar extends Toolbar extends Lumino Widget

Steps to mount:

  1. The doc manager uses a factory to create a new notebook panel

  2. The factory, using a toolbar factory, creates the toolbar items. Note: the toolbar items are gathered from a json file.

  3. The toolbar factory passes each item to its own item factory. For example, the save button has its own factory.

  4. The save button factory creates the save button, which is a React button wrapped in a Lumino Widget.

  5. The notebook widget factory adds the save button to the toolbar. The toolbar passes the save button to its layout, which is a Lumino PanelLayout that handles putting this node into the toolbar node tree.

So when the notebook toolbar gets attached to the DOM, so does the toolbar item.