Accessible Modal Dialogs -- A11ycasts #19

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

If you want to know why not to just use a prompt(), it will

  • block scrolling
  • prevent switching tabs
  • look different everywhere
  • not have much space for content

among various other things. Also it selects "OK" by default, which may not be the desired outcome.

๐Ÿ‘๏ธŽ︎ 1 ๐Ÿ‘ค๏ธŽ︎ u/ZaneHannanAU ๐Ÿ“…๏ธŽ︎ Jun 16 2017 ๐Ÿ—ซ︎ replies
Captions
[MUSIC PLAYING] ROB DODSON: Hey, everybody. What's up? It's Rob Dodson. Welcome back to the A11ycasts show. Today, I want to talk about modal dialogs, which I consider sort of like the boss battle at the end of accessibility, because they're pretty difficult to implement and there's a lot of things you've got to consider. And today, I want to show you how I do it, and hopefully give you some tips on how you can make it easier to do them in your own app. So starting off with usually, whenever I'm going to implement any new pattern that I'm not super familiar with, I'll go over to the ARIA Authoring Practices Guide. And I'll see what they recommend for a pattern, and in particular around keyboard interaction and any ARIA roles or states that those elements need to have applied to them. So here it's telling me, you know, when the user presses Tab, we've got to move focus within the dialog, ideally wrapping focus inside of the dialog. And when the user presses Escape, we want to close the dialog because, you know, someone might not be familiar with the orientation of it, they might not land on the right Close button or whatever, they just want to get out of the thing. We should make sure that they can do that. And then there's a few roles that we want to make sure we've got. So we've got a role of Dialog on the element, and we're using aria-labelledby to label the element, as well. So when it opens, we move the user into it using some focus management techniques, it just announces, hey, this is what this thing is, it announces its role, and then hopefully they can move around to the immediately focusable controls. The authoring practices are really useful here. There's another document I want to clue you into, as well, which is the eBay MIND patterns. So this is a guide written by some accessibility testers over at eBay. It's also a super useful, very practical guide to building a lot of different UI patterns, in particular modal dialogs. So the implementation that I'm going to follow today is the one that is sort of presented in this MIND patterns doc. So I'll be using a lot of the same code. There are some places where I'm going to deviate a little bit, and I'll kind of call those out. But both the ARIA Authoring Practices Guide and the MIND patterns doc are very, very useful for building some of these UI patterns. So over here in this lifestyle site that I've built, I'm going to show you the modal dialog that I've created. And it's pretty simple. Basically, when I click on this Add to Cart button, I'm going to pop open this little window that just says, like, Dialog Example, and has a little message here. And there's a couple controls for either closing the dialog or saying, OK, yeah, sure, it was added to my cart, whatever. Now, there's a few things that are happening here that I want to sort of highlight. So notice that, when I tab down to this Add To Cart button, and I hit spacebar to open my modal, that focus is moved right away into the modal dialog. So it's actually on the first focusable element, which is this Close button. And I can hit Escape, right, to close the modal dialog. And focus is returned to the previous active element. So that's a very nice pattern that you want to implement to make sure that the user has kind of, like, a continuity of their context, right? You don't want to open a modal dialog and then, when the user closes it, you just, like, blur focus, and then they have to start over from the top of the page and work their way all the way back down to where they previously were. Turn on a screen reader so you can see the experience in voiceover. SPEAKER: Voiceover on Chrome. A-D-D to Cart button. Dialog example. Buy our awesome stuff. It's really awesome. Close. OK. Dialog Close button. ROB DODSON: Right? So it announced the title of the dialog, it announced the controls that were inside of it, kind of read the content. It said the role. It was kind of mixed in there. But it also said dialog, letting the user know what they're interacting with, right. So I want to make sure that I get that same result in my implementation, as well. And there's a fair bit of code that I've used in this example. So rather than build it live, I'm going to walk you through the code that I've already implemented, and just kind explain what's happening as we go. So the first thing that I often try to do with a modal dialog is, rather than mix it into the rest of my page content, right-- so you might have, like, a main section, and then, like, some other sections inside of there, and sometimes I see people just, like, drop a modal dialog right inside of kind of like a main area-- instead what you want to do is you want to make it kind of like a top-level sibling to all of the other stuff inside a body. So for instance, you want it to be sort of an immediate descendant of body. And what this lets you do is make sure that all of the other controls in the modal, or sorry, all the other controls that are sibling to the modal, are disabled in some fashion. And that way, we can make sure that the screen reader and keyboard focus stays just inside of the modal, and doesn't ever break out and get into the rest of the page, which we definitely don't want. And so I've placed this right-- basically, right along where my script tags are, kind of the closing body tag. I've got the implementation for my modal right here. And that way, I can just go through and I can make everything else in the document inert, which I will talk about in just a sec. And let's walk through the HTML here that I'm using for this dialog. So first, I create a div with a role of Dialog. And I'm also going to say that it is going to be labeled by this element called Dialog Example, which is actually this little h2 that I have inside of here. Now, following the pattern from that MIND patterns doc, there's actually two elements inside of my dialog. There's the dialog window itself, right, which is where the OK and the Close button goes, and then there's also the dialog mask. So this is going to be the overlay that sits behind the dialog. And if the user clicks on that, that will also close down the dialogue and return their focus back to the page. So these are kind of the two elements that I want to work with. Over in my CSS, initially my dialogue is set to Display None. So all those things are hidden. And that also means that they're not going to be in the accessibility tree or anything like that. What I often see people do with dialogs is they just position them offscreen, but they forget to hide them. And so a screen reader user is still actually able to navigate into those things, even though they're offscreen. And you definitely don't want that. So setting them to Display None or Visibility Hidden is a very useful way to make sure that they're completely removed from the accessibility tree. Then, whenever the dialog is getting ready to be opened, I'll just apply an Opened class to it. And that's when we'll set it to Display Block. And in that moment, it gets added back to the accessibility tree. For the dialog window, I'm using a fixed position for that element. I'm saying that I want it to be top and left 50%, and then using a little transform trick to kind of, like, move it back. So this is how we sort of perfectly vertically and horizontally center a fixed element using this little Position Fixed and Transform combo here. And then for the dialog mask, I'm going to also set it to Position Fixed. And I'm just going to make it span, basically, the entire dimensions of the page, and set my background to black with a 60% opacity. Now, over in my app.js file, whenever you click on that little, you know, Product By button, I'm just going to run a little open dialog method. And over in my modal.js file, here is my actual implementation for that open dialog method. And there's quite a bit of code here. So let's walk through this kind of piece by piece. So the first thing I often do is I'll just define, like, a constant for some key codes that will be commonly used. In this case, the Escape key is probably the only one that we really care about for our dialog. And that's going to, again, let the user quickly exit out of the dialogue, and collapse everything down. And then I go through it, and I'm just going to grab references to a few important elements, the dialog, the mask, and the dialog window itself. And then I've got this variable here for the previous active element. Now, remember, the behavior that we want is, when the user opens the dialog, we want to retain knowledge of what the previous active element was so, when they close it, we can return that. So I'll go ahead and set up a variable for that. And whenever I open the dialog-- so this is what gets run when someone clicks that button-- the first thing I do is I look at the document's active element, and I save that in my previous active element variable. So that way, I can restore it later. And then I go and-- this is kind of a quick and dirty trick that I'm doing here. You might want to do something a little bit more refined in your own site. But I just look at all the immediate children of the document body. And remember, we made sure that our dialog was an immediate child of the body. So look at all the other sibling elements. And I'm going to set them-- if they're not the dialog, I'm going to set them to inert equals true. So, inert. Some of you maybe have not seen that property before. This is actually something that we're working on proposing in sort of the standard space. And what inert does is it removes an element from the accessibly tree, and it makes sure that none of its children are focusable. There is a polyfill for Inert, which you can use, which I have used in this site. So you go to github.com/wicg/inert. You can also just npm-install this with npm-install--savewicg-inert. And that will get you a JavaScript file for the Inert polyfill. In my application, I've just included this basically right here, right before my modal.js script. So I'm setting all the other siblings to Inert. So they're not going to be in the accessibility tree. And that means that someone can tab around, and they'll just stay inside of the modal dialog. Then I open the modal dialog up by adding that class. That makes our CSS go. And then I just add a few listeners for things that should close the dialog. So you know, someone clicking on the dialog mask. Here, I'm kind of cheating and I'm just saying, if you click on any of the buttons inside of the dialog, I'll just go ahead and close it. But in your case, you probably want to actually have some additional actions that happen there. And then also listening for a keydown handler. I've got a method here called Check Close dialog. And what that's actually just doing is, hey, if someone hit the Escape key, let's close the dialogue. And then finally, at the very, very end, I find the first focusable element inside the dialog, which is a button, in this case. And I move the user's focus there. So I've managed the focus into the dialog element. That's really important to do because, if you just open the dialog and you don't move the user's focus, then what's going to happen is they're not even going to know maybe that the dialog was open. They're not going to know anything is on the page because you haven't kind of moved their context into that new element. You haven't informed them about it. So we're moving focus into the dialog. Now the user knows it's there, and the screen reader should announce it as such. And then for closing the dialog, we're basically just going to go through and kind of de-structure all the things that we just did. So we go remove our event listeners. We loop through all the sibling elements. We make sure that they are no longer inert. We remove the Opened class. And then finally, we just find that previous active element, and we focus it instead. So we've managed focus out of the dialog now back to that element. And so what this gives us is that experience that I was showing before. Here, we'll go through it again with our screen reader. And we'll highlight some of the focus issues here, as well. So. SPEAKER: Voiceover on Chrome. License 0. A-D-D to Cart. Dialog Example. Buy our awesome stuff. It's really awesome. OK button. Voiceover off. ROB DODSON: So that's working. Something that I want to highlight is how the focus behavior works. So I open this, and I'm tabbing. And you'll notice that focus is allowed to escape a little bit, right? So it actually is allowed to move up to the URL bar. And then the user tabs, and it moves right back down into the dialog. Some documents, like the ARIA Authoring Practices Guide, say that should be completely trapped inside of the dialog. Personally, I-- there's a bit of a debate, and I kind of question that, mainly because if you're trapped inside of the dialog, then you're forcing the user to take an action, which I think could be a bit of a security issue. For instance, you might have a malicious website that's trying to trap a user inside a dialog and force them to, like, install some malware or something. So I want to make sure the user does have the opportunity to get out of the page if they need to to close the tab. And what will happen for a screen reader user is it will actually announce that they're leaving the document. So we can see that here. SPEAKER: Voiceover on Chrome. Dialog Example. Buy our awesome stuff. OK button. Leaving web content. Close-- Voiceover off. ROB DODSON: Right. So it said leaving web content. I actually started to hit some keys there, so it didn't quite finish what it was saying. But it said leaving web content, and then it was going to say, you know, close lifestyle tab, basically letting me know that I can close this current page if I don't want it to be open any longer. So anyway, I know it's a lot of code. And hopefully, we can have some new standards in the future, like the dialog element itself, which is part of HTML, but currently only shipping in Chrome and Opera. But hopefully, some other folks get interested in that. We also have some lower-level primitives that we're working on to make this easier, things like Inert and a few other things. So hopefully, in the near future, this will be a little bit easier. But today, if you're building modal dialogs, this is the process that I would recommend you follow to make sure that your screen reader users have a really nice experience. That about covers it for this episode. Again, if you have any questions, you can always leave them for me down below in the comments, or hit me up on a social network of your choosing. As always, thank you so much for watching, and I'll see you next time. Hey, folks. If you want to learn more about managing focus or things like the inert polyfill, I've got a couple episodes that you can check out. Give those a look. As always, thanks for watching, and I'll see you next time.
Info
Channel: Google Chrome Developers
Views: 36,475
Rating: 4.9692779 out of 5
Keywords: Chrome, Developers, Google, Web, modal dialog, accessible modal dialog, accessibility, app development, accessibility in app development, modal, modal dialogs, accessible modal dialogs, dialog, dialogs, product: chrome, fullname: Rob Dodson, Location: MTV, Team: Scalable Advocacy, Type: DevByte, Other: NoGreenScreen, GDS: Yes;
Id: JS68faEUduk
Channel Id: undefined
Length: 12min 46sec (766 seconds)
Published: Fri Jun 16 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.