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 5 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. |
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