Skip to content

refactor!: move date cell semantics to a button inside the cell - #12596

Open
web-padawan wants to merge 1 commit into
mainfrom
refactor/month-calendar-cell-button
Open

refactor!: move date cell semantics to a button inside the cell#12596
web-padawan wants to merge 1 commit into
mainfrom
refactor/month-calendar-cell-button

Conversation

@web-padawan

@web-padawan web-padawan commented Sep 2, 2026

Copy link
Copy Markdown
Member

Description

Part of #12398

Changed DOM structure to use the React Aria Calendar approach.

  • Added <div part="date-button" role="button"> inside <td role="gridcell"> as the focus target
  • Moved the aria-label, aria-disabled and tabindex from <td> element to <div role="button">
    • needed because on iOS VoiceOver reads text content of gridcell and ignores its aria-label.
  • Set role="application" on vaadin-month-calendar to enforce NVDA to switch to focus mode
    • needed as by default, NVDA remains in browse mode, where arrow keys do not move to other dates
  • Button is stretched using CSS to cover the whole cell, so that cell remains a pointer and touch target
  • No whitespace in cell template so blank cells remain empty and the :empty CSS selector applies
  • Updated focus ring to :has(:focus-visible), so it shows on keyboard focus but not after a click
  • Split the calendar getters into focusableDateElement and focusableDateButton for the cell / button
  • Modified test helpers, added getDateButton and getFocusableDateButton for getting focus target
  • Updated tests, added unit tests for tapping the date button and for selection staying on the cell
  • Regenerated the month calendar DOM snapshots to cover the new markup and the application role

Type of change

  • Refactor

Note

Note

NVDA announces a date twice, once for the cell and once for the focused button, because the cell
takes its name from its content. That is inherent to a labelled focusable element inside a
gridcell rather than something this PR can attribute away, and react-spectrum ships the same
behavior.

Warning

Date cell markup changed: aria-label, tabindex and aria-disabled are now on a
button inside the cell rather than on the cell itself. Code that queried a date cell for
any of these needs to be updated accordingly, including Flow component ITs.

Screen readers

With using role="application" on vaadin-month-calendar, arrow key navigation works correctly.

VoiceOver on Safari 26

cell-button-voiceover-safari

NVDA 2026.02

cell-button-nvda

JAWS 2026.2608.25

cell-button-jaws

@web-padawan
web-padawan force-pushed the refactor/month-calendar-cell-button branch 4 times, most recently from 0e47ae1 to bbf6965 Compare September 3, 2026 10:09
@web-padawan
web-padawan force-pushed the refactor/month-calendar-lumo-base-styles branch from 6558e2d to 2dc4f79 Compare September 4, 2026 06:37
@web-padawan
web-padawan force-pushed the refactor/month-calendar-cell-button branch from bbf6965 to 5a5628e Compare September 4, 2026 06:38
@web-padawan
web-padawan force-pushed the refactor/month-calendar-cell-button branch from 5a5628e to 84dba9a Compare September 10, 2026 13:37
@web-padawan
web-padawan force-pushed the refactor/month-calendar-lumo-base-styles branch from 5a24690 to 94a5aa2 Compare September 10, 2026 13:39
@web-padawan
web-padawan force-pushed the refactor/month-calendar-cell-button branch from 84dba9a to 418c26e Compare September 10, 2026 13:40
@DiegoCardoso

Copy link
Copy Markdown
Contributor

I am still understanding how to user iOS VO, so this is what I was able to test so far.

With the current changes, the dates are announced as can be seen on the recording. Disabled dates are announced as "dimmed", which is how it usually announces disabled buttons. As expected, you can't go back from the first day of the month and from the last day, it goes to "Today".

ScreenRecording_09-10-2026.17-00-51_1.mov

@web-padawan
web-padawan force-pushed the refactor/month-calendar-cell-button branch 2 times, most recently from 55dbf8b to ac1f6d4 Compare September 11, 2026 08:44
Base automatically changed from refactor/month-calendar-lumo-base-styles to main September 11, 2026 13:22
iOS VoiceOver reads the text content of a gridcell and ignores its
aria-label, so dates were announced as a bare day number. The accessible
name, ARIA state and tabindex now live on a button inside the cell, the
shape used by react-aria, Duet, Angular Material and Ionic.

NVDA does not enter focus mode for a button the way it does for a grid
cell. Left in browse mode it swallowed the arrow keys, so the roving
tabindex never moved, and it read out every date when the overlay
opened. The month calendar is an application region to force focus mode,
which is what react-aria wraps its calendar in and what MOJ Frontend
adopted for the same problem.

NVDA announces a date twice, once for the cell and once for the focused
button, because the cell takes its name from its content. That is
inherent to a labelled focusable element inside a gridcell, and
react-spectrum ships the same behavior.

This covers only the announcement half of #12398. Months that do not
contain DOM focus still carry aria-hidden, so month traversal with iOS
VoiceOver is unchanged and still needs verification on a device.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@web-padawan
web-padawan force-pushed the refactor/month-calendar-cell-button branch from ac1f6d4 to a7eec4e Compare September 11, 2026 13:33
@sonarqubecloud

Copy link
Copy Markdown

@web-padawan
web-padawan marked this pull request as ready for review September 11, 2026 13:42
@web-padawan
web-padawan requested a review from vursen September 11, 2026 13:42

@vaadin-review-bot vaadin-review-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed the changes — left 1 comment.

Finding
📐 Default role is set unconditionally, so it can't be overridden

Comment thread packages/date-picker/src/vaadin-month-calendar-mixin.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants