Skip to content

useFocusWithin()

Category
Export Size
951 B
Gzipped
422 B
SSR
SSR support

Last updated: 24/04/2026

Overview

useFocusWithin places focusin / focusout on the container: focusin sets focused true; on focusout it re-checks document.activeElement to see if focus stayed inside the node via contains, so tabbing between inner inputs keeps focused true until focus leaves the subtree. Like useFocus, the effect has no dependency array and re-binds every render. Use for Roving focus groups, “section active” outlines, or form panels.

What it accepts

  • None.

What it returns

  • { focused, ref }.

Usage

Two child inputs inside a ref’d box (no JSON.stringify).

tsx
import useFocusWithin from '@dedalik/use-react/useFocusWithin'

function Example() {
  const { focused, ref } = useFocusWithin<HTMLDivElement>()

  return (
    <div
      ref={ref}
      style={{
        padding: 16,
        borderRadius: 8,
        border: focused ? '2px solid #4f46e5' : '1px solid #e2e8f0',
      }}
    >
      <h3 style={{ marginTop: 0 }}>Group</h3>
      <p>
        focus-within: <strong>{focused ? 'yes' : 'no'}</strong>
      </p>
      <p style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        <input type='text' placeholder='First' />
        <input type='text' placeholder='Second' />
      </p>
    </div>
  )
}

export default function Demo() {
  return <Example />
}

API Reference

useFocusWithin

Signature: useFocusWithin<T extends HTMLElement = HTMLElement>(): UseFocusWithinReturn<T>

Parameters

None.

Returns

UseFocusWithinReturn<T> - focused if the container or any descendant has focus.

Copy-paste hook

TypeScript

tsx
import { RefObject, useEffect, useRef, useState } from 'react'

export interface UseFocusWithinReturn<T extends HTMLElement> {
  focused: boolean
  ref: RefObject<T | null>
}

/**
 * Tracks whether container or any descendant has focus.
 */
export default function useFocusWithin<T extends HTMLElement = HTMLElement>(): UseFocusWithinReturn<T> {
  const ref = useRef<T | null>(null)
  const [focused, setFocused] = useState(false)

  useEffect(() => {
    const node = ref.current
    if (!node) return

    const onFocusIn = () => setFocused(true)
    const onFocusOut = () => {
      const active = document.activeElement
      setFocused(Boolean(active && node.contains(active)))
    }

    node.addEventListener('focusin', onFocusIn)
    node.addEventListener('focusout', onFocusOut)

    return () => {
      node.removeEventListener('focusin', onFocusIn)
      node.removeEventListener('focusout', onFocusOut)
    }
  })

  return { focused, ref }
}

JavaScript

js
import { useEffect, useRef, useState } from 'react'

export default function useFocusWithin() {
  const ref = useRef(null)
  const [focused, setFocused] = useState(false)

  useEffect(() => {
    const node = ref.current
    if (!node) return

    const onFocusIn = () => setFocused(true)
    const onFocusOut = () => {
      const active = document.activeElement
      setFocused(Boolean(active && node.contains(active)))
    }

    node.addEventListener('focusin', onFocusIn)
    node.addEventListener('focusout', onFocusOut)

    return () => {
      node.removeEventListener('focusin', onFocusIn)
      node.removeEventListener('focusout', onFocusOut)
    }
  })

  return { focused, ref }
}

Released under the MIT License.