Accessibility conformance report · No Look Typing
This is a VPAT-style report against WCAG 2.1 Level AA for the No Look Typing app at nolooktyping.com/play/. It is written to be checked. Where we fall short we have said so and given the measurement, because a report that overclaims is worse than one that admits gaps.
Report published 8 September 2026 · based on testing of the build deployed 8 September 2026 · author: the No Look Typing development team · self-assessment
At a glance
The four outright failures are Reflow (the on-screen keyboard inside a lesson loses its outer key columns on a narrow screen), Timing Adjustable (practice runs have a fixed clock that cannot be extended or switched off), Pause, Stop, Hide (a small animation on the reach arrow loops with no way to stop it) and Non-text Contrast (the border colour used throughout the interface is below the required ratio). All four have a fix identified and none of them requires re-architecting anything.
The product does some things unusually well for its size: every control on every screen has a proper accessible name, the whole app is keyboard-driven by nature, its modal dialogs are implemented correctly down to focus restoration, and it ships two themes whose body text clears AA by a wide margin in both.
Method — read this before the table
Not NVDA, not JAWS, not VoiceOver, not TalkBack, not Narrator. Everything in this report was established by reading the source code, computing colour contrast arithmetically, and driving the live page through the browser’s DOM and accessibility APIs.
That is enough to prove that the right markup is present and behaves correctly. It is not enough to prove that a blind person can use the practice screen, and we are not going to claim otherwise. Every judgement below that depends on how assistive technology actually behaves is marked needs AT confirmation, and the specific open questions are listed at the end.
Also untested: Windows High Contrast / forced-colors mode, voice control (Dragon, Voice Control), switch access, screen magnification combined with assistive technology, and mobile screen readers.
tabindex,
unlabelled graphics and heading structure.The web application at nolooktyping.com/play/. The
marketing page at nolooktyping.com/ has not been separately
audited to the same depth; two contrast problems found there in passing
are noted at the bottom of this report.
| index.html | 1d72d7ab |
| css/app.css | 1da9b064 |
| js/app.js | c821fca1 |
| js/keyboard.js | 1dee85c7 |
| js/keys.js | 4e8aaa38 |
| js/practice.js | 8e3780d5 |
MD5 prefixes, verified against the live server on 8 September 2026 at 02:12 PDT. The app is under active development; a later build may differ.
The problems
In plain words, worst first. Each one is in the conformance table below with its criterion number.
Who this affects: anyone using a small window, a split screen, or high browser zoom — which includes many people with low vision, since zooming is the most common adaptation there is.
What happens: inside the two guided lessons, the picture
of the keyboard is a fixed 474 pixels wide and does not shrink below
a certain point. At a 320 pixel viewport its outer columns
(1 Q A Z on the left, 0 P ’ / on the right)
sit outside the visible area, and because the page itself does not scroll
sideways there is no way to bring them into view.
Status: fix identified — a smaller key size below 560 pixels plus a horizontal scroll container as a backstop.
What already works: the four main tabs reflow correctly at 320 pixels with nothing clipped, and the practice screen deliberately refuses to run below 700 pixels, showing an explanation and pointing you to the parts of the app that are useful on a phone.
Who this affects: anyone who needs more time — people using a screen reader, a switch, an on-screen keyboard, or who simply reads more slowly.
What happens: a practice run lasts 90, 150 or 180 seconds depending on which one you pick, and there is no setting to extend it, adjust it or turn it off. Pressing Escape pauses the clock, which helps, but a pause is not an extension.
Status: fix identified — a practice-length setting offering a normal, a long (three times) and a no-clock option. The engine already supports untimed runs; two of its modes use them.
The one exception we are claiming: the two 60-second speed tests. Words per minute is defined as a rate, so for those the time limit is the measurement — changing it would not produce a longer test, it would produce a different and meaningless number. WCAG allows this under the “Essential” exception, in the same way a stopwatch is essential to a race. That argument covers the two tests and nothing else; the ordinary practice runs have no such defence and will be fixed.
Who this affects: people with low vision or reduced contrast sensitivity, and anyone on a poor screen or in bright light.
What happens: one colour draws most of the boundaries in the app — card edges, button outlines, input outlines, table rules, the tab strip. It measures 1.75–2.61:1 against the surfaces it sits on, where 3:1 is required. Separately, the empty half of every progress bar is 1.12–1.19:1 against the panel behind it, so you can see how far the bar has filled but not how far it could go.
Status: fix identified, with computed replacement values that clear 3:1 on every surface in both themes. This one is a two-line change and is near the top of the queue. Note that the border colour was already darkened once during this audit; the change was a genuine improvement (from 1.36:1) but it did not reach the threshold, which is why we compute these rather than judge them by eye.
The eight finger names on the PROGRESS tab are printed in a colour set designed for the light page. In the dark theme all nine measure 2.11–4.07:1 against their panel; two of them also miss in the light theme. A second, brighter colour set already exists in the code for exactly this purpose and is simply not being used here.
The line that tells you which finger to use is announced to screen readers, and it is rewritten on every keystroke — measured at 12 updates a second, each a full sentence. Because these announcements queue rather than interrupt, a screen reader would fall further and further behind. We intend to announce only when the finger changes, and at most once every two seconds.
The theme, keyboard, sound and shortcut switches are announced by their current value alone — “MIDNIGHT”, “SHOWN”, “ON”, “MAC”. Their visible labels are next to them but not connected to them in code. Fix: give each an accessible label naming the setting and its state.
Ten weeks of practice history are drawn as seventy shaded squares. They now carry graded borders as well as colour, so the intensity no longer depends on telling greens apart — but they still have no text alternative, so a screen reader gets nothing from them. Fix: label each square with its date and minutes, and add a one-sentence summary.
Downloading or deleting an account asks for the
password through window.prompt, which shows it in clear
text and cannot be labelled or masked. This is on the list to replace
with the app’s own dialog, which is already built correctly
elsewhere.
A small dot travels along a curved arrow from your finger’s home key to the key you owe, looping continuously while that key is on screen. Continuously moving content needs a way to pause or stop it, and there is not one yet. It will be bounded to a few repetitions and switched off for people who ask their system to reduce motion.
An earlier internal note claimed this was
implemented. It is not. The stylesheet contains two
prefers-reduced-motion rules, covering two small decorations;
six animations, five transitions and the moving arrow above are not
covered. Honouring the setting is a single stylesheet block plus a
three-line guard for the arrow, and it is queued. (This is a AAA-level
criterion, so it is not a AA conformance failure — but it was
claimed, so it belongs here.)
The other side of the ledger
A sweep of all four tabs found zero unnamed
buttons or fields, zero fields relying on placeholder text as their
label, no duplicate element IDs, no forced tab order, and no
unlabelled graphics. Every input carries an autocomplete
value.
It is a typing tutor, so this was never optional — but it also holds up in the places that usually break: no keyboard traps, focus moves into dialogs and back out to the right place when they close, and when the button you opened a dialog from has been removed by a redraw, focus lands on the main panel rather than at the top of the page.
Real dialog roles, the page behind made inert so it leaves the tab order and the accessibility tree entirely, Tab cycling inside the dialog, and a safeguard that guarantees the page becomes usable again however a dialog is closed. Focus deliberately lands on the dialog itself rather than on its first button, so Enter and Space cannot fire something you did not mean.
Body text measures 14.17:1 (light) and 16.32:1 (dark). Of the text pairs we computed, the great majority clear AA with room to spare; the failures listed above are specific elements, not a systemic problem with the palette.
At the 200% zoom equivalent, and again with the WCAG text-spacing override applied, all four tabs render with no horizontal scrolling and nothing clipped or overlapping.
Sign-in problems are placed in a live alert region rather than only shown. A failed practice run says exactly what was missed — “Needed 34 wpm at 97%. You were 28 at 98% — 6 wpm short” — rather than just “failed”.
The detail
Terms are the ones used in the standard VPAT: Supports means the criterion is met; Partially means it is met in most places but there are named exceptions; Does not means it is not met; N/A means the criterion does not apply to this product. The verdict is always written as a word, never signalled by colour alone.
| Criterion | Level | Conformance | Remarks |
|---|---|---|---|
| 1.1.1 Non-text Content | A | Partially | Decorative graphics (the hands, the keyboard) are
correctly hidden from assistive technology and have a text
equivalent in prose. The progress chart is an image with a label.
Exception: the 70-cell practice calendar and the
level progress pips are empty elements carrying only a
title tooltip — no accessible name, no role, no
text alternative (app.js, calendar()). |
| 1.2.1 Audio-only / Video-only | A | N/A | No prerecorded media. The only sound is synthesised keystroke feedback generated in the browser. |
| 1.2.2 Captions (Prerecorded) | A | N/A | No media. |
| 1.2.3 Audio Description or Alternative | A | N/A | No media. |
| 1.3.1 Info and Relationships | A | Partially | Tabs, panels and dialogs use correct ARIA; the line you
are typing is a labelled group with aria-current on the
live character; tables use header cells; fields are labelled.
Exceptions: there is no h1 anywhere and
heading levels do not follow visual hierarchy; the settings toggle
buttons are not associated with their adjacent labels; the calendar
conveys a data relationship with no programmatic structure. |
| 1.3.2 Meaningful Sequence | A | Supports | Reading order matches visual order on all four tabs and in the practice dialog. No CSS reordering is used. |
| 1.3.3 Sensory Characteristics | A | Supports | Instructions name the finger and the key (“Your right index — up from J to U”) rather than relying on shape, position or colour. |
| 1.4.1 Use of Color | A | Partially | Level pips, earned badges and the calendar now carry non-colour cues (a tick glyph, graded border weights) as well as colour. Exception: in the line you are typing, “typed correctly”, “corrected” and “typed wrong” are distinguished by hue alone (a mistyped space is the one case that also gets a glyph). |
| 1.4.2 Audio Control | A | Supports | No audio plays automatically. Keystroke sounds are user-initiated, under half a second, and there is an on/off control in Settings. |
| 2.1.1 Keyboard | A | Supports | All functionality is keyboard operable; verified by operating every screen and dialog from the keyboard alone. Needs AT confirmation for behaviour inside a screen reader’s virtual cursor mode. |
| 2.1.2 No Keyboard Trap | A | Supports | Dialogs cycle Tab within themselves and are left with Escape; the practice screen has no focusable elements, so Tab is not captured, and Escape opens the pause screen from which Quit is reachable. Verified. |
| 2.1.4 Character Key Shortcuts | A | Supports | Printable keys are content input inside a focused modal, not global shortcuts, which meets the “active only on focus” exception. There are no single-character shortcuts outside a practice run. Needs AT confirmation that a screen reader’s own single-key commands are not lost. |
| 2.2.1 Timing Adjustable | A | Does not | Practice runs are fixed at 90 s (warm up), 150 s (the three main practices) and 180 s (your own text), with no way to turn off, adjust or extend. Escape pauses the clock, which is not sufficient. The two 60-second speed tests are claimed under the Essential exception, as explained above; the practice runs are not. |
| 2.2.2 Pause, Stop, Hide | A | Does not | An SVG animation on the reach arrow is set to
repeatCount="indefinite" and runs for as long as a
non-home-row key is displayed, with no pause, stop or hide
mechanism. All other animation in the app is single-shot and under
half a second. |
| 2.3.1 Three Flashes or Below | A | Supports | Nothing flashes. Every keyframe animation was enumerated: all are single-shot, the longest a 9-second fade. |
| 2.4.1 Bypass Blocks | A | Supports | A main landmark is present (technique
ARIA11) and only four tab buttons precede it. A skip link would be
an improvement but is not required where a landmark exists. |
| 2.4.2 Page Titled | A | Supports | Descriptive title. As a single-page app the title does not change per tab; the tab panel is labelled by its tab instead. |
| 2.4.3 Focus Order | A | Supports | Verified: opening a dialog moves focus into it and makes the rest of the page inert; closing restores focus to the element that opened it, or to the main panel if a redraw has removed it. |
| 2.4.4 Link Purpose (In Context) | A | Supports | The app contains almost no links; those present have descriptive text. |
| 2.5.1 Pointer Gestures | A | Supports | Single-pointer activation only; no path-based or multipoint gestures. |
| 2.5.2 Pointer Cancellation | A | Supports | Actions fire on the up-event via standard click handling. |
| 2.5.3 Label in Name | A | Supports | Accessible names are taken from visible text throughout. (The settings toggles are named by their visible text too — the problem there is that the text does not describe the control’s purpose, which is reported under 4.1.2 and 2.4.6.) |
| 2.5.4 Motion Actuation | A | N/A | No device-motion or user-motion operation. |
| 3.1.1 Language of Page | A | Supports | <html lang="en">. |
| 3.2.1 On Focus | A | Supports | Focus alone never changes context. |
| 3.2.2 On Input | A | Supports | No field changes context on entry; every state change follows an explicit activation. |
| 3.3.1 Error Identification | A | Supports | Sign-in and recovery errors are written into a live alert region and describe the problem in words. |
| 3.3.2 Labels or Instructions | A | Partially | Every field has a programmatic label and helpful surrounding instructions. Exceptions: the account username and password fields use placeholder text as their only visible label, which disappears on typing; the four settings toggles have no programmatic label at all. |
| 4.1.1 Parsing | A | Supports | No duplicate IDs and no malformed nesting found across all four tabs and every dialog. (This criterion is obsolete in WCAG 2.2; assessed here for completeness against 2.1.) |
| 4.1.2 Name, Role, Value | A | Partially | Every interactive element has an accessible name; tabs expose selection state, dialogs expose modality, selectable rows expose pressed state. Exception: the four settings toggles expose only their current value as their name (“MIDNIGHT”, “SHOWN”, “ON”, “MAC”) with no indication of what they control and no pressed state. |
| Criterion | Level | Conformance | Remarks |
|---|---|---|---|
| 1.2.4 Captions (Live) | AA | N/A | No live media. |
| 1.2.5 Audio Description | AA | N/A | No media. |
| 1.3.4 Orientation | AA | Supports | No orientation is locked. |
| 1.3.5 Identify Input Purpose | AA | Supports | Every relevant field carries an appropriate
autocomplete token (username,
current-password, new-password,
given-name). |
| 1.4.3 Contrast (Minimum) | AA | Partially | Body and interface text clear AA comfortably in both themes. Exceptions: the character you owe when you have just mistyped it, in the dark theme (2.85:1, needs 3:1 at that size); the eight finger names on the Progress tab (2.11–4.07:1 dark, and two of nine below 4.5:1 light); and the letters on the on-screen keyboard once the hint fade drops below full strength (4.27:1 at the second difficulty, 1.74:1 at the third, in the light theme). Full figures below. |
| 1.4.4 Resize Text | AA | Supports | Verified at the 200% equivalent: no loss of content or function, no horizontal scrolling. |
| 1.4.5 Images of Text | AA | Supports | The app contains no images at all; everything is live text or drawn vector graphics. |
| 1.4.10 Reflow | AA | Does not | The four main tabs reflow correctly at 320 CSS pixels, and the practice screen substitutes an equivalent message below 700. The two guided lessons fail: the keyboard graphic is fixed at 474 pixels wide, so at 320 pixels its outer key columns fall outside the viewport with no horizontal scrolling available to reach them. |
| 1.4.11 Non-text Contrast | AA | Does not | The focus indicator, filled progress bars, selected rows, the lit key and the lit finger all pass. The general border colour does not (1.75–2.61:1 across both themes), nor do the empty tracks of progress bars and the two faintest calendar levels (1.12–2.87:1), nor the keycap borders (2.22:1), nor the hand silhouettes against the dark theme (1.65:1). |
| 1.4.12 Text Spacing | AA | Supports | Verified with the standard override applied: no clipping, no overlap, no horizontal scrolling. |
| 1.4.13 Content on Hover or Focus | AA | Supports | The only hover content is native browser tooltips, which are provided by the user agent and exempt. (Their information is also needed in text — that is reported under 1.1.1, not here.) |
| 2.4.5 Multiple Ways | AA | N/A | The app is a single page; the tab bar is the only navigation and this criterion does not apply to a single-page process. |
| 2.4.6 Headings and Labels | AA | Partially | Headings and labels are descriptive where they exist. Exceptions: there is no top-level heading on the page, and heading levels are used for visual weight rather than structure; the four settings toggles are labelled with their value rather than their purpose. |
| 2.4.7 Focus Visible | AA | Supports | A 3 pixel outline measuring 4.49:1 in the light theme and 13.88:1 in the dark. Text fields and links fall back to the browser’s own indicator. |
| 3.1.2 Language of Parts | AA | Supports | All content is in English. |
| 3.2.3 Consistent Navigation | AA | Supports | The tab bar is identical in order and position on every screen. |
| 3.2.4 Consistent Identification | AA | Supports | Repeated components are named and behave identically throughout. |
| 3.3.3 Error Suggestion | AA | Supports | Errors say what to do next, and a failed practice run states the exact shortfall in speed or accuracy. |
| 3.3.4 Error Prevention | AA | Partially | Deleting a player or an account is confirmed, states
what will be lost, and offers to export the data first.
Exception: both account paths collect the password
through a browser prompt, which displays it in clear
text and cannot be labelled or masked. |
| 4.1.3 Status Messages | AA | Partially | Status regions are correctly placed outside the part of the page that becomes inert behind a dialog — verified, and a real defect until recently. Run start, word completion, wrong keys and results are all announced. Exception: the finger-hint region is a polite live region rewritten on every keystroke (measured at 12 updates a second), which will queue up far behind the user. Needs AT confirmation for the practical severity. |
Appendix
Ratios were computed with the sRGB relative-luminance formula from the
specification — each channel linearised as
c ≤ 0.03928 ? c/12.92 : ((c+0.055)/1.055)^2.4 — rather
than read from a tool, because several of these sit within a fifth of a
point of the threshold. Translucent layers were composited first. 198 pairs
were checked across both themes, with further passes for state boundaries
(a lit key against the keys beside it, rather than against the page) and for
the way the hints fade out as you improve. A representative selection
follows.
One correction worth recording, since it is an easy mistake to make in the other direction: the lit finger and lit key look like failures when compared with the page background, but the correct comparison under 1.4.11 is with the adjacent unlit fingers and keys, and against those they pass comfortably. We are not going to claim a failure we do not have any more than we will hide one we do.
The page at nolooktyping.com/ was not
audited to the same depth, but two contrast problems were found in
passing and are on the list: its muted text colour measures 3.60–3.94:1
where 4.5:1 is required, and its rule colour measures 1.38–1.51:1
where 3:1 is required. This report page uses darkened versions of both.
The honest gap
These questions cannot be answered by reading code or computing ratios. They need a person with a screen reader, and until that has happened we are not going to characterise the app as usable with assistive technology.
We would rather publish this list than a claim we cannot support. If your institution runs its own assistive-technology testing, we would genuinely like the results, whatever they say.
Report a problem
Email [ACCESSIBILITY CONTACT — set this up before publishing; a reviewer will test it]. Useful things to include, though none of them are required — a description of the problem on its own is welcome:
What to expect. We aim to acknowledge within five working days and to come back with either a fix or a date. Anything that stops somebody using the app goes to the front of the queue.
If we cannot fix it quickly we will say so and describe the workaround, rather than leave the report open. Problems reported this way are added to our test suite, so the same fault cannot come back unnoticed.