Technical teardown
A booking flow a screen reader user couldn’t complete. Then it could.
Cal.diy — an MIT-licensed, open-source scheduling platform (github.com/calcom/cal.diy), a community fork of Cal.com. Not modified for our benefit beyond the fixes described here; not our product.
Community issue #26416 (opened 3 January 2026, still open) documents these barriers from an audit conducted with a blind user. Issue #28772 and open PR #28776 (updated 7 August 2026, not merged) independently propose partial fixes for date and slot naming. ABØ did not discover these barriers first and does not claim to.
A spatial read of the same fix: a visual layer that looked fine, a semantic layer with a hole in it — the missing accessible name — found, then patched. Scroll to watch it resolve. This is additive; the screenshots and transcript below are the actual evidence.
01 · User experience
What a real screen reader user heard
Verbatim, from a real NVDA session — not paraphrased, not synthesized. No audio recording exists for this session; this is the transcript, quoted exactly.
11 button
No date. No month. No year. No indication it can be selected.
August 2026 grouping Tuesday, August 11, 2026, available toggle button pressed
Full date, availability, month context, and toggle state — one utterance.
4:00am button
A bare time. No date. No state.
4:00am on Tuesday, August 11 toggle button not pressed
Time, date, and explicit pressed/not-pressed state.
Human NVDA — live status on date change
Baseline: silence. Fixed: “16 times available on August 12, 2026 pressed”
evidence/track-b/nvda-human-session-2026-08-10.md, Test F
This is the strongest single piece of human evidence in the engagement: a real screen reader user hears nothing at all on the baseline when the date changes, and a specific, correct count on the fixed build.



The two screenshots look almost identical on purpose — this fix changes what is announced to assistive technology, not what a sighted user sees. That is the entire point: the barrier was never visible in the first place.
02 · Component cause
Traced to source, not guessed at
Source review located the exact cause in two files. Neither exposed selection state or announced updates.
packages/features/calendars/components/DatePicker.tsx
packages/features/calendars/components/DatePicker.tsx rendered date cells with {date.date()} as their only accessible content.
Diagnosis
apps/web/modules/bookings/components/AvailableTimes.tsx
apps/web/modules/bookings/components/AvailableTimes.tsx did the same with bare time strings.
Diagnosis
Neither component exposed selection state or announced async updates.
03 · Code remediation
Fixed in the repository, not painted over
Component clustering: the fix lives in 3 components. Every date cell and every time slot on the page inherits it — 31 date cells and 16 time slots all show correct names with zero per-instance work, because the fix is at the component, not the page.
- Baseline:
- 176037d0
- Fixed:
- ad3de48d
04 · Testing
Verified twice, by two different methods
| Metric | Baseline | Fixed |
|---|---|---|
| Violations | 3 | 3 |
| Incomplete | 2 | 2 |
| Passes | 28 | 28 |
Identical in violation IDs, node counts, impact levels, incomplete count, and passes. All three violations are pre-existing and unrelated to this remediation — two in Cal.diy’s own code, one from the Next.js dev-mode toolbar.
Locked claim — exact wording
In this full-application evidence, the automated axe run did not identify the task-level barriers demonstrated through accessible-name and state analysis and interaction-focused testing, and its result set was unchanged by the remediation.
docs/TRACK-B-RESULTS.md §5
0 of 5 of the 5 blocking findings this engagement fixed were detectable by axe alone. This is why ABØ runs a manual and assistive-technology pass on every engagement — an automated-only scan would have reported this booking flow as unchanged, both before and after the fix.
| Tab presses, page load → first available date | 6 |
|---|---|
| Tab presses, page load → first available time slot | 22 |
| Unit tests, targeted components | 23/23 passing |
Real Enter presses correctly activated both the date and the slot, confirming genuine keyboard operability of the entire hero flow, not just mouse. Tabs 7–20 pass through the 14 remaining enabled dates one at a time on the way to the first slot — a real, unchanged limitation of this flow’s tab order, out of this remediation’s scope.
23/23 passing
05 · Human NVDA
Run once, partial coverage, real findings
One session, one tester — the founder, not a daily screen-reader user, which is stated here as a real limitation, not a footnote. NVDA + Chrome, both live environments, 10 August 2026.
| Covered and confirmed | Not covered this session |
|---|---|
| A (date names) · C (selected-date state) · D (slot names) · F (live status) | B · G (aria-pressed verbosity — still open) · H (weekday/month orientation) · J (full task, screen off) |
No-availability modal — NVDA on open
dialog clickable Close button button Close
evidence/track-b/nvda-human-session-2026-08-10.md, Test I
Two real, pre-existing defects were found in a file this remediation never touched (NoAvailabilityDialog.tsx): the dialog’s title and body are not automatically announced on open, and focus is not returned to the trigger after Escape. Reproduced by temporarily changing a live admin setting, then reverting it. Documented, not fixed — fixing an untouched file was out of this engagement’s scope, and treating it otherwise would be exactly the scope creep this studio’s own discipline exists to prevent.
0 of 5
blocking findings in this engagement were detectable by automated tooling alone.
06 · Evidence
What this does not claim
- One human NVDA session, one tester who is not a daily screen-reader user, partial script coverage.
- Tested against a development build, not production.
- Second-locale correctness not verified in either direction.
- aria-pressed verbosity across ten enabled dates — the specific question the NVDA script was built to answer — remains genuinely open.
- Two real, pre-existing defects found in a file we did not touch (NoAvailabilityDialog.tsx): title/body not announced on open, focus not returned after Escape. Documented, not fixed, not claimed as our discovery.
Never claimed, on this page or anywhere else
- No "ADA compliant."
- No "fully accessible."
- No "guaranteed."
- No "NVDA verified" beyond the specific items actually run (A, C, D, F, I — not B/G/H/J).
- No implication that ABØ discovered barriers the community already reported.
- No claim that the NoAvailabilityDialog.tsx defects were fixed — they were not; they are documented and out of scope.
Evidence manifest
The underlying artifacts this page is drawn from, kept in ABØ’s local engagement record.
evidence/track-b/ nvda-human-session-2026-08-10.md the human NVDA session — verbatim quotes + analysis full-app-axe-comparison.json baseline vs fixed, identical violation set keyboard-tab-sequence.json real Tab/Enter sequence, exact counts no-availability-dialog.json modal reproduction, focus management, 2 real findings r3-r7-live-region-fixed.json live-region + selection state, fired together, live desktop-baseline.png / desktop-fixed-slot-focused.png / mobile-fixed-375x812.png
This is what a Conformance Sprint produces.
Merged fixes, tested twice, with evidence you can hand to a customer — not a findings PDF.