DOM Structure Best Practices
Proper DOM structure directly affects screen reader navigation efficiency, focus order, and accessibility tree quality.
Semantic markup
<header>header / footer / main
Landmark elements allow screen readers to jump between sections using keyboard shortcuts.
<nav>nav
Navigation link group. Use aria-label to distinguish multiple nav elements.
<article>article / section
Independent content units and topic divisions. Conveys more meaning than overusing div.
<button>button vs div
<div onclick> requires manually implementing keyboard access, role, and state. Use native elements.
<ul>/<ol>ul / ol / li
Use list elements for lists. Screen readers automatically announce item count and current position.
Heading hierarchy
Maintain h1 → h2 → h3 order
Skipping heading levels can cause screen reader users to misunderstand structure. h3 after h1 is prohibited.
One h1 per page
Only one h1 to represent the page topic. Use h2 for sections, h3 for subsections.
Do not skip heading levels
Screen reader users navigate by scanning the heading list to jump directly to a section. Skipping levels makes the page hierarchy feel broken. To change visual size, use CSS — not the heading level.
Nesting depth
Recommended nesting depth ≤ 7–8 levels
Excessive nesting complicates the accessibility tree and makes it harder for screen reader users to understand context.
Minimize layout-only divs
Do not add wrapper divs for layouts solvable with CSS Grid/Flex.
Watch for component abstraction nesting
Many stacked components produce unnecessary divs in the actual DOM. Prefer Fragment.
DOM size
Lighthouse recommends ≤ 1,500 nodes
More DOM nodes increase the cost of building the accessibility tree and slow screen reader initial load time.
Accessibility tree = subset of DOM
Elements hidden via aria-hidden or display:none are excluded from the accessibility tree, but all visible elements are included.
Consider virtualizing long lists
Virtualize hundreds of list items with react-virtual to reduce both DOM size and the accessibility tree.
Focus order
DOM order = focus order
Tab key focus follows DOM order. Even if CSS (e.g. flex-direction: row-reverse) changes the visual order, focus order is still based on the DOM. When visual and tab order diverge, keyboard users become confused.
Never use positive tabindex
tabindex="0" includes the element in the natural tab order; tabindex="-1" makes it focusable only via JavaScript. Positive tabindex values re-sort the entire page's tab order by number, causing focus to jump unpredictably. As components are added or changed, all the numbers need to be managed again — making it practically unmaintainable.
Focus trap required inside modals
When a modal opens, focus must be trapped inside. On close, return focus to the trigger element.
Glossary
Programmatic focus
Moving focus to a specific element by calling .focus() directly in JavaScript code, without user input. Used when opening or closing modals, and when navigating to an error field after form validation.
Accessibility Tree
A separate tree structure built by the browser from the DOM. Assistive technologies like screen readers read this tree, not the DOM directly. It includes role, aria-* attributes, and text content. Elements hidden with aria-hidden or display:none are excluded.
Focus Trap
A pattern that keeps Tab key navigation cycling within a specific container (e.g., a modal), preventing focus from reaching background content while the modal is open. Libraries like @radix-ui and react-focus-lock implement this.
Landmark
Semantic HTML elements or ARIA roles (header, nav, main, aside, footer) that divide the page structure. Screen reader users can jump between landmarks using keyboard shortcuts, allowing fast navigation to desired sections in long pages.
Virtualization
A technique that renders only the visible items in the DOM, removing the rest. Even with 1000 items, only the ~20 visible ones exist in the DOM. Reduces DOM node count and accessibility tree size, improving performance and assistive technology response time.