This document describes the patterns we are using to organize and write
CSS for JupyterLab. JupyterLab is developed using a set of npm packages
that are located in packages
. Each of these packages has its own style, but
depend on CSS variables dfined in a main theme package.
const
s, but we are moving away from that.src/style
subdirectory and
imported into the plugin's index.css
.theme-light-extension
and
theme-dark-extension
packages are used to style packages where ever possible.
Individual packages should not npm-depend on these packages though, to allow the
theme to be swapped out.We are using native CSS variables in JupyterLab. This is to enable dynamic theming of built-in and third party plugins. As of December 2017, CSS variables are supported in the latest stable versions of all popular browsers, except for IE. If a JupyterLab deployment needs to support these browsers, a server side CSS preprocessor such as Myth or cssnext may be used.
We use the following convention for naming CSS variables:
--jp-
.-
.--jp-notebook-cell-
.active
, not-active
or
focused
: --jp-notebook-cell-focused
.color
, font-size
or background
: --jp-notebook-cell-focused-background
.Some CSS variables in JupyterLab are considered part of our public API. Others are considered private and should not be used by third party plugins or themes. The difference between public and private variables is simple:
--jp-private-
private-
prefix are public.:root
pseudo-selector. This
ensures that public CSS variables can be inspected under the top-level
<html>
tag in the browser's dev tools.:root
.JupyterLab includes a default set of CSS variables in the file:
packages/theme-light-extension/style/variables.css
To ensure consistent design in JupyterLab, all built-in and third party
extensions should use these variables in their styles if at all possible.
Documentation about those variables can be found in the variables.css
file
itself.
Plugins are free to define additional public and private CSS variables in
their own index.css
file, but should do so sparingly.
Again, we consider the names of the public CSS variables in this package to be our public API for CSS.
We are organizing our CSS files in the following manner:
packages
directory should contain
any CSS files in a style
subdirectory that are needed to style itself.index.css
at the top-level of the plugin.We have a fairly formal method for naming our CSS classes.
First, CSS class names are associated with TypeScript classes that extend
phosphor.Widget
:
The .node
of each such widget should have a CSS class that matches
the name of the TypeScript class:
class MyWidget extends Widget {
constructor() {
super();
this.addClass('jp-MyWidget');
}
}
Second, subclasses should have a CSS class for both the parent and child:
class MyWidgetSubclass extends MyWidget {
constructor() {
super(); // Adds `jp-MyWidget`
this.addClass('jp-MyWidgetSubclass');
}
}
In both of these cases, CSS class names with caps-case are reserved for
situations where there is a named TypeScript Widget
subclass. These classes
are a way of a TypeScript class providing a public API for styling.
Third, children nodes of a Widget
should have a third segment in the CSS
class name that gives a semantic naming of the component, such as:
jp-MyWidget-toolbar
jp-MyWidget-button
jp-MyWidget-contentButton
In general, the parent MyWidget
should add these classes to the children. This
applies when the children are plain DOM nodes or Widget
instances/subclasses
themselves. Thus, the general naming of CSS classes is of the form
jp-WidgetName-semanticChild
. This allows the styling of these children in a
manner that is independent of the children implementation or CSS classes they
have themselves.
Fourth, some CSS classes are used to modify the state of a widget:
jp-mod-active
: applied to elements in the active statejp-mod-hover
: applied to elements in the hover statejp-mod-selected
: applied to elements while selectedFifth, some CSS classes are used to distinguish different types of a widget:
jp-type-separator
: applied to menu items that are separatorsjp-type-directory
: applied to elements in the file browser that are directoriesOver time, we have found that there are some edge cases that these rules don't fully address. Here, we try to clarify those edge cases.
When should a parent add a class to children?
Above, we state that a parent (MyWidget
), should add CSS classes to children
that indicate the semantic function of the child. Thus, the MyWidget
subclass
of Widget
should add jp-MyWidget
to itself and jp-MyWidget-toolbar
to a
toolbar child.
What if the child itself is a Widget
and already has a proper CSS class name itself,
such as jp-Toolbar
? Why not use selectors such as .jp-MyWidget .jp-Toolbar
or .jp-MyWidget > .jp-Toolbar
?
The reason is that these selectors are dependent on the implementation of the
toolbar having the jp-Toolbar
CSS class. When MyWidget
adds the
jp-MyWidget-toolbar
class, it can style the child independent of its
implementation. The other reason to add the jp-MyWidget-toolbar
class is if
the DOM stucture is highly recursive, the usual descendant selectors may
not be specific to target only the desired children.
When in doubt, there is little harm done in parents adding selectors to children.