0

I have the following datepicker. The problem is that when running screen reader (NVDA), when moving the selection on the datepicker grid to different cells using keyboard, the screen reader just says "blank". (However, if I hover over the cells with mouse, it does read the number of the day correctly.)

$(document).ready(function () {
    $('#ui-datepicker-div').addClass("month-calendar-widget");
    $(".month-calendar input").datepicker({
        showOtherMonths: true,
        minDate: 0,
        beforeShow: function () {
          $('#ui-datepicker-div').addClass("month-calendar-widget");
        }
    }).datepicker("setDate", convertDateToUTC(new Date()));
});

What I was hoping was to fix this issue by adding aria-label attribute to each cell of the grid using beforeShowDay like below:

  beforeShowDay: function(date) {
    var dateString = $.datepicker.formatDate('MM d, yy', date);
    return [true, '', dateString + "" + 'aria-label="' + dateString + '" role="gridcell" aria-selected="false"'];
  }

However, this results in the follwing html which is wrong:

<td class=" " data-event="click" data-handler="selectDay" data-month="1" data-year="2024" title="February 16, 2024aria-label=&quot;February 16, 2024&quot; role=&quot;gridcell&quot; aria-selected=&quot;false&quot;">
    <a class="ui-state-default" href="#">16</a>
</td>

What am I missing?

Update 1:

To further clarify, here is my goal:

  • Run NVDA (just google nvda download and download and run it)
  • Navigate to, for example, the datepicker Diego provided in his answer below, then using keyboard move to different cells in the grid: enter image description here
  • I am expecting NVDA say the date, but it just says "blank".

update 2

Use this jsfiddle to play with Diego's answer

update 3

After further investigation, I think there is a fundamental issue with regard to accessibility of this JQuery datepicker: basically, imagine you cannot visually see the page and you tab into that field. Now there are two simultaneous options to enter the date: both by typing and moving across the datepicker grid. This is also probably why the screen-reader is confused too. I think the two options should have separate triggers: i.e. we have a text box where user can enter text, AND a button that is when clicked, opens the date picker where the user can now select a date. I looked some other implementations of the date-picker and they all seem to have implemented it this way. I also found this github issue for the same: https://github.com/jquery/jquery-ui/issues/2155

2
  • 1
    I'm not still giving a solution but just hinting that beforeShowDay expects its 3rd arg to be an optional popup tooltip for this date (that will be rendered as the value of the title attribute). It's not custom html to be appended inside the html element
    – Diego D
    Commented Feb 8 at 22:47
  • 1
    You could try using dateString + '" ' + "aria-label='... so that it ends the title attribute and starts a new one, and it will likely escape the quote marks, so you'd probably need to use single quotes (') around the aria attribute values, but you're playing with fire... Commented Feb 8 at 23:00

1 Answer 1

1

Since beforeShowDay supports only creating the value of the title attribute, and since the trick of closing the quote to add your further attribute may not be enough because it's addressing a td and not the interactive element, you may just use a different strategy to populate the aria-label attribute on the actual anchor.

You can rely on the option beforeShow that will run the function given just one moment before the datepicker is actually shown:

https://api.jqueryui.com/datepicker/#option-beforeShow

beforeShow Type: Function( Element input, Object inst )

A function that takes an input field and current datepicker instance and returns an options object to update the datepicker with. It is called just before the datepicker is displayed.

Such function will iterate over each interactive element inside the calendar containing every single day the user can pick (anchor element) and will add an aria-label attribute to it with the value coming from the title attribute on its parent cell (set by beforeShowDay as you did).

This is an example of a day cell being processed:

<td
  class="ui-datepicker-days-cell-over ui-datepicker-current-day ui-datepicker-today"
  title="February 9, 2024"
  data-handler="selectDay"
  data-event="click"
  data-month="1"
  data-year="2024">
    <a
      class="ui-state-default ui-state-highlight ui-state-active" href="#"
      aria-label="February 9, 2024">9</a>
</td>

There was the option of crafting the date by picking its parts from each component like this, but it was overkill:

const year = parentCell.attr('data-year');
const month = parentCell.attr('data-month');
const day = $(this).text();

const paddedMonth =
  (parseInt(month, 10) + 1).toString().padStart(2, '0'); 
const paddedDay = day.padStart(2, '0'); 

//previously grab day info and format the correponding date
const formattedDate = `${paddedDay}/${paddedMonth}/${year}`;

//populate the day anchor with the aria-label containing dd/mm/yyyy
$(this).attr('aria-label', formattedDate);

As a further action I also dealt with the onSelect event that will add the aria-label attribute also to the input element once a date was selected inside the datepicker. It could be not needed since probably screen readers already can read the value of an input element.

Edit: as you found out with a library specifically designed to solve this particular problem, the datepicker doesn't move the focus while navigating over the calendar and it instead remains locked on the input field. That's why despite the 'aria-label` was set, it couldn't be read if the screenreader couldn't see the focus moving.

So the solution I provided here, so far, cannot fully address the scenario because it's missing the logics to deal with the focus.

That library I see instead is using a strategy based on the tab-index attribute and aria-select correctly achieving that goal.

I also used the option showOn as button so that the datepicker won't show up when the text input gained focus but only when the dedicated button is clicked.

https://api.jqueryui.com/datepicker/#option-showOn

When the datepicker should appear. The datepicker can appear when the field receives focus ("focus"), when a button is clicked ("button"), or when either event occurs ("both").

$(document).ready(function () {

  /*
  $("#pickdate").click(function(){
    $(".month-calendar input").datepicker("show");
  });
  */

  $(".month-calendar input").datepicker({
    showOtherMonths: true,
    minDate: 0,
    //prevent from showing on focus and use the default button instead
    //if you wish instead a custom button, uncomment both html and js related
    //and here use the option "none"
    showOn: "button",
    
    //on date selected
    onSelect: function(dateText, inst) {
      const value = "Selected date: " + dateText;
      //set the aria-label of the input element with the date picked
      $(this).attr("aria-label", value);
    },
    
    //before showing each day in the datepicker grid
    beforeShowDay: function(date) {
      //return a value used to populate the title attr of the day td
      const dateString = $.datepicker.formatDate('MM d, yy', date);
      return [true, '', dateString];
    },
    
    //before showing the days grid
    beforeShow: function(input, inst){
      //set timeout is to wait for the ui to be rendered
      setTimeout(function() {
        //select the calendar element
        const daysGrid = $('#ui-datepicker-div .ui-datepicker-calendar');
        //fetch all the anchors being interactive days
        const interactiveDays = $(daysGrid).find('td a');
        
        //for each one of them
        interactiveDays.each(function(){
          const parentCell = $(this).closest('td');                                    
          //set the aria-label as the same value on the title attr of its parent
          $(this).attr('aria-label', $(parentCell).attr('title'));
          
          //...other aria attributes could be set at this point...
        });
      }); 
    }

  }).datepicker("setDate", new Date());
});
button{
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<div class="month-calendar">
  <input type="text" id="datepicker">
  <!--
  <button id="pickdate">datepicker</button>
  -->
</div>

14
  • A good strategy, although it would need to be tested to see if the attribute is set before the screen reader reads the element's attributes. Commented Feb 8 at 23:17
  • @diego-d are you sure this adds the attribute? I am looking at the td-elements on this page for that datepicker and I don't see the attributes being added. also screen reader still doesn't recognize it.
    – havij
    Commented Feb 8 at 23:29
  • @HereticMonkey yes I agree but all I could do was following the line I was hoping was to fix this issue by adding aria-label attribute. And I still believe it's a good lead since the aria-label attribute can be used to define a string that labels the interactive element on which it is set.
    – Diego D
    Commented Feb 8 at 23:54
  • @havij my code is actually adding the aria attribute to the input element originating the datepicker ($(".month-calendar input")datepicker(...)) . I could guess a td was involved watching at the html you shared where the title attribute was being set by beforeShowDay but it didn't make sense because your datepicker was bound as well as mine on the input element and how could that callback address any td anyhow? maybe if you update the question with more details about how is your html containing the input element and how it's related to any td, I can understand better
    – Diego D
    Commented Feb 8 at 23:58
  • but yet you should understand that as I desribed earlier in my previous comment, the aria-label is expected to be added to interactive elements (such input) and not a td. So the scenario you described is still unclear. Plus what do you mean that the screen reader doesn't read it? did you focus on the input element? that's what's holding the value and that you wish to read.
    – Diego D
    Commented Feb 8 at 23:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.