This calendar year is almost over and we still don’t have any solid guidance or official ARIA pattern on how to build an interactive and accessible calendar widget. Of course, HTML5 did provide us with input type="date"
which I highly recommend if all you need is a simple datepicker without needing to support IE11. However, if you need to provide specific date formats, complex validations, or mark dates as disabled or holidays, then engineering your own calendar widget is necessary. Just as important as those extra features, custom calendar widgets also need to be accessible and usable.
Notice I mention accessible and usable separately because, to me, accessible only means Assistive Technology (AT) support. It is the difference between providing the bare minimum to get the job done and providing an experience that is seamless. Accessible is a great start, and if that is the most that can be provided then the internet would definitely be a better place. Providing usability is just the perfectly sweetened icing on an already great cake. In this article, I will propose what I think is an improved calendar experience. (Now I want cake!)
While the features that a calendar provide vary depending on the intended purpose, in all cases providing a usable calendar experience can be broken up into 2 simple parts:
- Finding and selecting a date efficiently
- Proper Feedback
The quickest way to allow users to specify a particular date is by not using a calendar at all. A simple text input (<input type="text" />
) is the quickest, easiest, and most accessible method. Just provide the expected format up front, or better yet allow for forgiving date formats, and I promise users will love you for it!
Text inputs are the best choice for entering known dates, like birthdays or credit card expirations, and mostly optimal for keyboard or voice interfaces. Using more complex interfaces for these types of dates are cumbersome. Muscle memory usually takes over and anything more than a simple text field can get in the way.
On the other hand, if users need to be able to compare dates, be aware of holidays or other unavailable dates, or just know what day of the week a particular date falls on, then a full-blown calendar widget would be expected and generally better for pointer inputs like a mouse, touch, or pens. A calendar widget should support all these input modalities.
Getting Around
A good, usable calendar needs to provide a fast and easy way to jump to specific dates in time, and there are different UI elements that can be used to achieve this. The most common elements are buttons and/ or dropdowns that are used to change calendar views and move from month to month or year to year. These elements can be used by keyboard as well as mouse/ touch. Just make sure control target sizes are large enough to easily activate with fine pointer inputs.
Additionally, providing special keyboard shortcuts would also be useful. Some examples would be:
- LEFT/RIGHT for previous/ next day
- UP/DOWN for previous/ next week
- HOME/END to move the first/ last day of the current month
- PAGE UP/PAGE DOWN to move to the same day in the previous/ next month
In order for these keyboard shortcuts to work properly with AT, you’ll need to add role="application"
to the calendar so that keyboard events are not consumed by AT and instead passed through to the page for the JavaScript (JS) to receive.
Danger
role="application"
is serious business and should rarely ever be used, only if it is absolutely necessary and you know what you are doing before using it.
For custom keyboard shortcuts, any combination of keys can be used as long as instructions are provided. Using arrow keys to navigate the dates may be intuitive to most, but any other shortcuts may not be easily discoverable and are invisible without some kind of notice. You might as well save some keystrokes and testing by not providing shortcuts if you also don’t provide information on how to use them. There are many different ways to provide instructions, it could be as simple as text above the calendar or it could be provided via a popup or overlay of some kind. The key (maybe a pun?) is to provide them and make sure they are available up front for users to find.
Tip
Try to avoid single-character shortcuts, like letters, since users of assistive technologies, like screen readers or speech input, may inadvertently invoke actions that are unintended. For example, an action tied to the letter B may conflict with a screen readers ability to navigate buttons. Speech users may accidentally invoke an action tied to the letter J simply by saying something like the name “James.”
When it comes to navigating around a calendar, there is one mistake a lot of calendars make that adds a significant amount of effort for AT; TABLES! Table semantics add extra information that is important to understand a table, but just not necessary for a calendar. As a user is moving around a calendar built with a table, row and column information is announced along with the date, e.g. “Column 2, Row 2.” If column headings are used to display day names, those are announced too. Chances are that day names are abbreviated, like T or TUE for Tuesday, etc. This makes finding a date even more cumbersome. Putting it all together in an example, using a table to find Tuesday the 2nd, which happens to land in the second column of the second row, could announce as “TUE column 2 row 2 2.” Too much!
In order to avoid the extraneous information that table semantics, some calendar implementations remove native semantics by adding role="presentation"
to all the table elements. I suppose that is better than not, but when I consider the amount of overhead required to use a table, e.g. extra ARIA to remove table semantics, bulky markup, and CSS to fight table styles, it is a lot of extra code for a diminished experience. If you use a table and remove almost everything that makes it a table (by usingrole="presentation"
), you really didn’t want a table. I’d rather just start from scratch with something more lean. Less code equals smaller surface area for bugs and better performance, and that means greater usability!
I can see how using table to lay out the grid of dates makes a lot of sense for presentation, but I recommend using flexbox and/ or CSS grid as a better way to achieve the same results. Using better CSS will help create a responsive calendar and improve usability for users with low-vision that need to be able to zoom up to 400%.
Making a Selection
To provide date selection, it’s important to choose an element that has the proper semantics to communicate interactivity as well as various states, such as selection. I choose to use a simple <button>
, which is natively focusable, is an easy target for event handling (click, keypress, touch), and supports aria-pressed
making the button a toggle to communicate selected state for AT. Even though it may sound more appropriate, aria-selected
is only supported on a few choice elements, and a <button>
is not one of them. I also prefer to use ARIA states as CSS selectors, so using [aria-pressed="true"]
as a CSS selector makes it easy to style. Using ARIA states for styling is a win-win because you only have to update one property, and broken styles are a quick cue that your accessibility is also broken.
Make it Human
With all the proper elements and behaviors covered, the last step is make sure that the calendar sounds human. This is all about providing great UI labels and feedback/ confirmation. Labels are important because they help describe what something is, and provide a cue to as to what is to be the expected interaction. Feedback provides confirmation to users that their intended actions have taken place. Everything gets tied together with easy, conversational language.
Labels
All interactive elements need to have accessible labels. It is great to be succinct, but not at the expense of leaving out key information. For example, the controls for moving from month to month are usually just labeled as “Previous” or “Next” and are barely OK as a minimum. A good improvement would be to include the expected result of interaction. In this case, adding the month that a user is moving to could be added, e.g. “Previous month, November 2018” and “Next month, January 2019” would be great. An AT user would not have to check other parts of the calendar to figure out which month/ year they landed on.
<!-- Control to move to previous month -->
<button class="Calendar-navigation Calendar-navigation--previous" type="button"
aria-label="Previous month, November 2018">❮</button>
<!-- Control to move to next month -->
<button class="Calendar-navigation Calendar-navigation--next" type="button"
aria-label="Next month, January 2019">❯</button>
Labels for calendar dates should include the entire date in a human readable format. Instead of just “11” or “Th 11” date labels should include the day and date as “11, Thursday October 2018” for a complete, descriptive label. If there is something special about the date, include that in the label as well. For example, the label for Global Accessibility Awareness Day would be “16, Thursday May 2019, Global Accessibility Awareness Day.” Remember that date!
Note
Commas are announced by AT as you would read out loud, meaning they create a brief pause. The comma placement I am demonstrating is intentional and helps with ensuring that dates are not rattled off as long, incoherent strings.
In addition to descriptive labels, using the aria-pressed
state will give users a complete picture. In the case of an unavailable date, including the reason the date is unavailable could also be helpful. As an example, “1, Tuesday January 2019, New Year’s Day, Office closed” could be used./p>
<!-- Default calendar date control -->
<button class="Calendar-date" type="button" aria-pressed="false"
aria-label="8, Saturday December 2018">8</button>
<!-- Today -->
<button class="Calendar-date" type="button" aria-pressed="false"
ria-current=”date” aria-label="9, Sunday December 2018, Today">9</button>
<!-- Selected calendar date control -->
<button class="Calendar-date" type="button" aria-pressed="true"
aria-label="11, Tuesday December 2018">11</button>
<!-- Holiday calendar date control -->
<button class="Calendar-date Calendar-date--holiday" type="button" aria-pressed="false"
aria-label="12, Wednesday December 2018, Dee Dee's birthday">12</button>
Now, some might be looking at those labels and saying “That’s really long!” and you would be right. This is why the date and day is first in the label. An AT user can choose to skip the rest of the label if they are not interested in hearing the whole thing. Unlike the added table semantics, at least this is information that is relevant to the date. An added benefit of having the date at the start of the label is that it will help with voice input users.
Tip
Be careful when setting accessible labels that are not the same as the visual label. Make sure that the visible label is at the start of the accessible name. This will ensure that speech input users are able to access and activate the proper controls.
Feedback
The last step in my usable calendar is providing proper feedback. There is a lot of interactivity with visual cues, micro-animations, and changes that are almost always never properly communicated to non-sighted users. Taking care to announce these events is a small improvement but could have a significant impact on the overall comprehension and usage of calendars. With this in mind, there are two small notifications that I like to announce.
The first notification is used when changing calendar views. Using a control to move to the previous/ next month or year would announce the change. For example, moving from December to January would announce “January 2019” to confirm the update.
The second notification is actually pretty simple. Providing a notification of a selected date as “October 11 2018, selected” is all you need do. That’s it!
The feedback itself is provided by injecting messages into a live region with role="alert"
that is visually hidden. Visually hidden means that content is in the DOM but not visible on screen. It is a very effective way of providing additional information specifically for AT. However, since it is rendered to the DOM and discoverable, I like to remove it soon after it is placed with a simple utility function.
/* CSS */
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}
<!-- HTML -->
<span id="announcement" class="visually-hidden" role="alert"> </span>
// JS
const announcement = document.getElementById('announcement');
// fast way to update text
function setText(node, text) {
let child = node.firstChild;
if (child && !child.nextSibling && child.nodeType === 3) {
child.data = text;
} else {
node.textContent = text;
}
}
// announce content to assistive technologies
function announce(content) {
setText(announcement, content);
setTimeout(function () {
setText(announcement, '');
}, 1000);
}
Providing these extra notifications might feel a little verbose, but without them a user needs to do a some extra work verify an action. Not requiring this extra effort is the difference between accessible vs usable.
Demo
So here is the day you have been waiting for, an accessible and usable calendar widget!
You can check out a live demo, that incorporates everything I have mentioned here, and some additional improvements that you should take a look at.
Summary
Calendar widgets are pretty ubiquitous around the internet, and that means a lot of people use them every day. As complex as they are to build, it doesn’t take much to make them accessible and usable. Using the right elements and states, accounting for different modes of interaction/ input, using human language to label interactive controls, and providing a little extra feedback will make choosing a date a better experience for everyone.
Here’s looking to better calendars in the new year!
Dan says:
Hey Gerard,
Thanks for a great article! Our team valued the way you’ve matched theory to practice in your examples.
We had a question around best-practice for integrating a datepicker into a larger form, with consideration to customers using assistive technologies.
Where we currently use a Semantic UI x React custom component (which isn’t a great accessible experience), it appears as a pop-over.
If we were to implement a new component based on your article, what factors would you consider when deciding whether to make the datepicker a modal, a pop-over, or some kind of slide-out panel?
Gerard Cohen says:
Hi Dan, I am glad you enjoyed the article and examples!
In general, I would suggest that you keep context intact by ensuring that the popover you choose is close to the appropriate date controls. For this purpose, a large modal dialog or slide-out panel would not be required, but instead a pop-over/ overlay sized just enough to contain the calendar itself. This will make it easier to keep everything in context.
Additionally, make sure you are maintain good keyboard focus and keep tab order in check.
Let me know if you need anything else.
Aaron Farber says:
I’ve used Airbnb’s React calendar component and it worked pretty well out of the box. I’d check it out. https://github.com/airbnb/react-dates
Gerard Cohen says:
Hi Aaron!
I haven’t checked out the Airbnb calendar yet, but I will certainly take a look.
Thanks for reading and leaving a comment.
Adrian Roselli says:
I am struggling with your advice against tables. It might stem from the fact that I wholeheartedly disagree with your opening assertion that “accessible only means Assistive Technology (AT) support.” After all, a keyboard is not AT. Nor is zooming text in my browser.
From my own user testing, screen reader users are generally not hampered by the way AT announces tables. Generally they rely on it for wayfinding. As column/row data generally comes after the content is announced, then can skip it anyway.
Citing poor column names and improper use of the ARIA presentation role are a failing of broken implementations but not tables themselves. That’s a bit of a red herring.
Given that you generally do not reflow a calendar grid (you might scroll the container) in a narrow/zoomed viewport as it impacts visual comprehension, suggesting that grid or flex is a better approach does not seem to apply here.
Using a native HTML table is the best baseline choice of a progressively-enhanced experience and is resilient to network failures for styles and script (for viewing and reading).
Separately, your tip about using single-key shortcuts is good. It is also the easiest way to meet WCAG SC 2.1.4, Character Key Shortcuts (level A).
As to your use of aria-label, we know that auto-translation tools will not translate text in aria-label. I try to use aria-labelledby and point to IDs of visible text on the page that will get translated. It also means less risk of my text strings falling out of sync and is easier for sighted developers to test.
Gerard Cohen says:
Hi Adrian,
I can understand your disagreement with my statement regarding Assistive Technology support. Would it be better if I explained that I am not only considering screen readers or braille output in my definition of AT? I most certainly would consider keyboard and browser zoom as an assistive technology, but being able to tab to everything does not equate to usability all the time. At the same time, I do point out that if we could always provide good assistive technology support at a minimum, the internet would be a much better place.
From my own user testing, screen reader users immediately noticed and thanked me for not using a table. As the saying goes, know your users!
You make an excellent point about aria-labelledby vs aria-label, and in cases where visible text is available it would definitely be my preference.
Adrian, I sincerely appreciate you taking time to read and comment on this article. Thank you!
Adrian Roselli says:
I am guessing the users in your testing probably only had experience with the terrible tables you cite in your post (maybe in awful calendar control implementations). It may be worth running them through properly-coded tables in future tests. I would be interested in hearing their feedback after such a test. If they do not panic, I suspect the benefits to using proper tables outweigh their prior frustrations.