Skip to content

fix(ui): fix disabled date contrast and update datepicker tests (#28771)#28791

Closed
nk10nikhil wants to merge 2 commits intocalcom:mainfrom
nk10nikhil:main
Closed

fix(ui): fix disabled date contrast and update datepicker tests (#28771)#28791
nk10nikhil wants to merge 2 commits intocalcom:mainfrom
nk10nikhil:main

Conversation

@nk10nikhil
Copy link
Copy Markdown

What does this PR do?

  • Fixes fix/wcag-color-contrast #28771
  • This PR resolves a WCAG 2.1 AA contrast issue in the calendar date picker where disabled days in light mode had insufficient contrast (~4.2:1).

Changes:

  • Replaced text-muted opacity-50 with accessible color values in Calendar.tsx:
    • Light mode: text-[#4A4E59]
    • Dark mode: dark:text-[#A3A3A3] (unchanged behavior, already compliant)
  • Updated datepicker tests to use semantic aria-disabled="true" instead of relying on opacity-50
  • Removed style-based assertions and aligned tests with accessibility best practices

Result:

  • Disabled dates now meet WCAG 2.1 AA contrast requirements
  • Improved accessibility for users with low vision
  • More robust and future-proof tests

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Steps to reproduce (before fix):

  1. Open any Cal.com booking page
  2. Ensure system/browser is in light mode
  3. Inspect a disabled date in the calendar
  4. Run:
    getComputedStyle(document.querySelector('[aria-disabled="true"]')).color

@nk10nikhil nk10nikhil requested review from a team as code owners April 8, 2026 10:35
Copilot AI review requested due to automatic review settings April 8, 2026 10:35
@github-actions github-actions bot added the 🐛 bug Something isn't working label Apr 8, 2026
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 8, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

Calendar.tsx: adjusted classNames for DayPicker; day_disabled now uses explicit color tokens for light/dark modes. DatePicker.tsx: date prop type changed from Date to Date | null; click handling now passes the clicked date directly and defaultMonth/selected use date ?? ... logic; trigger Button passes disabled={disabled}. Tests: added beforeEach to clear onChangeMock, updated tests to use date={null}, removed CSS-class-based disabled assertions in favor of ARIA-based checks and updated click/assertion flows. No exported signatures besides the date prop type change.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive The PR includes DatePicker.tsx changes extending beyond the calendar color fix (null handling, disabled prop handling), which were not explicitly mentioned in issue #28771 objectives. Clarify whether DatePicker.tsx null/disabled handling improvements are within scope; if not, consider separating into a follow-up PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main changes: fixing disabled date contrast (WCAG issue) and updating datepicker tests, which aligns with the core objectives of the PR.
Description check ✅ Passed The description comprehensively explains the WCAG 2.1 AA contrast issue being fixed, the specific color changes made, test updates, and expected results.
Linked Issues check ✅ Passed The PR addresses all key objectives from issue #28771: replaces text-muted opacity-50 with explicit color values (#4A4E59 light mode, #A3A3A3 dark mode) achieving WCAG 2.1 AA contrast, and updates tests to use aria-disabled="true" instead of style-based assertions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/ui/components/form/datepicker/datepicker.test.tsx (2)

59-63: Redundant assertion - already guaranteed by the filter.

The assertion on line 63 is tautological: disabledDates is filtered to only include elements where aria-disabled === "true" (line 60), so asserting that the first element has this attribute will always pass when disabledDates.length > 0 (which is already verified on line 62).

Additionally, await is unnecessary here since toHaveAttribute is synchronous.

Consider simplifying:

Suggested fix
     const disabledDates = getAllByRole("gridcell").filter(
       (cell) => cell.getAttribute("aria-disabled") === "true",
     );
     expect(disabledDates.length).toBeGreaterThan(0);
-    await expect(disabledDates[0]).toHaveAttribute("aria-disabled", "true");

If you want to keep an explicit assertion for documentation purposes, consider checking a property that isn't already guaranteed by the filter, or simply remove the redundant line.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/components/form/datepicker/datepicker.test.tsx` around lines 59 -
63, The test builds disabledDates by filtering getAllByRole("gridcell") for
cells with aria-disabled === "true", so the subsequent
expect(disabledDates[0]).toHaveAttribute("aria-disabled", "true") is redundant
and the await is unnecessary; remove that final assertion (or replace it with a
non-tautological check such as verifying a different attribute or text on
disabledDates[0])—update the test where disabledDates is defined and used to
either drop the redundant expect/toHaveAttribute call or change it to assert
something not already guaranteed by the filter.

43-43: Remove unnecessary await - toHaveAttribute is synchronous.

The toHaveAttribute matcher from @testing-library/jest-dom is synchronous and returns immediately. The await is unnecessary here.

Suggested fix
-    await expect(selectedDate).not.toHaveAttribute("aria-disabled", "true");
+    expect(selectedDate).not.toHaveAttribute("aria-disabled", "true");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/components/form/datepicker/datepicker.test.tsx` at line 43, The
test uses an unnecessary await on the synchronous matcher toHaveAttribute; in
the test file remove the await before the assertion so the line becomes a normal
synchronous assertion on selectedDate (i.e., replace await
expect(selectedDate).not.toHaveAttribute("aria-disabled", "true") with
expect(selectedDate).not.toHaveAttribute("aria-disabled", "true")). Ensure other
uses of toHaveAttribute in this test remain synchronous as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ui/components/form/datepicker/datepicker.test.tsx`:
- Around line 59-63: The test builds disabledDates by filtering
getAllByRole("gridcell") for cells with aria-disabled === "true", so the
subsequent expect(disabledDates[0]).toHaveAttribute("aria-disabled", "true") is
redundant and the await is unnecessary; remove that final assertion (or replace
it with a non-tautological check such as verifying a different attribute or text
on disabledDates[0])—update the test where disabledDates is defined and used to
either drop the redundant expect/toHaveAttribute call or change it to assert
something not already guaranteed by the filter.
- Line 43: The test uses an unnecessary await on the synchronous matcher
toHaveAttribute; in the test file remove the await before the assertion so the
line becomes a normal synchronous assertion on selectedDate (i.e., replace await
expect(selectedDate).not.toHaveAttribute("aria-disabled", "true") with
expect(selectedDate).not.toHaveAttribute("aria-disabled", "true")). Ensure other
uses of toHaveAttribute in this test remain synchronous as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f2116d5-f27c-4fc7-88b3-2eeac7bcea4c

📥 Commits

Reviewing files that changed from the base of the PR and between dcbb417 and 2c0a0f2.

📒 Files selected for processing (2)
  • packages/ui/components/form/date-range-picker/Calendar.tsx
  • packages/ui/components/form/datepicker/datepicker.test.tsx

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses WCAG 2.1 AA contrast compliance for disabled days in the UI date picker calendar (light mode), and updates the related tests to assert disabled state via accessibility attributes rather than styling.

Changes:

  • Updated react-day-picker day_disabled styling to use higher-contrast text colors in light mode.
  • Refactored DatePicker tests to identify disabled days via aria-disabled="true" instead of opacity-50.
  • Minor formatting adjustments in test renders/assertions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
packages/ui/components/form/date-range-picker/Calendar.tsx Updates disabled-day text color to improve contrast in light mode.
packages/ui/components/form/datepicker/datepicker.test.tsx Updates tests to assert disabled state semantically via aria-disabled and removes style-based checks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +23
test("Should show placeholder when no date is provided", () => {
const { getByTestId } = render(<DatePicker date={null as unknown as Date} />);
const { getByTestId } = render(
<DatePicker date={null as unknown as Date} />,
);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test passes null as unknown as Date into DatePicker, which bypasses the component’s date: Date prop typing. Consider either updating DatePicker to accept Date | null (and handle null when passing defaultMonth/selected to Calendar) or adjusting the test to avoid unsafe casts so it matches the supported API surface.

Copilot uses AI. Check for mistakes.
Comment on lines 29 to 44
@@ -36,28 +40,38 @@ describe("Tests for DatePicker Component", () => {
});

expect(selectedDate).toBeTruthy();
await expect(selectedDate).not.toHaveClass("opacity-50");
await expect(selectedDate).not.toHaveAttribute("aria-disabled", "true");
});
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is named as if it validates date selection, but it never clicks a day or asserts that onDatesChange was called with the expected date. Instead it finds the cell with tabindex="0" (focus target) and asserts it’s not disabled, which doesn’t prove selection logic works. Update the test to click a specific enabled day and assert onDatesChange is invoked with that date (and optionally that the clicked day is not aria-disabled).

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +63
const disabledDates = getAllByRole("gridcell").filter(
(cell) => cell.getAttribute("aria-disabled") === "true",
);
expect(disabledDates.length).toBeGreaterThan(0);
await expect(disabledDates[0]).toHaveAttribute("disabled");
await expect(disabledDates[0]).toHaveAttribute("aria-disabled", "true");
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the minDate test, filtering cells by aria-disabled === "true" and then asserting the first result has aria-disabled="true" is redundant and doesn’t verify which dates are disabled. Consider asserting that a known date before minDate is disabled and that minDate itself is not disabled/selectable, to make the test meaningful and less dependent on internal rendering order.

Copilot uses AI. Check for mistakes.
Comment on lines 72 to 75
expect(dateButton.classList.toString()).toContain(
"disabled:cursor-not-allowed",
);
expect(dateButton.classList.toString()).toContain("disabled:opacity-30");
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts that the rendered button’s class list contains Tailwind disabled:* variants, but those variants are present in the class string regardless of whether the button is actually disabled. To validate disabled={true} behavior, assert the trigger is disabled (e.g., toBeDisabled() / disabled attribute) and/or that clicking it does not open the calendar popover.

Suggested change
expect(dateButton.classList.toString()).toContain(
"disabled:cursor-not-allowed",
);
expect(dateButton.classList.toString()).toContain("disabled:opacity-30");
expect(dateButton).toBeDisabled();

Copilot uses AI. Check for mistakes.
day_outside: "",
day_disabled: "text-muted opacity-50",
day_range_middle: "aria-selected:bg-emphasis aria-selected:text-emphasis",
day_disabled: "text-[#4A4E59] dark:text-[#A3A3A3]",
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

day_disabled now uses hard-coded hex colors. This bypasses the project’s semantic theme tokens (e.g., text-subtle, text-muted) and makes the disabled-day color harder to tune for custom themes/branding. Consider introducing a semantic token/class for disabled date text (or using an existing token if one matches the required contrast) instead of embedding hex values here.

Suggested change
day_disabled: "text-[#4A4E59] dark:text-[#A3A3A3]",
day_disabled: "text-subtle",

Copilot uses AI. Check for mistakes.
@pull-request-size pull-request-size bot added size/L and removed size/M labels Apr 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/ui/components/form/datepicker/DatePicker.tsx (2)

10-11: Make callback nullability symmetric with the date prop.

date accepts null (Line 10), but onDatesChange only accepts Date (Line 11). This makes controlled usage asymmetric and harder to model for “cleared” state.

Proposed API-shape cleanup
 type Props = {
   date: Date | null;
-  onDatesChange?: ((date: Date) => void) | undefined;
+  onDatesChange?: ((date: Date | null) => void) | undefined;
   className?: string;
   disabled?: boolean;
   minDate?: Date | null;
   label?: string;
 };

Also applies to: 27-27

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/components/form/datepicker/DatePicker.tsx` around lines 10 - 11,
The onDatesChange callback signature is asymmetric with the date prop: change
the onDatesChange type so it accepts Date | null (matching the date prop)
wherever declared (e.g., the onDatesChange prop in DatePicker.tsx and the other
occurrence at the referenced line), update any related handler signatures (props
and internal calls to onDatesChange) to pass null when the date is cleared, and
adjust any TypeScript usages/calls to expect Date | null instead of only Date.

30-30: Rename calender to calendar for readability.

Small naming typo at Line 30 (used at Line 73) that hurts quick scanning.

Tiny readability diff
-  const calender = (
+  const calendar = (
     <Calendar
@@
-            {calender}
+            {calendar}

Also applies to: 73-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/components/form/datepicker/DatePicker.tsx` at line 30, Rename the
misspelled variable "calender" to "calendar" throughout the DatePicker
component: update the declaration (const calender = ...) and all usages (e.g.,
the JSX reference currently using calender) to "calendar" so the identifier is
consistent and readable; ensure you update any related references in the same
file to avoid TypeScript/JS errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ui/components/form/datepicker/DatePicker.tsx`:
- Around line 10-11: The onDatesChange callback signature is asymmetric with the
date prop: change the onDatesChange type so it accepts Date | null (matching the
date prop) wherever declared (e.g., the onDatesChange prop in DatePicker.tsx and
the other occurrence at the referenced line), update any related handler
signatures (props and internal calls to onDatesChange) to pass null when the
date is cleared, and adjust any TypeScript usages/calls to expect Date | null
instead of only Date.
- Line 30: Rename the misspelled variable "calender" to "calendar" throughout
the DatePicker component: update the declaration (const calender = ...) and all
usages (e.g., the JSX reference currently using calender) to "calendar" so the
identifier is consistent and readable; ensure you update any related references
in the same file to avoid TypeScript/JS errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 241706fe-6c79-4d3c-ad2e-a763bc766063

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0a0f2 and 78dda92.

📒 Files selected for processing (2)
  • packages/ui/components/form/datepicker/DatePicker.tsx
  • packages/ui/components/form/datepicker/datepicker.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/components/form/datepicker/datepicker.test.tsx

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

packages/ui/components/form/datepicker/DatePicker.tsx:32

  • Minor naming issue: the local variable is named calender (typo). Renaming to calendar would improve readability and reduce confusion when scanning the component.
  const fromDate = minDate ?? new Date();
  const calender = (
    <Calendar
      initialFocus

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 40 to +51
day: cn(
buttonClasses({ color: "minimal" }),
"w-8 h-8 md:h-11 md:w-11 p-0 text-sm font-medium aria-selected:opacity-100 inline-flex items-center justify-center"
"w-8 h-8 md:h-11 md:w-11 p-0 text-sm font-medium aria-selected:opacity-100 inline-flex items-center justify-center",
),
day_range_end: "hover:bg-inverted! text-inverted!",
day_range_start: "hover:bg-inverted! text-inverted!",
day_selected: "bg-inverted text-inverted",
day_today: "",
day_outside: "",
day_disabled: "text-muted opacity-50",
day_range_middle: "aria-selected:bg-emphasis aria-selected:text-emphasis",
day_disabled: "text-[#4A4E59] dark:text-[#A3A3A3]",
day_range_middle:
"aria-selected:bg-emphasis aria-selected:text-emphasis",
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

day uses buttonClasses({ color: "minimal" }), which includes disabled:opacity-30 (see Button.tsx). Disabled days will therefore still render with reduced opacity even after changing day_disabled to a darker text color, likely reintroducing contrast failures. Consider overriding the disabled opacity for calendar day buttons (e.g., ensure disabled days render at full opacity while keeping a muted color) so the WCAG contrast fix is effective.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@sahitya-chandra sahitya-chandra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr does not follow description template. no before/after video provided

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 bug Something isn't working size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix/wcag-color-contrast

4 participants