Skip to main content
react-md

useFocusContainer

function useFocusContainer<E extends HTMLElement>(
  options: FocusContainerOptions<E>
): FocusContainerImplementation<E>;

This hook is mostly for internal use only for dialog accessibility behavior to prevent the focus from moving outside of the dialog while it is visible. This API was developed to be used with the useCSSTransition/useTransition hooks as well.

Example Usage

import { Button } from "@react-md/core/button/Button";
import { useFocusContainer } from "@react-md/core/focus/useFocusContainer";
import { useScaleTransition } from "@react-md/core/transition/useScaleTransition";
import { useToggle } from "@react-md/core/useToggle";
import { type ReactElement } from "react";

function Example(): ReactElement {
  const { toggled, enable, disable } = useToggle(false);

  const { eventHandlers, transitionOptions } = useFocusContainer({
    activate: toggled,
  });
  const { elementProps, rendered } = useScaleTransition({
    transitionIn: toggled,
    temporary: true,
    ...transitionOptions,
  });

  return (
    <>
      <Button onClick={enable}>Toggle</Button>
      {rendered && (
        <div {...eventHandlers} {...elementProps}>
          <Button onClick={disable}>Button 1</Button>
          <Button onClick={disable}>Button 2</Button>
          <Button onClick={disable}>Button 3</Button>
          <Button onClick={disable}>Button 4</Button>
        </div>
      )}
    </>
  );
}

Parameters

export interface FocusContainerOptions<E extends HTMLElement>
  extends FocusContainerTransitionOptions<E>,
    FocusContainerComponentProps {
  onKeyDown?: KeyboardEventHandler<E>;
  /**
   * This to `true` will capture the current focused element as a focus target
   * once the `onExited` hook is fired. This should usually be set to the
   * `transitionIn` prop for `useTransition`.
   */
  activate: boolean;

  /**
   * Set this to true if elements that can be programmatically focused should be
   * included. These would be elements with a `tabIndex={-1}`.
   *
   * @defaultValue `false`
   */
  programmatic?: boolean;
}

/**
 * `"mount"` - this will attempt to focus the container element if:
 * - there is no `document.activeElement`
 * - the container element does not contain the `document.activeElement`
 *
 * `"unmount"` - attempts to re-focus the element that was focused before the
 * focus container became active. The previous focus element is captured
 * whenever the `activate` option on the `useFocusContainer` hook is set to
 * `true`. This is normally when an element becomes `visible`.
 *
 * `"keyboard"` - refocuses the first focusable element if pressing `Tab` would
 * move the focus outside of the container element. If `Shift + Tab` was used,
 * the last focusable element will be used instead.
 */
export type FocusType = "mount" | "unmount" | "keyboard";
export type IsFocusTypeDisabled = (type: FocusType) => boolean;

export interface FocusContainerComponentProps {
  /**
   * @defaultValue `() => false`
   */
  isFocusTypeDisabled?: IsFocusTypeDisabled;

  /**
   * @defaultValue `false`
   */
  disableTransition?: boolean;
}

export interface FocusContainerTransitionOptions<E extends HTMLElement>
  extends FocusContainerTransitionCallbacks {
  /**
   * An optional ref that will be merged with the
   * {@link FocusContainerImplementation.nodeRef}
   */
  nodeRef?: Ref<E>;
}

Returns

export interface FocusContainerImplementation<E extends HTMLElement> {
  nodeRef: RefObject<E>;
  eventHandlers: Required<FocusContainerEventHandlers<E>>;
  transitionOptions: Required<FocusContainerTransitionOptions<E>>;
}

export interface FocusContainerEventHandlers<E extends HTMLElement> {
  onKeyDown?: KeyboardEventHandler<E>;
}