use-active-element

Tracks the currently focused element (document.activeElement)

Usage

use-active-element reactively tracks document.activeElement – the element that currently has focus. The returned value is updated every time focus moves to another element:

Focused element: null

import { Code, Group, Text, Textarea, TextInput } from '@mantine/core';
import { useActiveElement } from '@mantine/hooks';

function describe(element: Element | null) {
  if (!element) {
    return 'null';
  }

  return element.id ? `${element.tagName.toLowerCase()}#${element.id}` : element.tagName.toLowerCase();
}

function Demo() {
  const activeElement = useActiveElement();

  return (
    <>
      <Text>
        Focused element: <Code>{describe(activeElement)}</Code>
      </Text>

      <Group grow mt="md">
        <TextInput id="first-name" label="First name" placeholder="First name" />
        <TextInput id="last-name" label="Last name" placeholder="Last name" />
      </Group>

      <Textarea id="message" mt="md" label="Message" placeholder="Your message" />
    </>
  );
}

Definition

interface UseActiveElementOptions {
  /** If set, pierces shadow roots to return the truly-focused element inside web components,
   *  true by default */
  deep?: boolean;

  /** If set, updates the returned element when the active element is removed from the DOM,
   *  true by default */
  triggerOnRemoval?: boolean;
}

function useActiveElement<T extends Element = Element>(
  options?: UseActiveElementOptions,
): T | null;

Example: ignore shortcuts while typing

Use the hook to disable global keyboard shortcuts while the user is typing. In the example below, / focuses the search field only when no input is focused:

Press / to focus the search field

Shortcut active
import { useRef } from 'react';
import { Badge, Group, Kbd, Stack, Text, Textarea, TextInput } from '@mantine/core';
import { useActiveElement, useWindowEvent } from '@mantine/hooks';

function Demo() {
  const searchRef = useRef<HTMLInputElement>(null);
  const activeElement = useActiveElement();

  const isEditing =
    activeElement?.tagName === 'INPUT' ||
    activeElement?.tagName === 'TEXTAREA' ||
    (activeElement as HTMLElement | null)?.isContentEditable === true;

  useWindowEvent('keydown', (event) => {
    // Ignore the shortcut while the user is typing in a field
    if (event.key === '/' && !isEditing) {
      event.preventDefault();
      searchRef.current?.focus();
    }
  });

  return (
    <Stack>
      <Group>
        <Text size="sm">
          Press <Kbd>/</Kbd> to focus the search field
        </Text>
        <Badge color={isEditing ? 'gray' : 'teal'} variant="default">
          {isEditing ? 'Shortcut disabled' : 'Shortcut active'}
        </Badge>
      </Group>

      <TextInput ref={searchRef} label="Search" placeholder="Press / to focus" />
      <Textarea label="Notes" placeholder="Typing here disables the shortcut" />
    </Stack>
  );
}

Shadow DOM

When focus is inside a web component, document.activeElement returns the host element. By default, the hook returns the element that is focused inside the shadow root. Set deep: false to get the host element instead:

import { useActiveElement } from '@mantine/hooks';

function Demo() {
  // Returns the host element instead of the focused element inside its shadow root
  const activeElement = useActiveElement({ deep: false });
  // ...
}

Trigger on removal

Focus events are not dispatched when the focused element is removed from the DOM. By default, the hook uses MutationObserver to update the value in this case. Set triggerOnRemoval: false to disable it:

import { useActiveElement } from '@mantine/hooks';

function Demo() {
  const activeElement = useActiveElement({ triggerOnRemoval: false });
  // ...
}

TypeScript

The hook accepts a generic type argument for the returned element. It defaults to Element, narrow it if you know the type of the focused element in advance:

import { useActiveElement } from '@mantine/hooks';

function Demo() {
  const activeElement = useActiveElement<HTMLInputElement>();
  // activeElement is typed as HTMLInputElement | null
}

UseActiveElementOptions type is exported from the @mantine/hooks package, you can import it in your application:

import type { UseActiveElementOptions } from '@mantine/hooks';