Accessibility: A JupyterLab Developer’s Guide#
If you’re making changes to the JupyterLab source code and you’re concerned about accessibility, this page is for you.
Looking for other ways to contribute to accessibility on Jupyter projects?
Where to start#
Thank you for being interested in improving JupyterLab’s accessibility. Whether making accessibility-specific fixes or considering the accessibility impacts of another contribution, your work betters JupyterLab for everyone who uses it.
A common question when accessibility-minded developers come to JupyterLab is: where do I get started?
If you don’t have a lot of time to immerse yourself in the big picture of JupyterLab accessibility work, then the GitHub issues labeled good first issue and accessibility are a good place to start.
If you want to more fully immerse yourself in the work of making JupyterLab (or other Jupyter projects) more accessible, then a good place to start would be to join the Jupyter accessibility meeting that happens every other week. If you cannot attend the call, you can peruse the meeting notes and find links to other resources on the Jupyter Accessibility site.
Looking for a frontend developer’s orientation to the JupyterLab codebase?
Best practices while developing#
JupyterLab is a web application and authoring tool. Therefore the following standards apply:
These are good places to familiarize yourself with accessibility best practices for developing websites (WCAG) and web applications (ARIA). Note that although WCAG was created primarily for static websites, the guidelines are nonetheless applicable to web apps like JupyterLab.
One resource that is often particularly helpful for developers looking for examples and best practices is ARIA Patterns. This web resource contains examples of how to implement UI elements—such as menus, dialogs, breadcrumbs, and more—in a more accessible way. However, be careful! Just because you can implement a button using divs and aria attributes does not mean that you should! (Most likely you should just use the button tag.) As a best practice, you should only use ARIA when you cannot use existing HTML elements (button, input, nav, aside, etc.) to achieve the UX that you desire.
Finally, there is much more accessibility knowledge on the Internet than there is in JupyterLab or Project Jupyter alone. Whatever you decide to work on, consider exploring accessibility resources in other spaces for similar or equivalent efforts. Accessibility communities tend to be generous with the resources they provide to improve web accessibility. Many times, searching for the name of the task or issue appended with accessibility in a search engine will give you several results and a chance to learn from the broader community right away.
The rest of this section contains best practices specific to JupyterLab and its development.
Use color variables from the CSS#
When fixing contrast or other visual accessibility issues in JupyterLab, it can be tempting to pick a color and apply it to the part of the UI that you are working on. However, it quickly becomes unmanageable to have color values spread throughout the app across different CSS files. Therefore, the JupyterLab codebase defines a set of color variables that can be used for borders, icons, and such. If you’re adding any CSS that needs a color value, please use one of the variables defined.
Upstream fixes in Lumino#
JupyterLab uses a front-end framework that was built specifically for it called Lumino. Lumino is similar in some ways to React, Vue, and Angular, but it also provides a number of UI widgets like menu bars, tab bars, and dock panels. As a result, some of the accessibility issues reported in the JupyterLab GitHub repo need to be fixed in the Lumino repo. A good resource for learning Lumino: PhosphorJS (now Lumino) Mentor Sessions. PhosphorJS was Lumino’s previous name. There is a page with notes from the PhosphorJS sessions that also has a link to some additional videos that were not uploaded to YouTube.
It’s not always obvious when an accessibility issue should be fixed in JupyterLab or Lumino. Some guidance to help you identify where your change should be made:
Generally speaking, if you can fix the issue in Lumino, it’s better to fix it in Lumino because then the fix will be absorbed in more places.
However, for that same reason, because Lumino is used by more codebases than just JupyterLab—specifically, by JupyterLab extensions—one should be careful making changes to Lumino that might break downstream consumers/extensions.
So an additional rule of thumb is: if you can’t make the fix in Lumino without breaking dependants, then it might be better to make the fix in JupyterLab. In this case, you might take a two-track approach, where you fix the accessibility issue in JupyterLab and also submit a breaking fix in Lumino that targets a future, major, API-breaking release/version of Lumino.
Automated Regression Testing#
If you fix an accessibility issue in the source code but you don’t add a test with your fix, then there’s a strong chance that your fix will be undone accidentally by some future changes to the codebase.
Sometimes it’s straightforward to unit-test an accessibility fix, such as when enabling keyboard shortcuts on a toolbar button. But often it’s difficult to unit-test accessibility fixes.
Therefore there is an effort underway to use Playwright to write user-level accessibility tests to JupyterLab. To illustrate how to use it within your development process, let’s walk through an example.
This example will involve three separate GitHub repos:
This is a real world example, taken from actual past work.
Let’s say you do an accessibility audit of the start page of the JupyterLab UI and find a tab trap in the top menu bar, meaning the user can press the tab key to get into the menu bar but cannot easily get past it using only the keyboard.
You dig in further and discover that the tab trap bug is in the
jupyterlab/lumino repo, so
you fork the jupyterlab/lumino repo, create a new branch called
fix-tab-trap
, and open a pull request.
You decide that you want to write a test. This is one of those cases where writing a unit test would be a straightforward task. However, a unit test would only check the top menu bar, so it would not prevent a reappearance of the issue that you decided you want to fix once and for all, namely: you don’t want any tab traps anywhere on the JupyterLab start page.
So you decide that you want to add a regression test to the
Quansight-Labs/jupyter-a11y-testing repo.
This test checks that there are no tab traps on the JupyterLab start page by
using Playwright to open JupyterLab and press the tab key repeatedly. So as with
the Lumino repo before, you fork the Quansight-Labs/jupyter-a11y-testing repo,
create a branch called test-tab-trap
, and open a pull request. The important
thing in this step is that you save your test file with a .test.ts
extension
next to the other regression test files.
Now you want to run your test. Specifically, you want to run the test against a build of JupyterLab that incorporates your Lumino fix. Here’s how you would do that.
Let’s pretend that your GitHub username is a11ydev and you’ve forked the Lumino and testing repos and created the following branches on those forks, one with your bug fix and the other with your test:
a11ydev/lumino:fix-tab-trap
a11ydev/jupyter-a11y-testing:test-tab-trap
On GitHub, go to your fork of the testing repo, a11ydev/jupyter-a11y-testing.
Make sure that you are on your test-tab-trap branch, which contains the
.test.ts
file that you added. Then go to Actions and click on the workflow
titled “Run accessibility tests on JupyterLab.” Click “Run workflow.” This will
open a form to configure the workflow.
Here’s how you should fill out the form:
Use workflow from:
test-tab-trap
JupyterLab repo:
jupyterlab/jupyterlab
Branch/tag/SHA:
main
Test suite: leave blank
External package repo:
a11ydev/lumino
External package ref:
fix-tab-trap
Then press the “Run workflow” button. A GitHub action should then build JupyterLab from source, linking your Lumino fork and branch, then run the test suite, including your test, and then finally show the test results, hopefully with your test passing.
Note that in this example you did not fork the jupyterlab/jupyterlab repo or change the branch name to something other than “main” in the workflow config form. This is because you did not need to modify the JupyterLab codebase to fix this issue. But if you were working on an issue that required you to modify the JupyterLab codebase, you would do the same thing that you did earlier with Lumino: fork the repo, create a branch with your fix, and then enter your fork and branch in the workflow config form before running the workflow. That should cause it to build a version of JupyterLab based on your changes and then run the test suite against it. The workflow is flexible enough to allow you to test against changes in JupyterLab or Lumino or both at the same time if needed.
Note
There are more detailed instructions for how to use the GitHub workflow in the testing repo.
PR Review and Manual Testing#
When reviewing code, documentation, or other contributions, you can use manual testing to help prevent accessibility bugs. Typically you try and complete a task related to your fix or contribution using an accessibility accommodation or setting. Common options include:
Using a screen reader.
Zooming the page up to 400% via your browser.
Unplugging or not using your mouse. Navigate only with the keyboard.
Emulating vision deficiencies (Chrome, Edge, and Firefox all provide built-in tools to do this.)
While testing, take note of what happens and compare it to what you can do to complete the task without your chosen accessibility accommodation. If there is anything you cannot complete, then you have a blocking accessibility issue. Even though your use of assistive tech or an accessibility accommodation will likely differ from someone who uses them regularly, knowing the results is helpful to tell if JupyterLab is behaving as you expect.
GitPod#
If you have a GitPod account and you have submitted
a PR to JupyterLab, you can manually test it by copying the GitHub URL to your
PR and concatenating it to gitpod.io/#
, like so:
https://gitpod.io/#https://github.com/jupyterlab/jupyterlab/pull/your-pr-number
GitPod will build JupyterLab from source with your PR applied and set up a tunnel so that you can load the UI in your browser at localhost:8888.
Useful tools for development#
Here is a list of some apps that developers have found useful while doing accessibility work in JupyterLab:
Chrome Dev Tools for discovering and fixing low contrast text and for viewing the accessibility tree
Axe DevTools, extension for Chrome Dev Tools
Color Contrast Analyzer, desktop app for Windows and Mac
Polypane, desktop browser with some dev tools built in (note it’s not free but it does have a free trial)
Axe Accessibility Linter, extension for VS Code
GitPod: See the GitPod section under the Testing section above.
And of course, screen readers such as JAWS, NVDA, and VoiceOver.