Skip to content

Focus History — Remembering and Restoring Focus Position

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Focus History. We cover key concepts, practical examples, and best practices to help you master this topic.

Focus history tracks the sequence of focused elements so focus can be restored to previous positions when navigating back, closing overlays, or undoing actions.

In this tutorial, you'll learn Focus Management with focus history.

What You'll Learn

By the end of this lesson, you'll understand how to implement a focus history stack, restore focus on navigation, and handle complex focus scenarios.

Why It Matters

Users expect to return to their previous focus position when they close a dialog, navigate back, or undo an action. Focus history makes this possible.

Real-World Use

Doda Browser's multi-level settings panels use focus history so users return to the correct panel when navigating back from sub-settings.

Focus History Stack

flowchart LR
  A[Focus Stack] --> B[Push: element focused]
  A --> C[Pop: return to previous]
  B --> D[Stack grows]
  C --> E[Stack shrinks]
  D --> F[Top: current focus]
  E --> F

Implementing Focus History

class FocusHistory {
  constructor(maxSize = 10) {
    this.stack = [];
    this.maxSize = maxSize;
  }

  push(element) {
    this.stack.push(element);
    if (this.stack.length > this.maxSize) {
      this.stack.shift();
    }
  }

  pop() {
    const element = this.stack.pop();
    if (element && document.contains(element)) {
      element.focus();
      return true;
    }
    return false;
  }

  peek() {
    return this.stack[this.stack.length - 1] || null;
  }

  clear() {
    this.stack = [];
  }
}

const focusHistory = new FocusHistory();

// Track focus changes
document.addEventListener('focusin', (event) => {
  focusHistory.push(event.target);
});

Using Focus History

// When a dialog opens, save the current focus
function openPanel(trigger) {
  focusHistory.push(trigger);
  // Open the panel
}

// When the user navigates back
function handleBack() {
  if (!focusHistory.pop()) {
    // No history, go to a safe default
    document.querySelector('main h1').focus();
  }
}

// When a modal closes
function closeDialog(trigger) {
  dialog.hidden = true;
  if (document.contains(trigger)) {
    trigger.focus();
  } else {
    focusHistory.pop();
  }
}

Focus History for SPAs

// Track focus per route
class SPARouter {
  constructor() {
    this.routeHistory = {};
    this.currentRoute = null;
  }

  navigate(route) {
    // Save current focus for the previous route
    this.routeHistory[this.currentRoute] = document.activeElement;

    // Load new route
    this.currentRoute = route;
    loadContent(route);

    // Restore focus for the new route
    const savedFocus = this.routeHistory[route];
    if (savedFocus && document.contains(savedFocus)) {
      savedFocus.focus();
    } else {
      document.querySelector('main h1').focus();
    }
  }
}

Common Mistakes

  • Not clearing history when appropriate: History accumulates stale references.
  • Storing references to removed elements: Focus restoration to a removed element fails.
  • Not capping the history size: Memory leaks from infinite history.
  • Over-focusing elements: Not every focus change needs to be in the history.
  • Restoring focus to hidden elements: Elements hidden or disabled should not receive focus.

Practice and Challenge

1. What is focus history used for? Tracking focus positions for restoring them after navigation or overlay dismissal.

2. How does a focus history stack work? Elements are pushed as focus changes and popped when restoring previous positions.

3. What happens if you pop an element that no longer exists? The pop fails. Provide a fallback focus target.

4. Why should you cap the history size? To prevent memory leaks from unbounded history growth.

5. Challenge: Implement focus history for a multi-step form. Each step saves focus and restores it when navigating back.

FAQ

Should I track every focus change?

Track meaningful navigation events. Internal widget navigation within a dialog does not need history.

What is the maximum useful history size?

10-20 elements is usually sufficient. More than that is rarely needed.

Does focus history work across page loads?

Focus history is in-memory and lost on page reload. Use sessionStorage for persistence.

Can I use focus history with mobile?

Focus history is primarily for keyboard focus. Mobile touch does not have a focus cursor.

What is the fallback if history is empty?

Focus the main heading or first focusable element on the page.

Mini Project

Build a multi-panel settings interface with navigation history. Users should return to the correct panel and element when clicking Back.

What's Next

Continue to Inert Attribute to learn about making content unfocusable.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro