Four things, all of which are required
A correct modal traps focus inside itself, locks background scroll, makes the rest of the page `inert`, and returns focus to the trigger on close. Three out of four is not a partial pass — each missing one produces its own concrete failure.
`Modal` and `Drawer` do all four. That is why `Modal` costs 2.9 kB rather than a few hundred bytes: the visual part of a dialog is a positioned box, and essentially all of the code is the behaviour around it.
Focus trapping and what happens without it
Without a trap, Tab eventually moves focus behind the dialog to the page underneath. The user is now interacting with content they cannot see, while a visible overlay implies it is unavailable. There is no indication anything has gone wrong — focus simply stops appearing anywhere useful.
Trapping means Tab from the last focusable element wraps to the first, and Shift+Tab from the first wraps to the last. Focus should move into the dialog when it opens — usually to the first focusable element, or the dialog itself when it opens with static content.
Scroll lock without layout shift
The naive scroll lock is `overflow: hidden` on the body. On any platform with a classic scrollbar this removes the scrollbar, the viewport gets wider, and the entire page jumps sideways as the dialog opens. It is a small effect that looks conspicuously broken.
The fix is compensating for the scrollbar width so the layout stays still. This is the kind of detail that separates a dialog someone built once from one that has been used in production — you notice it immediately when it is wrong and never when it is right.
What inert is for
Focus trapping handles Tab. It does not stop a screen reader user from navigating into the background content by heading or landmark, because that navigation does not use focus at all. Those users can read a page that is visually behind an overlay and has no business being available.
`inert` on the background solves it properly: the browser removes that subtree from the accessibility tree and from focus order together. Before `inert` this needed `aria-hidden` plus manual focus management, and getting the pair consistent was a common source of bugs.
Returning focus, and why it is forgotten
When a dialog closes, focus must return to the element that opened it. Without this, focus falls back to the document body and a keyboard user restarts from the top of the page — after every single dialog interaction.
It is forgotten because it is invisible with a mouse. You click a button, a dialog opens, you close it, everything looks fine. The bug only exists for people navigating by keyboard, which is precisely why it survives code review so often.
Drawer is a modal that arrives from the side
A drawer has the same four requirements. The frequent mistake is treating a slide-in panel as a layout element rather than a dialog, which means it gets none of them — no trap, no scroll lock, no inert background, no focus return.
The test is whether the background is meant to be unavailable while it is open. If it is, it is a dialog regardless of how it animates in, and it needs the full behaviour. And as everywhere else in the library, the animation respects `prefers-reduced-motion`.
Key takeaways
- Trap focus, lock scroll, make the background inert, return focus on close — all four are required
- Without a trap, Tab moves focus behind the overlay with no visible indication
- Compensate for scrollbar width or the page jumps sideways as the dialog opens
- inert removes the background from focus order AND the accessibility tree; focus trapping alone does neither for screen reader navigation
- Returning focus to the trigger is invisible with a mouse, which is why it survives review
- A drawer is a dialog if the background is unavailable — animation direction is irrelevant
Conclusion
Almost all of a modal's code is behaviour rather than appearance, which is why 2.9 kB is a reasonable price and why hand-rolled dialogs are so often subtly broken. Judge any implementation by opening it and pressing Tab until you find out where focus went.
Enjoyed this article?

Vivek Kumar Singh
Technical Expert · Full Stack Cloud Engineer · Tokyo, Japan