 Hey, everybody, what's up? It's Rob Dodson. Welcome back to The AliCast Show. Today, I want to talk about modal dialogues, which I consider sort of like the boss battle at the end of accessibility, because they're pretty difficult to implement. There's a lot of things you 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 when the user presses Tab, we've got to move focus within the dialogue, ideally wrapping focus inside of the dialogue. And when the user presses Escape, we want to close the dialogue, because 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 we want to make sure we've got. So we've got a role dialogue on the element. And we're using Aria label by 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 dialogues. 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's 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 have built, I'm going to show you the modal dialogue that I've created. And it's pretty simple. Basically, when I click on this Add to Cart button, I'm going to pop up in this little window that just says Dialog Example, and it has a little message here. And there's a couple controls for either closing the dialogue or saying, OK, yeah, sure. It was added to my cart, whatever. And there's a few things that are happening here that I want to 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 dialogue. So it's actually on the first focusable element, which is this Close button. And then I can hit Escape to close the modal dialogue, 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 a continuity of their context. You don't want to open a modal dialogue, and then when the user closes it, you just 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 voice over. Voice over on drone, light zero. Stepper name ADD to cart button. Dialogue example, by our awesome stuff. It's really awesome. Close, OK, dialogue, close button. So it announced the title of the dialogue. 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 dialogue, letting the user know what they're interacting with. So I want to make sure that I get that same result in my implementation as well. And it'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 of explain what's happening as we go. So the first thing that I often try to do with a modal dialogue is rather than mix it into the rest of my page content, right? So you might have a main section and then some other sections inside of there. And sometimes I see people just drop a modal dialogue right inside of kind of 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 place this 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 dialogue. So first, I created div with a roll of dialogue. And I'm also going to say that it is going to be labeled by this element called dialogue example, which is actually this little h2 that I have inside of here. Now following the pattern from that mind pattern stock, there's actually two elements inside of my dialogue. There's the dialogue window itself, which is where the OK and the Close button goes. And then there's also the dialogue mask. So this is going to be the overlay that sits behind the dialogue. And if the user clicks on that, that'll also close down the dialogue and return their focus back to the page. So these are 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 dialogues is they just position them off screen, 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 off screen, 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 dialogue is getting ready to be opened, I'll just apply an opened class to it. And that's when we'll set it to a display block, and in that moment it gets added back to the accessibility tree. For the dialogue 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 dialogue 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 product buy button, I'm just going to run a little open dialogue method. And over in my modal.js file, here is my actual implementation for that open dialogue 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 dialogue. 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. I'm just going to grab references to a few important elements, the dialogue, the mask, and the dialogue window itself. And then I've got this variable here for the previous active element. Remember, the behavior that we want is when the user opens the dialogue, 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 dialogue, so this is what gets run when someone clicks that button, the first thing I do is I look at the documents active element, and I save that in my previous active element variables. That way I can restore it later. 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 dialogue 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 dialogue, I'm going to set them to inert equals true. So inert, right? 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 accessibility tree and 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 slash wicg slash inert. You can also just npm install this with npm install dash dash save wicg dash 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 dialogue. Then I open the modal dialogue up by adding that class that makes our CSS go. And then I just add a few listeners for things that should close the dialogue. So someone clicking on the dialogue mask, here I'm kind of cheating. And I'm just saying, if you click on any of the buttons inside of the dialogue, 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 key down handler, we've got a method here called check close dialogue. 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 dialogue, which is a button in this case. And I move the user's focus there, right? So I've managed the focus into the dialogue element. That's really important to do because if you just open the dialogue 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 dialogue 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 dialogue. Now the user knows it's there and the stream reader should announce it as such. And then for closing the dialogue, we're basically just going to go through and kind of destructure 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 open to class. And then finally, we just find that previous active element and we focus it instead. So we've managed focus out of the dialogue now back to that element. And so what this gives us is that experience that I was showing before. Here, we'll go through again with our stream reader and we'll highlight some of the focus issues here as well. So voiceover on Chrome, life's at zero. Step or ADD to cart the dialogue example. I have awesome stuff. It's really awesome. Cool. 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. 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 dialogue. Some documents like the already authoring practices guide say that should be completely trapped inside of the dialogue. Personally, there's a bit of a debate and I kind of like question that mainly because if you're trapped inside of the dialogue, 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 like trying to trap a user inside a dialogue and force them to 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 stream reader user is it'll actually announce that they're leaving the document. So we can see that here. Voiceover on Chrome, life's dialogue example. I have awesome stuff. OK, button. Leaving web content. Close, WAF, WAF, voiceover off. Right. So it said leaving web content. I actually started to hit some keys there, so it didn't quite finish what I was saying, but it said leaving web content. And then I was going to say 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, this is, I know it's a lot of code. And hopefully we can have some new standards in the future, like the dialogue 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 Endert 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 dialogues, 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. I 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 Endert polyfill, I've got a couple of episodes that you can check out. Give those a look. As always, thanks for watching, and I'll see you next time.