We need to take care of many things to make the Datepicker keyboard accessible. They are:
Showing the Datepicker
Tabbing into/out of the Datepicker
Keyboard shortcuts inside the Datepicker
Weâll work on the first two things in this lesson.
Showing the Datepicker
We showed the Datepicker when a user clicks on the <input>. But we also want to show the Datepicker when the user Tabs into the <input>. We can do this by changing the click event to focus.
This is okay because a click creates focus on the <input>.
We want to intercept the browserâs default behaviour when the user presses the Tab key. But we donât want to do anything if they press Shift + Tab.
input.addEventListener('keydown', event => {
const { key } = event
if (key !== 'Tab') return
if (event.shiftKey) return
event.preventDefault()
})
If they press Tab, we want to focus on the first focusable element in the Datepicker. This would be the previous monthâs button.
Notice the input becomes blueish-grey when you Tab into the Datepicker? This signifies the focus on the input was lost. (Which is true, because focus is now on the previous monthâs button).
But the user is using the Datepicker (which is linked to the input). They expect the Datepicker to be part of the input. So the input should still be âfocusedâ.
We can retain the focused state (the white background) by adding a custom attribute called data-state. Weâll set data-state to focus. (You can use a class if you prefer).
The necessary CSS to activate this state has already been done for you.
If the user closes the Datepicker, we want to remove âfocusâ from the input. To do this, we can delete the data-state attribute.
// Hides the Datepicker
document.addEventListener('click', event => {
if (event.target.closest('.datepicker')) return
if (event.target.closest('input') === input) return
datepicker.setAttribute('hidden', true)
delete input.dataset.state
})
Tabbing out of the Datepicker
It would be a chore for users to Tab through all date buttons before getting out of the Datepicker.
A better way is to let users Tab into ONE date button. Then, their next Tab takes them out of the Datepicker. (We can let them navigate through the rest of the dates with arrow keys later).
To do this, we need to set all buttonâs (except the first) tabindex to -1.
const createDateGridHTML = date => {
// ...
for (let day = 1; day <= daysInMonth; day++) {
if (day === 1) button.style.setProperty('--firstDayOfMonth', firstDayOfMonth + 1)
if (day !== 1) button.setAttribute('tabindex', '-1')
// ...
}
}
When a user clicks on a button, we want to allow users to Tab back into the button. We can do this by changing up the tabindex attributes.
When a user Tabs out of the Datepicker, we want to direct the user to the next focusable element (after the input). In this case, the focusable element is the link we placed in the HTML.
datepicker.addEventListener('keydown', event => {
// ...
const focusableElements = getFocusableElements()
const index = focusableElements.findIndex(element => element === input)
})
And we focus on the next element after the input. Remember to prevent the default Tab behaviour here. Otherwise youâll focus on the 2nd element after the input.
After the user Tabs out of the Datepicker, we want to close the Datepicker. We can do it by setting the hidden attribute. We also need to remove the data-state custom attribute from the <input>