FocusScope

A FocusScope manages focus for its descendants. It supports containing focus inside the scope, restoring focus to the previously focused element on unmount, and auto focusing children on mount. It also acts as a container for a programmatic focus management interface that can be used to move focus forward and back in response to user events.

installyarn add @react-aria/focus
version3.5.0
usageimport {FocusScope, useFocusManager} from '@react-aria/focus'

Introduction#


FocusScope is a utility component that can be used to manage focus for its descendants. When the contain prop is set, focus is contained within the scope. This is useful when implementing overlays like modal dialogs, which should not allow focus to escape them while open. In addition, the restoreFocus prop can be used to restore focus back to the previously focused element when the focus scope unmounts, for example, back to a button which opened a dialog. A FocusScope can also optionally auto focus the first focusable element within it on mount when the autoFocus prop is set.

The useFocusManager hook can also be used in combination with a FocusScope to programmatically move focus within the scope. For example, arrow key navigation could be implemented by handling keyboard events and using a focus manager to move focus to the next and previous elements.

Props#


NameTypeDefaultDescription
childrenReactNodeThe contents of the focus scope.
containbooleanWhether to contain focus inside the scope, so users cannot move focus outside, for example in a modal dialog.
restoreFocusbooleanWhether to restore focus back to the element that was focused when the focus scope mounted, after the focus scope unmounts.
autoFocusbooleanWhether to auto focus the first focusable element in the focus scope on mount.

FocusManager Interface#


To get a focus manager, call the useFocusManager hook from a component within the FocusScope. A focus manager supports the following methods:

MethodDescription
focusNext( (opts: FocusManagerOptions )): HTMLElementMoves focus to the next focusable or tabbable element in the focus scope.
focusPrevious( (opts: FocusManagerOptions )): HTMLElementMoves focus to the previous focusable or tabbable element in the focus scope.
focusFirst( (opts: FocusManagerOptions )): HTMLElementMoves focus to the first focusable or tabbable element in the focus scope.
focusLast( (opts: FocusManagerOptions )): HTMLElementMoves focus to the last focusable or tabbable element in the focus scope.

Example#


A basic example of a focus scope that contains focus within it is below. Clicking the "Open" button mounts a FocusScope, which auto focuses the first input inside it. Once open, you can press the Tab key to move within the scope, but focus is contained inside. Clicking the "Close" button unmounts the focus scope, which restores focus back to the button.

For a full example of building a modal dialog, see useDialog.

function Example() {
  let [isOpen, setOpen] = React.useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Open</button>
      {isOpen && (
        <FocusScope contain restoreFocus autoFocus>
          <input aria-label="First input" placeholder="First input" />
          <input aria-label="Second input" placeholder="Second input" />
          <button onClick={() => setOpen(false)}>Close</button>
        </FocusScope>
      )}
    </>
  );
}
function Example() {
  let [isOpen, setOpen] = React.useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Open</button>
      {isOpen && (
        <FocusScope contain restoreFocus autoFocus>
          <input
            aria-label="First input"
            placeholder="First input"
          />
          <input
            aria-label="Second input"
            placeholder="Second input"
          />
          <button onClick={() => setOpen(false)}>
            Close
          </button>
        </FocusScope>
      )}
    </>
  );
}
function Example() {
  let [
    isOpen,
    setOpen
  ] = React.useState(
    false
  );
  return (
    <>
      <button
        onClick={() =>
          setOpen(true)
        }>
        Open
      </button>
      {isOpen && (
        <FocusScope
          contain
          restoreFocus
          autoFocus>
          <input
            aria-label="First input"
            placeholder="First input"
          />
          <input
            aria-label="Second input"
            placeholder="Second input"
          />
          <button
            onClick={() =>
              setOpen(
                false
              )
            }>
            Close
          </button>
        </FocusScope>
      )}
    </>
  );
}

useFocusManager example#


This example shows how to use useFocusManager to programmatically move focus within a FocusScope. It implements a basic toolbar component, which allows using the left and right arrow keys to move focus to the previous and next buttons. The wrap option is used to make focus wrap around when it reaches the first or last button.

function Toolbar(props) {
  return (
    <div role="toolbar">
      <FocusScope>{props.children}</FocusScope>
    </div>
  );
}

function ToolbarButton(props) {
  let focusManager = useFocusManager();
  let onKeyDown = (e) => {
    switch (e.key) {
      case 'ArrowRight':
        focusManager.focusNext({wrap: true});
        break;
      case 'ArrowLeft':
        focusManager.focusPrevious({wrap: true});
        break;
    }
  };

  return <button onKeyDown={onKeyDown}>{props.children}</button>;
}

<Toolbar>
  <ToolbarButton>Cut</ToolbarButton>
  <ToolbarButton>Copy</ToolbarButton>
  <ToolbarButton>Paste</ToolbarButton>
</Toolbar>
function Toolbar(props) {
  return (
    <div role="toolbar">
      <FocusScope>{props.children}</FocusScope>
    </div>
  );
}

function ToolbarButton(props) {
  let focusManager = useFocusManager();
  let onKeyDown = (e) => {
    switch (e.key) {
      case 'ArrowRight':
        focusManager.focusNext({wrap: true});
        break;
      case 'ArrowLeft':
        focusManager.focusPrevious({wrap: true});
        break;
    }
  };

  return (
    <button onKeyDown={onKeyDown}>{props.children}</button>
  );
}

<Toolbar>
  <ToolbarButton>Cut</ToolbarButton>
  <ToolbarButton>Copy</ToolbarButton>
  <ToolbarButton>Paste</ToolbarButton>
</Toolbar>
function Toolbar(props) {
  return (
    <div role="toolbar">
      <FocusScope>
        {props.children}
      </FocusScope>
    </div>
  );
}

function ToolbarButton(
  props
) {
  let focusManager = useFocusManager();
  let onKeyDown = (
    e
  ) => {
    switch (e.key) {
      case 'ArrowRight':
        focusManager.focusNext(
          {wrap: true}
        );
        break;
      case 'ArrowLeft':
        focusManager.focusPrevious(
          {wrap: true}
        );
        break;
    }
  };

  return (
    <button
      onKeyDown={
        onKeyDown
      }>
      {props.children}
    </button>
  );
}

<Toolbar>
  <ToolbarButton>
    Cut
  </ToolbarButton>
  <ToolbarButton>
    Copy
  </ToolbarButton>
  <ToolbarButton>
    Paste
  </ToolbarButton>
</Toolbar>