shadimage
shadcnhooks
hookuse-click-awaysource: ok · hit

Use Click Away

A hook to click away from an element

npx shadcn add "https://shadcn-hooks.com/r/use-click-away.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light

Registry dependencies

@shadcnhooks/use-latest@shadcnhooks/use-effect-with-target@shadcnhooks/create-effect-with-target

Files1 · source cached 10/9/2026, 4:48:54 AM

registry/hooks/use-click-away.ts
import { useEffectWithTarget } from '@/registry/hooks/use-effect-with-target'
import { useLatest } from '@/registry/hooks/use-latest'
import { getTargetElement } from '@/registry/lib/create-effect-with-target'
import type { BasicTarget } from '@/registry/lib/create-effect-with-target'

type DocumentEventKey = keyof DocumentEventMap

declare type TargetValue<T> = T | undefined | null

const checkIfAllInShadow = (targets: BasicTarget[]) => {
  return targets.every((item) => {
    const targetElement = getTargetElement(item)
    if (!targetElement) {
      return false
    }
    if (targetElement.getRootNode() instanceof ShadowRoot) {
      return true
    }
    return false
  })
}

const getShadow = (node: TargetValue<Element>) => {
  if (!node) {
    return document
  }
  return node.getRootNode()
}

const getDocumentOrShadow = (
  target: BasicTarget | BasicTarget[],
): Document | Node => {
  if (!target || !document.getRootNode) {
    return document
  }

  const targets = Array.isArray(target) ? target : [target]

  if (checkIfAllInShadow(targets)) {
    return getShadow(getTargetElement(targets[0]))
  }

  return document
}

export function useClickAway<T extends Event = Event>(
  onClickAway: (event: T) => void,
  target: BasicTarget | BasicTarget[],
  eventName: DocumentEventKey | DocumentEventKey[] = 'click',
) {
  const onClickAwayRef = useLatest(onClickAway)

  useEffectWithTarget(
    () => {
      const handler = (event: any) => {
        const targets = Array.isArray(target) ? target : [target]
        if (
          targets.some((item) => {
            const targetElement = getTargetElement(item)
            return !targetElement || targetElement.contains(event.target)
          })
        ) {
          return
        }
        onClickAwayRef.current(event)
      }

      const documentOrShadow = getDocumentOrShadow(target)

      const eventNames = Array.isArray(eventName) ? eventName : [eventName]

      eventNames.forEach((event) =>
        documentOrShadow.addEventListener(event, handler),
      )

      return () => {
        eventNames.forEach((event) =>
          documentOrShadow.removeEventListener(event, handler),
        )
      }
    },
    Array.isArray(eventName) ? eventName : [eventName],
    target,
  )
}

More from shadcnhooks

Browse all components in this library →