# Manage Focus

**MUST** · **ID:** `interactions-manage-focus` · **Category:** interactions
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-manage-focus

> MUST: Manage focus (trap, move, and return) per APG patterns

Use focus traps, move and return focus according to WAI-ARIA patterns

When opening modals or dialogs, trap focus within them so keyboard users can't accidentally tab outside. When closing, return focus to the element that triggered the dialog. This maintains context and prevents confusion for keyboard and screen reader users.

## Bad — do not do this

`interactions-manage-focus-bad`

```tsx
import { useState } from 'react';

export function ManageFocusBad() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="w-full max-w-sm">
      <button
        onClick={() => setIsOpen(true)}
        className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
      >
        Open Modal
      </button>

      {isOpen && (
        <div className="fixed inset-0 bg-overlay flex items-center justify-center z-50">
          <div className="bg-card rounded-lg p-6 w-80 shadow-xl">
            <h2 className="text-lg font-semibold mb-4">Modal Title</h2>
            <p className="text-sm text-muted-foreground mb-4">
              This modal has no focus trap. Try tabbing - focus escapes to the page behind.
            </p>
            <input
              type="text"
              placeholder="Type here..."
              className="w-full px-3 py-2 border border-border rounded-lg mb-4 focus:outline-hidden focus:ring-2 focus:ring-ring"
            />
            <div className="flex justify-end gap-2">
              <button
                onClick={() => setIsOpen(false)}
                className="px-4 py-2 text-muted-foreground hover:bg-muted rounded-lg"
              >
                Cancel
              </button>
              <button
                onClick={() => setIsOpen(false)}
                className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
              >
                Confirm
              </button>
            </div>
          </div>
        </div>
      )}

      <p className="text-xs text-error mt-4">
        No focus trap - Tab escapes modal, focus not returned on close
      </p>
    </div>
  );
}
```

## Good — do this

`interactions-manage-focus-good`

```tsx
import { useState, useRef, useEffect, useCallback } from 'react';

export function ManageFocusGood() {
  const [isOpen, setIsOpen] = useState(false);
  const triggerRef = useRef<HTMLButtonElement>(null);
  const modalRef = useRef<HTMLDivElement>(null);
  const firstFocusableRef = useRef<HTMLInputElement>(null);

  const handleKeyDown = useCallback((e: KeyboardEvent) => {
    if (e.key === 'Escape') {
      setIsOpen(false);
      return;
    }

    if (e.key !== 'Tab' || !modalRef.current) return;

    const focusableElements = modalRef.current.querySelectorAll<HTMLElement>(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    if (e.shiftKey && document.activeElement === firstElement) {
      e.preventDefault();
      lastElement.focus();
    } else if (!e.shiftKey && document.activeElement === lastElement) {
      e.preventDefault();
      firstElement.focus();
    }
  }, []);

  useEffect(() => {
    if (isOpen) {
      firstFocusableRef.current?.focus();
      document.addEventListener('keydown', handleKeyDown);
      return () => document.removeEventListener('keydown', handleKeyDown);
    } else {
      triggerRef.current?.focus();
    }
  }, [isOpen, handleKeyDown]);

  return (
    <div className="w-full max-w-sm">
      <button
        ref={triggerRef}
        onClick={() => setIsOpen(true)}
        className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
      >
        Open Modal
      </button>

      {isOpen && (
        <div
          className="fixed inset-0 bg-overlay flex items-center justify-center z-50"
          onClick={(e) => e.target === e.currentTarget && setIsOpen(false)}
        >
          <div
            ref={modalRef}
            role="dialog"
            aria-modal="true"
            aria-labelledby="modal-title"
            className="bg-card rounded-lg p-6 w-80 shadow-xl"
          >
            <h2 id="modal-title" className="text-lg font-semibold mb-4">Modal Title</h2>
            <p className="text-sm text-muted-foreground mb-4">
              Focus is trapped here. Tab cycles within modal. Escape or clicking outside closes it.
            </p>
            <input
              ref={firstFocusableRef}
              type="text"
              placeholder="Type here..."
              className="w-full px-3 py-2 border border-border rounded-lg mb-4 focus:outline-hidden focus:ring-2 focus:ring-ring"
            />
            <div className="flex justify-end gap-2">
              <button
                onClick={() => setIsOpen(false)}
                className="px-4 py-2 text-muted-foreground hover:bg-muted rounded-lg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
              >
                Cancel
              </button>
              <button
                onClick={() => setIsOpen(false)}
                className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
              >
                Confirm
              </button>
            </div>
          </div>
        </div>
      )}

      <p className="text-xs text-success mt-4">
        Focus trapped in modal, returns to trigger on close, Escape closes
      </p>
    </div>
  );
}
```

## References

- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
- [Focus Management](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/)
