Skip to main content
ABØ STUDIOS

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.

Fixed 3 components23/23 passingVerified twice — component & full app1 human NVDA session, 10 Aug 2026

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.

Before — NVDA, date cell

11 button

No date. No month. No year. No indication it can be selected.

After — NVDA, same date cell

August 2026 grouping Tuesday, August 11, 2026, available toggle button pressed

Full date, availability, month context, and toggle state — one utterance.

Before — NVDA, time slot

4:00am button

A bare time. No date. No state.

After — NVDA, same time slot

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

Full accessible names built from Intl.DateTimeFormat — real locale-aware, timezone-safe dates.
Selection state exposed via aria-pressed on the selected date and slot.
A polite live region (role="status", aria-live="polite", aria-atomic="true") mounted before content changes, so updates are never silently missed.
The date grid given role="group" with an accessible name for the visible month.

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.

DatePicker.tsx
AvailableTimes.tsx
AvailableTimeSlots.tsx
Baseline:
176037d0
Fixed:
ad3de48d

04 · Testing

Verified twice, by two different methods

Full-app axe-core — baseline vs. fixed, same run method
MetricBaselineFixed
Violations33
Incomplete22
Passes2828

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.

Scripted keyboard — real Tab/Enter, exact counts
Tab presses, page load → first available date6
Tab presses, page load → first available time slot22
Unit tests, targeted components23/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.

Script coverage — what this session actually confirmed
Covered and confirmedNot 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.

Request a PilotSee the Conformance Sprint

Read exactly how we test →