fix(ui): add ARIA labels to booking calendar date cells, weekday headers, and time slot buttons#28776
fix(ui): add ARIA labels to booking calendar date cells, weekday headers, and time slot buttons#28776LubaKaper wants to merge 6 commits intocalcom:mainfrom
Conversation
|
Luba Kaper seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
…context Fixes the accessibility bug where time slot buttons only announced the time string (e.g. "1:30pm") with no booking context for screen readers. Buttons now announce "Book 1:30 PM" for available slots and "1:30 PM, unavailable" for full or taken slots. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds accessibility labels and tests for booking and calendar UI. Introduces 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/ui/components/form/date-range-picker/Calendar.tsx (1)
62-65: Hardcoded weekday names bypass localization.The
labelWeekdayfunction uses hardcoded English weekday names, which won't translate for non-English users. Consider usingdate-fnslocale-aware formatting or theIntl.DateTimeFormatAPI for consistency with the rest of the codebase.♻️ Proposed fix using Intl.DateTimeFormat
labelWeekday: (day) => { - const fullNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; - return fullNames[day.getDay()]; + return new Intl.DateTimeFormat("en-US", { weekday: "long" }).format(day); },Note: To support the user's locale, you'd need to pass a
localeprop toCalendarand use it here. For now, this at least uses a standard API that can be extended.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ui/components/form/date-range-picker/Calendar.tsx` around lines 62 - 65, The labelWeekday implementation in Calendar currently returns hardcoded English names; update the labelWeekday function to produce locale-aware weekday labels (e.g., via Intl.DateTimeFormat or date-fns format) and accept a locale parameter for the Calendar component so it can format using the user's locale; replace the hardcoded fullNames logic in labelWeekday with a call to new Intl.DateTimeFormat(locale, { weekday: 'long' }).format(day) (or the equivalent date-fns call) and ensure the Calendar component accepts and forwards the locale prop to this formatter.apps/web/modules/bookings/components/AvailableTimes.test.tsx (1)
9-12: The framer-motion mock appears unnecessary.This mock imports the original module and re-exports it unchanged, which has no effect. If no mocking is needed, consider removing it. If the intent was to mock specific exports like
AnimatePresenceorm, update accordingly.♻️ Option 1: Remove the mock if not needed
-vi.mock("framer-motion", async (importOriginal) => { - const actual = (await importOriginal()) as typeof import("framer-motion"); - return { ...actual }; -}); -♻️ Option 2: If you need to suppress animations for test stability
vi.mock("framer-motion", async (importOriginal) => { const actual = (await importOriginal()) as typeof import("framer-motion"); - return { ...actual }; + return { + ...actual, + AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>, + m: { + div: ({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) => <div {...props}>{children}</div>, + }, + }; });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/modules/bookings/components/AvailableTimes.test.tsx` around lines 9 - 12, The vi.mock call in AvailableTimes.test.tsx currently re-imports and re-exports the original framer-motion module (vi.mock("framer-motion", ...)) which is a no-op; either remove this vi.mock block entirely if no mocking is needed, or replace it with a targeted mock that stubs the specific exports used in the tests (e.g., mock AnimatePresence to render children directly and mock the motion component alias like m to a simple passthrough) so tests are stable without recreating the whole module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/web/modules/bookings/components/AvailableTimes.test.tsx`:
- Around line 9-12: The vi.mock call in AvailableTimes.test.tsx currently
re-imports and re-exports the original framer-motion module
(vi.mock("framer-motion", ...)) which is a no-op; either remove this vi.mock
block entirely if no mocking is needed, or replace it with a targeted mock that
stubs the specific exports used in the tests (e.g., mock AnimatePresence to
render children directly and mock the motion component alias like m to a simple
passthrough) so tests are stable without recreating the whole module.
In `@packages/ui/components/form/date-range-picker/Calendar.tsx`:
- Around line 62-65: The labelWeekday implementation in Calendar currently
returns hardcoded English names; update the labelWeekday function to produce
locale-aware weekday labels (e.g., via Intl.DateTimeFormat or date-fns format)
and accept a locale parameter for the Calendar component so it can format using
the user's locale; replace the hardcoded fullNames logic in labelWeekday with a
call to new Intl.DateTimeFormat(locale, { weekday: 'long' }).format(day) (or the
equivalent date-fns call) and ensure the Calendar component accepts and forwards
the locale prop to this formatter.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4c564069-bdba-4cca-90eb-f3a9ea2f247c
📒 Files selected for processing (5)
apps/web/modules/bookings/components/AvailableTimes.test.tsxapps/web/modules/bookings/components/AvailableTimes.tsxpackages/features/calendars/components/DatePicker.tsxpackages/i18n/locales/en/common.jsonpackages/ui/components/form/date-range-picker/Calendar.tsx
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/web/modules/bookings/components/AvailableTimes.test.tsx (1)
70-81: Consider more precise assertions for regression safety.The regex-based assertions (
/^Book /i,/, unavailable$/i) are valid but loose. Using exact expected labels would catch subtle formatting regressions (e.g., wrong time zone, missing space).♻️ Suggested more precise assertions
it("renders time slot button with Book aria-label for an available slot", () => { render( <AvailableTimes slots={[mockSlot]} event={mockEvent} unavailableTimeSlots={[]} skipConfirmStep={false} /> ); - expect(screen.getByRole("button", { name: /^Book /i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Book 1:30pm" })).toBeInTheDocument(); }); it("renders time slot button with unavailable aria-label when slot is in unavailableTimeSlots", () => { render( <AvailableTimes slots={[mockSlot]} event={mockEvent} unavailableTimeSlots={[mockSlot.time]} skipConfirmStep={false} /> ); - expect(screen.getByRole("button", { name: /, unavailable$/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "1:30pm, unavailable" })).toBeInTheDocument(); });Also applies to: 83-94
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/modules/bookings/components/AvailableTimes.test.tsx` around lines 70 - 81, The test uses loose regex assertions for the button aria-label; update the assertions in AvailableTimes.test.tsx to assert the exact expected aria-label text for the rendered buttons (use the exact string that includes the full label, time and timezone) instead of /^Book /i and /, unavailable$/i so regressions in formatting are caught; locate the tests that render <AvailableTimes slots={[mockSlot]} event={mockEvent} ... /> (references: AvailableTimes component, mockSlot, mockEvent) and replace the regex-based getByRole name matchers with exact name strings for both the available-slot assertion and the unavailable-slot assertion (also update the second test around the other block noted at lines 83-94).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/web/modules/bookings/components/AvailableTimes.test.tsx`:
- Around line 70-81: The test uses loose regex assertions for the button
aria-label; update the assertions in AvailableTimes.test.tsx to assert the exact
expected aria-label text for the rendered buttons (use the exact string that
includes the full label, time and timezone) instead of /^Book /i and /,
unavailable$/i so regressions in formatting are caught; locate the tests that
render <AvailableTimes slots={[mockSlot]} event={mockEvent} ... /> (references:
AvailableTimes component, mockSlot, mockEvent) and replace the regex-based
getByRole name matchers with exact name strings for both the available-slot
assertion and the unavailable-slot assertion (also update the second test around
the other block noted at lines 83-94).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5ecc7bd6-f8ea-4d84-8107-5107cb44000b
📒 Files selected for processing (2)
apps/web/modules/bookings/components/AvailableTimes.test.tsxpackages/ui/components/form/date-range-picker/Calendar.tsx
✅ Files skipped from review due to trivial changes (1)
- packages/ui/components/form/date-range-picker/Calendar.tsx
|
CLA signed as LubaKaper. Some commits were made with local machine emails — I've added them to my GitHub account. |
|
Hey @sahitya-chandra — would love to get your eyes on this when you have a moment. This PR adds ARIA labels to the |
|
This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active. |
What does this PR do?
Screen readers announced no meaningful context when navigating Cal.com's booking calendar. Date buttons read only a
number ("4"), and time slot buttons read only the time ("1:30pm") — giving users no month, year, or booking context.
This PR adds descriptive
aria-labelattributes to both elements so screen reader users can navigate the booking flowindependently.
Visual Demo
Image Demo:
Date button — Before:
Name: "4", no aria-label, no contextDate button — After:
Name: "April 9, 2026"/Name: "April 11, 2026, unavailable"for disabled datesTime slot button — Before:
Name: "1:30pm", no aria-label, no booking contextTime slot button — After:
Name: "Book 9:45am"Mandatory Tasks (DO NOT REMOVE)
N/A
behavior is validated manually via VoiceOver and DevTools Accessibility tab. No automated test framework currently
covers screen reader output in this codebase.
How should this be tested?
localhost:3000/[username]/15min)Nameshould show"April 9, 2026"Nameshould show"April 9, 2026, unavailable"Nameshould show"Book 9:45am"Alternatively enable VoiceOver (Mac:
Cmd+F5) and tab through the calendar to hear the labels read aloud.No environment variables required. Any event type with available slots works.
Checklist