After the University's recent responsive redesign, the team was asked to add a feedback mechanism to the site, presumably to accommodate the predicted flood of users eager to congratulate us on how awesome everything looks.

Or how they can't find anything now because it's all changed.

Either way, having investigated taken-for-granted UI elements in the run-up to the responsive project, I was keen to continue nudging us away from the usual rushed approach of slapping the first Google result into the website with little thought given to anything other than how it looked. Creating an inclusive modal dialog sounded like it could be an interesting challenge.

Progressive Enhancement

Although some of my colleagues remain somewhat skeptical, the recent responsive redesign clearly showed that progressively enhancing the features we were building was the simplest way to maintain inclusivity while still allowing for the delightful aspects that were deemed so important.

Despite their misgivings, I was determined that developing this feedback mechanism should continue in the same spirit.

Requirements Analysis

I set myself the task of creating a feedback form which would be displayed on all pages of the site, preferrably within a modal dialog, and which would be as robust and accessible as I could make it.

Before diving into the code, I did some research to get an idea of the challenges ahead, especially the W3C standards I should be aiming for.

That investigation led me to this example - https://github.com/w3c/aria-practices/tree/master/examples/dialog-modal - which seems complicated, and probably for good reason. Much of the JavaScript deals with handling focus, and that example appears to be flexible enough to cope with multiple dialogs opening concurrently.

On the assumption that the content inside the modal wasn't going to be wild with dynamic interactivity - in other words, just a form with some text - I could hopefully simplify a great deal.

I began by sketching down the requirements as I found them.

HTML

The form would be included on each page, and since it's inviting feedback about the page it should probably come after the main content.

The form fields would consist of:

All fields would be optional; submitting a blank form should do nothing.

The email field could be given the HTML5 email type; although implementation in browsers varies, it's part of the standard. Any showstoppers would be revealed during testing, and it could always be made a vanilla text input.

As with all basic forms, it would post the submitted content to a server-side controller.

The modal "trigger" would be an in-page link to the form. In order for the focus to jump to the form properly and not just look like it has (looking at you, Internet Explorer) I'd need to add tabindex="-1" to the form element with the relevant ID.

Additionally, since the form would be at the bottom of the page we wouldn't want keyboard users to have to tab through the entire page to get to it. Similarly, the modal trigger link was unlikely to be the first link on the page, so adding another in-page link to the form within the accessibility skip links sounded appropriate.

If we were to stop at this point, we'd have a perfectly usable feedback mechanism, even though it has some rough usability edges that could be smoothed over.

CSS

The modal trigger would be somewhere within the code flow; at the top of the page content, or between the page content and the sectional navigation, or after the navigation just above the form itself. Since the urge to comment could strike a user at any moment, I thought that wherever the link appeared within the code flow it would be nice if it was always visible, perhaps as a fixed element stuck to the side of the browser window.

That might cause issues at smaller viewport sizes, where the fixed trigger could obscure some of the content, or even be mistaken for native navigation - some devices have a floating tab on the screen that hides the menu. A breakpoint would be needed to stick the modal trigger only at larger viewport sizes.

Naturally the form would need some styling. I especially like adding focus outlines to form elements: there's something satisfying about typing outline: 0.2em solid gold;.

JS

If we were to stop at this point, we'd have a basic, accessible, pleasantly-styled and (network permitting) robust foundation in place.

The rest of the magic would be up to JavaScript.

By default, the form would be visible at the bottom of the page. The idea was to hide the form, and only reveal it when the modal trigger was interacted with. That meant using JS to add a hidden class to the form, and an event handler to the modal trigger which would remove the hidden class when activated.

At this point, we'd have a clean page of content, with a Feedback trigger. On activating the trigger, the form would be shown and focused.

If the feedback form code came after the page content, and the user jumped down to the form but decided not to leave feedback after all, they'd have to scroll back to where they were, which on long pages could get frustrating. I suppose this is why modal dialogs are so useful.

Displaying a modal dialog usually involves adding an overlay to obscure the page contents behind the dialog, and then positioning the dialog in the middle of the viewport. As it turned out, this is a wee bit tricky...

A potential solution was found here - http://demo.chobits.ch/CSS3/modal.html - which also features some nice use of the `:target` selector. It creates a fixed element covering the whole browser window, with an inner content area that scrolls on overflow. Neat.

Making it behave like a dialog

The WAI ARIA guidelines are written pretty clearly, so the requirements to ensure a modal dialog remains inclusive sound relatively straightforward:

The guidelines also suggest the various ARIA roles and attributes to add. By default, role="dialog" seems to put NVDA into application mode, but the feedback modal dialog is likely to contain browsable content. To solve this, the outer modal structure gets the dialog role, while the inner structure is assigned role="document".

Trapping Focus

If you're framing content within a modal setting, chances are the content is associated with a specific task, distinct from the rest of the page content. Visual clues are provided to reinforce this, like covering the whole screen in a semi-transparent block to obscure the main page content, and then adding the modal dialog on top. However, not all users will benefit from visual clues.

The desired behaviour is that once a keyboard user gets into the modal area, only elements within that modal should be focusable. Once the user is finished interacting with the modal content, they can dismiss it and return to the main page content - preferrably picking up where they left off.

This is important for sighted keyboard users too: if focus is allowed to "leak" out of the modal without removing it, the user cannot interact with the main page content because it's visually obscured by the modal stuff.

It's simpler to keep the focus within the modal area until the user decides otherwise. It seems the way to do that involves detecting the first and last focusable elements within the content, and applying the following logic using event handlers:

That presents a challenge: if the modal can contain any kind of content, how do we determine what's focusable?

I delved into the example in the W3C ARIA Practices repo, which seemed to rely on a separate utility library for detecting elements, which I wanted to avoid if possible.

I tried sidestepping the issue by creating focusable "buffers" at the top and bottom of the modal. When focused, they mimicked the logic described above, simply focusing the opposite element.

It worked pretty well, apart from the fact that these two elements were announced to screen readers: bottom of modal and top of modal, and needed a visible focus style for sighted keyboard users. A bit clunky.

Then I sees this tweet from Heydon:

His code snippet neatly detects a variety of focusable elements based on a list you define, and declares them as an array, making it effortless to pick the first and last of them. Incorporating that into my code meant I could remove the buffers and associated clunkiness.

Finishing Touches

If we were to stop at this point, we'd have a pleasantly-styled and reasonably accessible feedback form in a modal dialog.

Submitting the form would navigate away from the page, and although the server-side controller could probably be smart enough to return the user to the same page, they'd have lost their place. It would be nice if the form contents could be sent via AJaX, so the user could continue from where they were.

This would require some UI shennanigans, for example:

Naturally any status updates would need to be available to all users.

Putting it all Together

Having scoped the requirements in some detail, I started the build exactly as described. The result is on this very page.

Remaining Issues

Scrolling with a mouse wheel still scrolls the page underneath the modal, which could potentially disorientate a user when returning to the main page content. Having the modal trigger in a fixed place within the content instead of glued to the side of the browser would solve that. I've seen other modal implementations attempt to disable scrolling, but I don't think I want to take it that far.

It's still possible to sneak a screen reader out of the modal dialog. For example, by moving the virtual cursor up to the feedback form legend, and then shift+tabbing, the focus jumps to the previous element outside the modal. That's testing with NVDA - I'm not sure what JAWS would do.

I could go nuts and use a Service Worker to cache the feedback for later, and send it only when the network conditions are favourable. So many opportunities to make stuff better, but I left it there, and it's just as well I did.

Final Thoughts

As it turns out, after all that, my boss now wants to use HotJar for the feedback mechanism.

I could consider all this as time wasted. There are many modal implementations out there, some of them listed below: I could have downloaded one of them and incorporated it into our codebase, and considered the job done. But that's kind of missing the point.

I can't help feeling that the apparent rush to get things shipped is a convenient excuse to abstract away the knowledge that would otherwise be required to do the job properly.

I'm not saying that anyone who downloads and benefits from someone else's work is lazy or incapable of coding it themselves. Nor am I saying that adopting a ready-made solution precludes learning how that solution is put together in order to understand the problems it solves.

What I have observed in my own surroundings is that very often a seemingly urgent requirement will appear out of the blue, usually from a persuasive senior source, to which our team is expected to respond alongside existing priorities. In a climate of dwindling University applicant numbers, reductions in funding, and redeployment exercises, attempting to negotiate can seem like a risky business.

Under these conditions it can be difficult to make the case that a core aspect of our craft - inclusivity - stays on the agenda, beyond a mere download something with “accessible” in the name so we can tick the box and move on.

Thanks

I've learned a great deal in putting this together, thanks mainly to knowledge provided freely by others who have gone before, so a brief shout out - in no particular order - to Marco Zehe, Ire Aderinokun, Heydon Pickering, Greg Kraus, and all the folks contributing to the W3C WAI ARIA resources.

Further Reading