Instantly share code, notes, and snippets.

What would you like to do?

Hi Zach :D

Modals are funny beasts, usually they are a design cop-out, but that's okay, designers have to make trade-offs too, give 'em a break.

First things first, I'm not sure there is such thing as a "simple" modal that is production ready. Certainly there have been times in my career I tossed out other people's "overly complex solutions" because I simply didn't understand the scope of the problem, and I have always loved it when people who have a branch of experience that I don't take the time to level me up.

If you're going to build one there are a few things you should know, and most of this applies to lots of other "modal-like" interfaces.

Focus Management

When the user opens the modal, likely from a button click, you need to move focus to the modal. Otherwise assistive device users have no clue anything happened. It's the equivalent of a visual design toggling visibility on a hidden element 10,000 pixels down the page in an app without visible scroll bars.

Inversely, when the modal is closed, you need to return the focus to the element that opened it in the first place, otherwise you've dropped the assistive device user off at the top of document.body. Its the equivalent of a visual design calling location.reload() when a modal closes in a browser that doesn't restore scroll position.

Go look at rackt/react-modal, it does this.

Scoped Tab Navigation

When the user opens the modal, you should ensure that tab navigation is scoped to the modal. In other words, if I'm at the last tabbable element in a modal, and I press the tab key, focus should move to the first tabbable element in the modal, it shouldn't allow me to navigate to the content behind the modal with the keyboard. Browsers don't make it easy.

Even trickier, when the user tabs from the browser chrome you need to capture that and go focus the first tabbable element in the modal.

Go look at rackt/react-modal, it does this

Rendering as a direct child of document.body

Modals should not be rendered in the context of the HTML they are declared in. Unless you're using position: static, you can run into CSS limitations when the modal is rendered inside parents with position: relative and the whole design can unpredictably break without workarounds.

Go look at rackt/react-modal, it does this.

Setting aria-hidden="true" on the rest of the app

I wrote an article on this here. Short story:

Like scoped tab navigation, you also want to prevent assistive devices from allowing the user to navigate outside the modal with the virtual cursor, you're (air quotes) "supposed" to use role=dialog, but that puts the screen reader into "forms mode", changing the keyboard navigation hot keys, and makes most of the modal content invisible to the user.

So instead of role=dialog, you set aria-hidden=true on the rest of the page content and then allow the modal content to be the only visible content to a screen reader. This way the user can't navigate outside the modal (the app is "hidden") and the screen reader doesn't enter forms mode so all the content in the modal is navigable. This is another great reason to render the modal content as a direct child of document.body, makes it easy to hide the rest of the app.

Go look at rackt/react-modal, it does this.

Declarative API

This isn't modal specific, but its React specific. As soon as you grab a ref to component instance and start calling methods on it like show that mutate state, you've lost nearly every benefit of React. Your app's render method + state no longer represent the UI the user gets. You now have to think about your app over-time, instead of as a snapshot in time.

If what the user sees can't be predicted by state + render methods, then it can only be predicted by following all the code paths that lead to non-declared state, which is where we were before React.

Any component that has methods that change what is rendered should be avoided if you want to get the benefits of React's declarative, functional paradigm (this is why things like Redux can do time travel, there is no time travel when you call show() on a ref because it isn't declarative, recordable state).

If this part isn't sinking in, its okay, I fought it too, but I'm sure you'll eventually come around :)


As for CSS, I shipped injectCSS() as a convenience in rackt/react-modal for initial productivity, I expect most people to add their own styles and not actually call injectCSS().

React Modal is one of my first React components, I would love to spend a few days on it to update the API, I feel like I understand React better and would make a few API changes, but its generally a great component.

Thanks for listening!

That's it, I hope this was helpful, I have the best of intentions writing this up.


This comment has been minimized.

jquense commented Jul 30, 2015

interesting point about aria-hidden, I hadn't thought about that approach! in the past I've added role='document' to the dialog content element, usually inside the node with a dialog role. I'll admit it has been a while since I've actually tested that approach in a SR so I wonder if it avoids the cursor mode pitfall.


This comment has been minimized.


ryanflorence commented Jul 30, 2015

Looks like it should

We implemented with aria-hidden on the rest of the app while getting certified by web aim.


This comment has been minimized.


ryanflorence commented Jul 30, 2015

But I don't think role="document" is going to prevent virtual cursor navigation to content behind the modal.


This comment has been minimized.

zackify commented Jul 30, 2015

Seriously thanks a ton for this!


This comment has been minimized.

mjackson commented Jul 30, 2015

<3 this


This comment has been minimized.

hedgerh commented Aug 8, 2015

So after about the 4th time that you mentioned rackt/react-modal, it dawned on me that you made it! (Duh)

One of my first React tasks was to build a modal component for a project. rackt/react-modal was a huge help for me when I didn't know what the heck I was doing. Thanks for that!

It would be interesting to see what changes you would make with your current knowledge/experience. If you ever get around to updating it, I would be willing to help out. I'm sure I'll have to build more modals down the road, and it would be excellent to have a solid one on hand.

Great writeup, by the way!


This comment has been minimized.

florida commented Nov 2, 2015

this is awesome!!! thanks @ryanflorence


This comment has been minimized.

svicalifornia commented Nov 12, 2015

What about nested modals? These happen a lot in enterprise apps: a client will request the ability to create a new folder when saving something, or create a new item when choosing items to associate, or open a color picker when setting other item attributes in a modal, or... you get the idea. These dialogs to be reusable and stackable in a variety of orders, but how would you do that if they're all in the document body to begin with? Toggling visibility is not enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment