Accessibility¶
Notepadia is a Notepad++ work-alike, and Notepad++ is a keyboard-first editor.
This page records what the app does today for people who drive it with a
keyboard or a screen reader, what it does not do yet, and how to report a
problem. It is written from measurements of the running app, not from the
source: every claim below is checked by e2e/a11y.cjs or
theme-contrast.test.cjs, so if the app regresses, this page goes with it.
Keyboard¶
The workbench is reachable with Tab alone, in this order:
| Stop | How it behaves |
|---|---|
| Menu bar | One stop for the whole bar, as in Notepad++. Left and Right move between File, Edit, … and Enter or Down opens the menu. Both the open keys are verified in e2e/a11y.cjs. |
| Toolbar | One stop, with Left and Right moving between buttons. A button that cannot act right now keeps aria-disabled and stays in the ring, so focus never skips past it. |
| Document tabs | One stop for the open documents, with Left and Right moving between them. The current tab is the only stop in the ring; the others are reached with the arrow keys. |
| Editor | The text itself. Tab inserts a tab, exactly as Notepad++ does. |
| Status bar | One stop carrying the line, column, selection, length, encoding and line-ending information. |
Escape, then Tab, leaves the editor¶
Monaco binds Tab to insert a tab, so by itself Tab never leaves the editor.
Press Escape and then Tab to move to the next stop instead, and
Escape then Shift+Tab to move back. This is the same escape hatch
VS Code uses, and it is implemented in
notepadia-accessibility-contribution.ts. Escape on its own keeps all of its
usual editor meanings, so the sequence costs nothing.
Every stop in the ring has a non-empty accessible name, checked through
Chromium's own accessibility tree with page.accessibility.snapshot().
Screen readers¶
Verified automatically against the accessibility tree:
- The editor is exposed as
role="code"with a named text area (aria-label="Editor content"), so a screen reader announces the document rather than an anonymous edit box. - Menu bar items, toolbar buttons, document tabs and the status bar all carry accessible names.
- Menus, tab bars and the status bar report their roles correctly.
- Every interactive control in the Search Results window, Incremental Search,
Function List, the Run dialog, the Column Editor, the Summary dialog, the
Windows dialog and the customized Date & Time box has a non-empty accessible
name, read through
page.accessibility.snapshot(), the way the ring stops are. A control that gains a name-less label again fails CI.
Not yet verified with a real screen reader — pending. A tree read is not a screen reader: it cannot tell you whether an announcement is usable, only that a name exists. docs/screen-reader-test-plan.md is the repeatable script for the run that can — Run A with Orca on Linux against the browser build, Run B with NVDA on Windows against the packaged build. Both runs are deferred to a dedicated multi-system pass and are not yet recorded; until one is completed and its version table filled in, treat screen reader support as "structurally correct, unconfirmed in practice", and do not read the automated pass as a substitute.
Known reader limitations¶
Orca 48.0 on Linux cannot enter the menu bar: it announces the top-level names as
the bar is crossed, but Space, Enter and Down do not open the menu, so the items
inside are unreachable with Orca alone. The keyboard path itself is correct —
e2e/a11y.cjs verifies that Down and Enter each open a focused top-level menu —
and NVDA reads the same menus on the packaged Windows build, so this is recorded
as an Orca limitation rather than an app defect. It is tracked in
the test plan and re-measured on each Orca run.
The expanded automated pass has already paid for itself: it found and fixed four
defects a tree read can see but that no one had looked for. The Column Editor's
field labels were not associated with their controls, so focusing one announced
an unnamed edit; each label now carries for. The customized Date & Time box's
format label had the same gap. The Windows dialog marked its visual column
header role="row" with no grid around it, which axe reports as critical
aria-required-children and aria-required-parent; the header is now a
presentational legend and each row announces its own Name, Path and Type. The
Incremental Search bar's labels used the hint colour and failed the contrast
check; they now use the primary foreground. Each fix is guarded in
e2e/a11y.cjs.
Monaco's own accessibility help, Alt+F1, is not available. Theia 1.75 does not register the command, so pressing it does nothing. This is an upstream gap, not a Notepadia choice, and the e2e suite asserts the current behaviour so the gap stays visible instead of quietly being assumed to work.
Automated checks¶
e2e/a11y.cjs runs in CI and fails the build on:
- any serious or critical axe-core violation in the default layout, with a menu open, with the Find dialog open, with each of the four panels (Document Map, Document List, Character Panel, Clipboard History) open, and on each of the surfaces added since: the Search Results window, Incremental Search, the Function List, and the Run, Column Editor, Summary, Windows and customized Date & Time dialogs, each scoped so a finding names the surface it is in;
- a control inside any of those surfaces that has no accessible name;
- a stop in the tab ring that has no accessible name;
- the editor becoming a keyboard trap;
- Tab failing to indent in the editor, which is the behaviour Escape+Tab must not have broken.
Moderate and minor axe findings are not gated. One is left in place
deliberately: region reports the Monaco text area and the current document
tab as content outside a landmark. Both are inside the editor's own container,
and the only way to satisfy the rule would be to mark Monaco's internals with
landmarks, which would fight the widget on its next update.
Contrast and colour¶
theme-contrast.test.cjs checks every shipped theme against WCAG 2.1 for the
pairs the app actually relies on: editor text, active and inactive line
numbers, the status bar, the menu bar, tabs, menus, buttons, inputs, the focus
outline, and the shown whitespace and arrows. Body text asks for AA (4.5:1) and
meaningful graphics for non-text contrast (3:1).
Where Notepad++'s colours were changed¶
The Notepad++ Classic palette fails WCAG AA in four places. Each was adjusted to the nearest passing colour rather than dropped:
| Colour | Notepad++ | Notepadia | Ratio |
|---|---|---|---|
| Inactive line numbers | #808080 (3.95:1) |
#767676 |
4.54:1 |
| Shown whitespace, light theme | #A0A0A0 (2.62:1) |
#8A8A8A |
3.45:1 |
| Shown whitespace, dark theme | #5A5A5A (2.42:1) |
#6A6A6A |
3.08:1 |
| Button background | #0078D7 (4.50:1, just under) |
#0072C6 |
4.97:1 |
The disabled toolbar icons are dimmed with opacity: 0.5 rather than a dimmer
0.4. An icon is the button's only content, which makes it meaningful
non-text graphics under WCAG 1.4.11, and at 0.4 it lands at 2.80:1 in Classic
and 2.66:1 in Classic Dark. The dimmed state is still unmistakably dimmer than
an enabled button.
The Find dialog's field labels and fieldset legends used
--theia-descriptionForeground, which is a hint colour and lands under 4.5:1.
They now use the normal foreground, because a form label is primary text. The
Incremental Search bar's label, option labels and result count had the same
problem and were changed the same way.
High contrast¶
Notepadadia High Contrast ships alongside the Classic and Classic Dark palettes and is listed in Settings ▸ Preferences ▸ Color Theme. It is a true high-contrast theme: pure black editor and panel backgrounds, white body text, and yellow, cyan and bright green reserved for the things that must stand out. Editor text reaches AAA (7:1 or better) against the background.
Known upstream problems worked around in Notepadia¶
Three defects are in the shell rather than in Notepadia code. They are repaired
at runtime by notepadia-accessibility-contribution.ts, which writes
attributes only and can be deleted when the shell stops producing these
shapes:
- Tab titles. Lumino renders tab titles as
<li>elements inside a<ul>that Theia marksrole="tablist", but only the current title getsrole="tab". The list role cancels the<li>semantics, which axe reports asaria-required-children(critical) andlistitem(serious), and no document tab is reachable by Tab. Every title is marked as a tab, with the current one as the single stop in the ring. - Menus. Lumino marks submenu items
role="presentation", leavingrole="menu"with no valid children at all. The items becomemenuitems and the decorative separators are hidden from the tree. - Status bar. Theia puts
aria-labelon roleless<div>s, whicharia-prohibited-attrforbids. Its editor language status item is worse: the label is built from an unrendered codicon token and a React node, so a screen reader announces the literal text$(bracket), [object HTMLDivElement]. The status bar becomes one focusable group, and labels carrying codicon or object noise are dropped rather than announced.
Reporting a problem¶
Open an issue and say which of these it is, with the app version and your platform:
- Keyboard: the chord or Tab sequence you pressed, and where focus went
instead of where you expected.
e2e/a11y.cjsfails on a trapped editor, so a new trap is a bug worth an issue even if you worked around it. - Screen reader: the reader and version, what it announced, and what you expected. Please include NVDA, JAWS, Orca or Narrator by name.
- Contrast: the theme, the element, and the colours if you have them. A
failing pair in a shipped theme fails
theme-contrast.test.cjs, so a report with exact colours is usually enough to fix it. - Anything you had to work around to finish a task without a mouse. That is the class of problem this page exists to remove, and it is worth reporting even when you found your own way past it.