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>
title
attribute). It's not custom html to be appended inside the html elementdateString + '" ' + "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...