Introduction
Knowing which DOM element currently has focus is essential for building accessible forms, keyboard navigation, custom focus indicators, and debugging focus-related issues. The document.activeElement property returns the currently focused element at any point in time. Combined with focus and blur events, it gives you complete control over focus management in web applications.
Basic: document.activeElement
1// Get the currently focused element
2var focused = document.activeElement;
3console.log(focused); // The DOM element with focus
4console.log(focused.tagName); // e.g., 'INPUT', 'BUTTON', 'BODY'
5console.log(focused.id); // The element's ID, if any
When no interactive element has focus (e.g., the user clicked on empty space), document.activeElement returns the <body> element. If the document has not finished loading or has no focus at all, it may return null.
Checking Focus State
1// Check if a specific element has focus
2var myInput = document.getElementById('email');
3if (document.activeElement === myInput) {
4 console.log('Email input is focused');
5}
6
7// Check if ANY input has focus
8if (document.activeElement.tagName === 'INPUT') {
9 console.log('An input field is focused');
10}
11
12// Check if focus is inside a container
13var form = document.getElementById('myForm');
14if (form.contains(document.activeElement)) {
15 console.log('Focus is somewhere inside the form');
16}
Listening for Focus Changes
focus and blur Events
1// Listen on a specific element
2var input = document.getElementById('username');
3
4input.addEventListener('focus', function() {
5 console.log('Username input gained focus');
6 this.classList.add('focused');
7});
8
9input.addEventListener('blur', function() {
10 console.log('Username input lost focus');
11 this.classList.remove('focused');
12});
focusin and focusout (Bubble)
Unlike focus/blur, focusin/focusout bubble up the DOM tree, making them ideal for event delegation:
1// Track focus changes on any element inside a form
2document.getElementById('myForm').addEventListener('focusin', function(e) {
3 console.log('Focus moved to:', e.target.id);
4});
5
6document.getElementById('myForm').addEventListener('focusout', function(e) {
7 console.log('Focus left:', e.target.id);
8 console.log('Focus moving to:', e.relatedTarget?.id || 'outside');
9});
e.relatedTarget in focusout tells you which element is receiving focus next (or null if focus is leaving the document).
Focus in Shadow DOM
When using Web Components with Shadow DOM, document.activeElement returns the shadow host, not the focused element inside the shadow tree:
1// document.activeElement returns the shadow host
2console.log(document.activeElement); // <my-component>
3
4// To get the actual focused element inside shadow DOM
5function getDeepActiveElement() {
6 let active = document.activeElement;
7 while (active && active.shadowRoot && active.shadowRoot.activeElement) {
8 active = active.shadowRoot.activeElement;
9 }
10 return active;
11}
12
13console.log(getDeepActiveElement()); // The actual input inside shadow DOM
Focus Management Patterns
Trap Focus in a Modal
1function trapFocus(modal) {
2 const focusable = modal.querySelectorAll(
3 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
4 );
5 const first = focusable[0];
6 const last = focusable[focusable.length - 1];
7
8 modal.addEventListener('keydown', function(e) {
9 if (e.key !== 'Tab') return;
10
11 if (e.shiftKey) {
12 if (document.activeElement === first) {
13 e.preventDefault();
14 last.focus();
15 }
16 } else {
17 if (document.activeElement === last) {
18 e.preventDefault();
19 first.focus();
20 }
21 }
22 });
23
24 first.focus();
25}
Restore Focus After Modal Close
1function openModal(modal) {
2 const previousFocus = document.activeElement;
3 modal.style.display = 'block';
4 modal.querySelector('input, button').focus();
5
6 modal.addEventListener('close', function() {
7 modal.style.display = 'none';
8 previousFocus.focus(); // Restore focus to the element that opened the modal
9 }, { once: true });
10}
Skip Links for Keyboard Navigation
1document.querySelector('.skip-link').addEventListener('click', function(e) {
2 e.preventDefault();
3 var target = document.getElementById('main-content');
4 target.setAttribute('tabindex', '-1');
5 target.focus();
6});
jQuery Equivalent
1// jQuery :focus selector
2var focused = $(':focus');
3console.log(focused.attr('id'));
4
5// Check if element has focus
6if ($('#myInput').is(':focus')) {
7 console.log('Input is focused');
8}
Debugging Focus Issues
1// Log every focus change (useful for debugging)
2document.addEventListener('focusin', function(e) {
3 console.log('Focus →', e.target.tagName, e.target.id || e.target.className);
4});
5
6// Pause on focus change in DevTools
7// In Chrome DevTools Console:
8document.addEventListener('focusin', function(e) {
9 debugger; // Pauses execution whenever focus changes
10});
11
12// Highlight currently focused element with CSS
13// Add to your stylesheet for debugging:
14// :focus { outline: 3px solid red !important; }
Common Pitfalls
document.activeElement returns <body>: This happens when no interactive element has focus. Check document.activeElement !== document.body before using the result, or verify the element type.
Focus during page load: document.activeElement may return null or <body> before the DOM is fully loaded. Wait for DOMContentLoaded before querying focus state.
focus/blur do not bubble: If you need event delegation (listening on a parent), use focusin/focusout instead. This is a common source of "my focus handler never fires" bugs.
relatedTarget is null: In focusout, e.relatedTarget is null when focus moves outside the document (e.g., to the address bar or another application). Handle this case explicitly.
Programmatic focus requires user interaction context: Some browsers restrict element.focus() calls that are not triggered by user interaction (e.g., calling .focus() in a setTimeout after page load). This is especially strict on mobile browsers.
Summary
Use document.activeElement to get the currently focused DOM element at any time
Use focusin/focusout events (not focus/blur) for event delegation on parent containers
Use e.relatedTarget in focusout to determine where focus is moving next
For Shadow DOM, walk shadowRoot.activeElement recursively to find the deeply focused element
Always restore focus to the previously focused element when closing modals or dialogs