Utilities
Focus trap

Focus Trap

Trap focus within a specified container.

Motivation

Focus trapping is essential for modal interfaces and other interactive elements that require user attention.

The FocusTrap component helps maintain accessibility by ensuring keyboard focus remains within a designated container until explicitly released.

Examples

import { useState } from 'react'
import { FocusTrap } from '../focus-trap'

export const Basic = () => {
  const [trapped, setTrapped] = useState(false)
  return (
    <>
      <button onClick={() => setTrapped(true)}>Start Trap</button>
      <FocusTrap returnFocusOnDeactivate={false} disabled={!trapped}>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '1rem',
            paddingBlock: '1rem',
          }}
        >
          <input type="text" placeholder="input" />
          <textarea placeholder="textarea" />
          <button onClick={() => setTrapped(false)}>End Trap</button>
        </div>
      </FocusTrap>
    </>
  )
}

Autofocus

The focus trap respects elements with the autofocus attribute.

import { useRef, useState } from 'react'
import { FocusTrap } from '../focus-trap'

export const Autofocus = () => {
  const [trapped, setTrapped] = useState(false)
  const toggle = () => setTrapped((c) => !c)

  const buttonRef = useRef<HTMLButtonElement | null>(null)
  const getButtonNode = () => {
    const node = buttonRef.current
    if (!node) throw new Error('Button not found')
    return node
  }

  return (
    <div>
      <button ref={buttonRef} onClick={toggle}>
        {trapped ? 'End Trap' : 'Start Trap'}
      </button>
      {trapped && (
        <FocusTrap disabled={!trapped} setReturnFocus={getButtonNode}>
          <div
            style={{
              display: 'flex',
              flexDirection: 'column',
              gap: '1rem',
              paddingBlock: '1rem',
            }}
          >
            <input type="text" placeholder="Regular input" />
            {/* biome-ignore lint/a11y/noAutofocus: <explanation> */}
            <input type="text" placeholder="Autofocused input" autoFocus />
            <button onClick={() => setTrapped(false)}>End Trap</button>
          </div>
        </FocusTrap>
      )}
    </div>
  )
}

Initial Focus

Use the initialFocus prop to set the element that should receive initial focus when the trap is activated.

import { useRef, useState } from 'react'
import { FocusTrap } from '../focus-trap'

export const InitialFocus = () => {
  const [trapped, setTrapped] = useState(false)
  const toggle = () => setTrapped((c) => !c)

  const inputRef = useRef<HTMLInputElement>(null)

  return (
    <div>
      <button onClick={toggle}>{trapped ? 'End Trap' : 'Start Trap'}</button>
      <FocusTrap disabled={!trapped} initialFocus={() => inputRef.current}>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '1rem',
            paddingBlock: '1rem',
          }}
        >
          <input type="text" placeholder="First input" />
          <input ref={inputRef} type="text" placeholder="Second input (initial focus)" />
          <textarea placeholder="textarea" />
          <button onClick={() => setTrapped(false)}>End Trap</button>
        </div>
      </FocusTrap>
    </div>
  )
}

API Reference