Focus Trapping in Full-Screen Grid Mode
Permalink to "Focus Trapping in Full-Screen Grid Mode"Many grids offer an “expand” or “full screen” button that enlarges the grid to fill the viewport. Visually it is a different mode; programmatically it is often just a CSS class. Screen reader users can still browse the page behind; keyboard users tab out of the grid into hidden controls; and there is no announced way back. Treating full-screen mode as a modal dialog — trapped focus, named, with a visible exit — makes it predictable for everyone.
This guide belongs to keyboard focus trapping and navigation.
Spec reference
Permalink to "Spec reference"- ARIA dialog (modal) — focus contained; content outside inert.
- SC 2.1.2 No Keyboard Trap (A) — a standard, documented exit (Escape and a button).
- SC 2.4.3 (A) — focus order stays within the visible mode.
- SC 4.1.2 (A) — the mode has a name and the expand button reports state.
When to use — and when not to
Permalink to "When to use — and when not to"Use full-screen mode for wide, dense grids where users need every pixel for a focused task. Make it a choice, not a forced state.
Do not use the browser Fullscreen API alone; it changes the visual viewport but not focus containment, and Escape exits the browser mode without your app knowing. The misapplication: a CSS class that positions the grid fixed over the page while leaving page controls focusable behind it.
Annotated code example
Permalink to "Annotated code example"<button type="button" id="expand" aria-haspopup="dialog">Expand grid</button>
<dialog id="grid-full" aria-labelledby="grid-full-title" class="full">
<header>
<h2 id="grid-full-title">Invoices — full screen</h2>
<button type="button" id="exit-full">Exit full screen</button>
</header>
<div id="grid-slot"></div> <!-- grid moved here -->
</dialog>
let savedScroll;
expand.onclick = () => {
savedScroll = window.scrollY;
gridSlot.append(grid); // move, do not clone: keeps state
gridFull.showModal(); // traps focus, page inert
grid.querySelector('[tabindex="0"]').focus(); // current cell
};
gridFull.addEventListener('cancel', e => {
if (grid.contains(document.activeElement) && isEditing()) { e.preventDefault(); cancelEdit(); } // Escape cancels the edit first
});
gridFull.addEventListener('close', () => {
originalSlot.append(grid); window.scrollTo(0, savedScroll); expand.focus();
});
exitFull.onclick = () => gridFull.close();
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Key | In full-screen mode | Announcement |
|---|---|---|
| Enter on Expand grid | Mode opens | “Invoices — full screen, dialog” + cell |
| Tab | Moves between Exit and grid | — |
| Escape while editing a cell | Cancels the edit only | “Edit cancelled” |
| Escape otherwise | Exits the mode | “Expand grid, button” |
| Arrow keys | Grid navigation | Cells |
Step-by-step implementation
Permalink to "Step-by-step implementation"- Move the grid into a dialog. Moving the same element preserves selection, scroll and editing state; cloning loses them.
- Name the dialog. “Invoices — full screen” with a heading, so screen reader users hear where they are.
- Focus the current cell. Keep the user’s position in the grid rather than jumping to the first cell.
- Resolve Escape conflicts. Escape first cancels an active cell edit or closes a popover; only then does it exit the mode.
- Restore on exit. Move the grid back, restore page scroll and focus the expand button.
Integration context
Permalink to "Integration context"Dialog choices are compared in native dialog versus custom focus traps. Restoring focus is covered in restoring focus after closing complex modals. Escape handling inside cells is part of inline editing and form controls. Nested dialogs from full-screen mode follow nested dialogs and stacked focus traps.
Gotchas
Permalink to "Gotchas"Fullscreen API Escape. If you also use requestFullscreen, the first Escape exits browser full screen and may never reach your handler; listen for fullscreenchange and close the dialog too.
Portals. Menus and popovers that portal to body will render under the modal or become inert; portal them into the dialog.
Virtualization. The viewport size changes; recompute virtual rows after the move.
Edge cases
Permalink to "Edge cases"- Deep links. If full-screen mode has a URL, the browser Back button should exit it.
- Zoom. At high zoom, full-screen mode is often the only usable view; keep the header compact.
- Multiple grids. Only one grid should be expanded at a time.
Worked example
Permalink to "Worked example"An analytics grid’s “Expand” button added a fixed-position class. NVDA users heard the page heading and navigation behind the grid when they pressed H, and keyboard users tabbed into invisible page controls. The team moved the grid into a native modal dialog with a heading and Exit button, made Escape cancel cell edits before exiting, and restored focus and scroll on close. Screen reader users heard a clear “full screen, dialog” on entry and returned exactly where they had been.
Common mistakes
Permalink to "Common mistakes"- CSS-only full screen.
- Escape exiting while a cell edit is active.
- Cloning the grid and losing state.
- Focus lost on exit.
Testing checklist
Permalink to "Testing checklist"Testing it with a screen reader
Permalink to "Testing it with a screen reader"Activate “Expand grid” with NVDA and listen for the dialog name and the focused cell. Press H in browse mode and confirm that only the dialog heading is found. Start editing a cell, press Escape and confirm only the edit is cancelled; press Escape again to exit and hear the expand button announced. In VoiceOver, check that the rotor’s landmarks and headings lists contain only dialog content while the mode is open.
Keyboard shortcut for the mode
Permalink to "Keyboard shortcut for the mode"Power users appreciate a shortcut to toggle full-screen mode, but choose it carefully. F11 belongs to the browser, and single-character keys conflict with screen reader quick navigation. A modifier combination such as Ctrl+Shift+F, scoped to when focus is in the grid, is safer; expose it with aria-keyshortcuts on the expand button and list it in the grid’s keyboard help. Make sure the same shortcut exits the mode, so users can toggle without hunting for the Exit button.
Toolbars in full-screen mode
Permalink to "Toolbars in full-screen mode"Grids usually come with a toolbar — filters, column chooser, export. If the toolbar stays on the page when the grid expands, users lose access to it in full-screen mode. Move the toolbar into the dialog header along with the grid, or provide the same commands in the dialog. Keep the tab order simple: dialog heading, Exit button, toolbar, grid. Users who exit full screen should find the toolbar back in its original place with the same state.
Design system notes
Permalink to "Design system notes"Offer full-screen mode as a grid feature built on the dialog service, so focus, Escape precedence and restoration come for free.
FAQ
Permalink to "FAQ"Should full-screen mode use the Fullscreen API?
Optionally, for extra space; the dialog provides the semantics.
What should the expand button announce?
A name like “Expand grid” and aria-haspopup=“dialog”.
Can users still reach page navigation?
Not while the mode is open; they exit first.
Where does focus go on entry?
To the grid’s current cell.
Is Escape enough as an exit?
Provide a visible button too; not everyone knows Escape.
What about mobile?
Full-screen mode is useful on phones; the same dialog approach applies.
Related
Permalink to "Related"- Nested dialogs and stacked focus traps — dialogs within dialogs
- Using inert to isolate background content — inertness
- Restoring focus after closing complex modals — exit behaviour
← Back to Keyboard Focus Trapping & Navigation for Data Interfaces