[scheduler] Create accessibility documentation section#22557
[scheduler] Create accessibility documentation section#22557mustafajw07 wants to merge 15 commits into
Conversation
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
|
Thanks for opening this — really appreciate you tackling the a11y docs! 🙌 I went through the doc against the current code and against the other MUI X a11y pages (Tree View, Data Grid) — sharing a first batch of things to revisit. Before the next push, could you do an audit pass against the shipped behavior? A few claims in the doc don't quite match what the code does today. A couple of things that aren't quite accurate1. Arrow key navigation actually works in Week, Day, and Month views. All three implement arrow nav between grid cells via the shared
There's a full test suite at 2. The Sections that could use more love3. Keyboard interactions doesn't cover the calendar grid yet — which is a big part of what users will look for here. Worth adding a section for it that covers:
4. The Resources sidebar table is a bit thin — since it's a Small consistency tweaks with the other a11y pages
Happy to chat through any of these if something's unclear — and thanks again for picking this up! 💛 |
…ard navigation details
Thanks for the review. |
mj12albert
left a comment
There was a problem hiding this comment.
Noticed a few inconsistencies between the doc and the implementation (possibly some are bugs?) CC @rita-codes
| When a month cell has more events than can be displayed, a **"X more"** button opens a popover listing all events for that day. | ||
|
|
||
| - The popover header element carries an `aria-label` with the full formatted date (for example, `"Monday, May 26"`). | ||
| - Each event inside the popover uses `aria-labelledby` that composes the popover header ID and the event title element ID, so screen readers announce the day context alongside the event title. |
There was a problem hiding this comment.
Noticed this doesn't match the implementation:
(Could be a bug, one of those don't have a schedulerId)
There was a problem hiding this comment.
Good catch — there was an inconsistency between the generated header ID and the referenced aria-labelledby value. Updated the implementation so both now use the same ID format.
|
|
||
| ## Localization of ARIA labels | ||
|
|
||
| All user-facing accessible labels are provided through the `localeText` prop and can be customized. |
There was a problem hiding this comment.
Some are not yet exposed, e.g.
There was a problem hiding this comment.
You're right — updated the documentation wording.
There was a problem hiding this comment.
IMO the right thing to do is actually expose all user-facing text labels
There was a problem hiding this comment.
Hey @mj12albert , I noticed that ViewSwitcher.tsx has aria-label="Switch View" hardcoded (line 67). I wanted to flag two things:
-
It's not localized. The button's visible text
{localeText[view]}(for example, "Week", "Month") is already going throughlocaleText, but thearia-labeloverrides that for screen readers with a hardcoded English string. -
Is it even needed? When a button has both visible text and an
aria-label, thearia-labeltakes precedence and suppresses the visible text for screen readers. Since{localeText[view]}already communicates the current view ("Week", "Month", etc.) and the button also getsaria-expandedfrom the menu pattern, the hardcodedaria-labelis actually making things worse — a French user would hear "Switch View" instead of "Mois".
Should we just remove it and let the button's localized visible text do the job? Or if we want to keep a label like "Switch View", it should go through localeText so it can be translated. Happy to do either — just wanted your input before changing it.
There was a problem hiding this comment.
Not specifically calling out "Switch View" here, but it's just one example of a non-exposed aria-label I noticed
There was a problem hiding this comment.
I believe all such cases have already been covered. Could you point out any specific aria-labels that you think are still missing so I can verify them?
There was a problem hiding this comment.
This for example:
There was a problem hiding this comment.
This case is already covered. I added the corresponding label on line 182.
| ## Live region announcements | ||
|
|
||
| The current date range label in the header toolbar is wrapped in an `aria-live="polite"` region. | ||
| When a user navigates to the previous or next time span (for example, clicking **Previous week**), the updated date range is announced automatically by screen readers without requiring focus to move. |
There was a problem hiding this comment.
This region doesn't seem to contain the "updated date range":
There was a problem hiding this comment.
Updated the wording to reflect the actual toolbar label behavior.
|
|
||
| The Event Dialog is a non-modal dialog (`aria-modal="false"`) that floats next to the event that opened it. | ||
|
|
||
| - It is announced by screen readers via `aria-labelledby` pointing to the dialog title. |
There was a problem hiding this comment.
This doesn't account for read-only dialogs which have a different id:
There was a problem hiding this comment.
Good catch — updated the documentation to account for the separate read-only dialog title ID as well.
There was a problem hiding this comment.
Why are the ids different in the first place? Is there any value to readers by telling them the exact id is "${schedulerId}-draggable-dialog-title"?
There was a problem hiding this comment.
Quick question on the event dialog title IDs: ReadonlyContent uses draggable-dialog-title and FormContent uses event-dialog-title. The "draggable" prefix feels like an implementation detail leaking into a semantic identifier — assistive tech only cares that aria-labelledby points to the title, not what the ID is called.
Should we normalize both to event-dialog-title for consistency? Since the two dialogs are never rendered at the same time there's no collision risk.
Also, the a11y docs currently expose the exact ID strings. I think that's more internal code detail than useful guidance — I'd suggest replacing it with a simple description like "The dialog is labelled by the event title via aria-labelledby." Does that sound right to you?
| The grid body is wrapped in a `role="rowgroup"` element. Day cells use `role="gridcell"` and carry: | ||
|
|
||
| - `aria-label` — the full formatted date string | ||
| - `aria-current="date"` — on today's date | ||
| - `aria-selected` — on the currently active (selected) date | ||
| - A roving `tabIndex` — `0` on the active cell, `-1` on all others |
There was a problem hiding this comment.
This seems to imply that Day cells apply all of these to one element, when in fact they are split between a button with a wrapping div:
mui-x/packages/x-scheduler/src/event-calendar/mini-calendar/MiniCalendar.tsx
Lines 276 to 293 in 1de8ee8
There was a problem hiding this comment.
Updated the description to clarify that the grid structure and interactive accessibility attributes are split between the wrapper gridcell and the inner button element.
There was a problem hiding this comment.
Just wondering did you test how the current markup structure is actually announced? (e.g. by VoiceOver)
There was a problem hiding this comment.
Yes i have tested those out.
There was a problem hiding this comment.
How is it currently announced?
There was a problem hiding this comment.
During my testing, the announcement was consistent with the current markup structure and the accessibility attributes exposed by both elements.
|
@rita-codes / @mj12albert Thanks. |
|
Hi @mj12albert, just a friendly follow-up on this PR when you have a chance. Please let me know if any changes are needed. Thanks! |
|
Hey @rita-codes, can I get a review on this pr? Thanks. |
|
Thanks for the patience on this — the doc is in really good shape now. 🙌 I did a fresh audit pass against the shipped code and most of it checks out: the grid roles, A few things are still open: 1. The color-option label still isn't exposed (re: @mj12albert's "expose all user-facing labels" thread). So a non-English user still hears "Select green as event color". To close @mj12albert's point we'd need to add a real 2. Read-only Event Dialog So in read-only mode the label target doesn't exist and the dialog ends up unlabelled. Your suggestion of normalizing both to 3. Same bug class as the popover fix, but in the Agenda view. So Agenda events don't announce the day context the doc describes. Since you already fixed the exact same thing in 4. The page is "Scheduler accessibility", but it only covers the Event Calendar — the Event Timeline isn't documented at all.
Since that's a fair bit of new ground, maybe we keep it out of this PR and do the Timeline section as a follow-up — that way this one stays focused and doesn't grow too big. Up to you! 5. Minor — @mj12albert's VoiceOver question on the mini-calendar markup is still open. Could you drop the actual announcement string you got when testing the split None of these are blockers on the writing itself — the doc reads great. Thanks again for sticking with it! 💛 |
…een reader support
|
Thanks for the detailed review. I've addressed the remaining items and updated the PR: Added and wired up the localization support for the color selection aria-label and reviewed the remaining user-facing labels. For the Event Timeline accessibility documentation, I agree it's substantial enough to deserve its own follow-up. I'd prefer to keep this PR focused on the Event Calendar accessibility documentation and handle the Timeline accessibility surface in a separate issue/PR. |
|
Regarding the VoiceOver question: I retested the Mini Calendar markup with VoiceOver. When focusing a regular day in the Mini Calendar, VoiceOver announces something similar to: "Tuesday, July 2, 2025, button, calendar table, 7 columns, 7 rows" For today's date, it additionally announces: "Current date" along with the date information. |
Closes #21463
Summary
Changelog