react-cool-onclickoutside

A tiny React hook that fires a callback when users click or touch outside one or more registered elements.

Library
npm
v1.7.0
551stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
37/100Needs Attention
Development Activity0
Maintenance20
Community40
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture70
Code Quality80
Innovation85
Learning Curve60

react-cool-onclickoutside is a single-purpose React hook for the classic “dismiss on outside click” pattern used by dropdowns, modals, tooltips, and popovers. Instead of wiring up document-level event listeners by hand in every component, you call the hook, attach the callback ref it returns, and it manages subscription, cleanup, and a handful of tricky edge cases for you.

Beyond the basic case, it supports registering multiple refs under one callback (useful when a trigger and its floating panel live in separate DOM subtrees), excluding specific elements via a CSS ignore-class, ignoring clicks on the browser’s own scrollbar, and detecting clicks that land inside an <iframe> — something a plain document click listener can’t see because iframes are a separate browsing context. It ships as a single dependency-free hook (aside from the React peer dependency) with full TypeScript typings and a sub-1kB gzipped footprint.

What You Get

  • A single useOnclickOutside hook returning a callback ref, with full TypeScript typings included
  • Multi-ref support for guarding several disconnected DOM elements with one shared callback
  • Passive touch listeners, scrollbar exclusion, and CSS-class-based ignore rules for edge cases
  • Iframe click detection via the window blur event, with an option to disable it

Common Use Cases

  • Closing a dropdown or select menu when the user clicks elsewhere
  • Dismissing a modal, popover, or tooltip on outside interaction
  • Coordinating outside-click behavior across a trigger and a portal-rendered panel
  • Detecting clicks that land inside a third-party iframe embed

Under The Hood

Architecture The whole implementation lives in one hook file, src/index.ts, plus a small helper module src/canUsePassiveEvents.ts. useOnclickOutside takes a callback and an options object and returns a callback ref that accumulates registered elements into React state (refsState) via useCallback; its effect attaches document-level mousedown/touchstart listeners (configurable through eventTypes) plus, when detectIFrame is true, a window blur listener that checks document.activeElement on a setTimeout to catch clicks landing inside <iframe> elements, which never bubble to document. The handler closure walks up from event.target via hasIgnoreClass to honor the ignoreClass opt-out, checks clickedOnScrollbar against document.documentElement’s dimensions when excludeScrollbar is set, and invokes the latest callback through a ref (callbackRef) to avoid stale closures without re-subscribing listeners on every render. There is no internal layering beyond this — it is intentionally a flat, single-function library, so any change to the core hook ripples directly to every consumer rather than being absorbed by an abstraction layer.

Tech Stack Written in TypeScript, targeting React >=16.8.0 as a peer dependency with no other runtime dependencies. Built with Rollup and a set of @rollup/plugin-* packages plus rollup-plugin-terser and rollup-plugin-size-snapshot, transpiled via Babel presets for env/React/TypeScript, and published as both CJS and ESM bundles with generated .d.ts typings. Testing runs on Jest with ts-jest, @testing-library/react, and react-test-renderer. Linting and formatting go through ESLint (a shared eslint-config-welly config) and Prettier, wired together with lint-staged and Husky git hooks. A companion demo app under app/ hosts the live Netlify playground referenced from the README.

Code Quality Tests live in src/__tests__/ and are comprehensive for the surface area involved — the main suite exercises multi-ref registration, both string and array forms of ignoreClass, dynamically-added refs, custom eventTypes, the disabled toggle, scrollbar exclusion, and several distinct iframe focus/blur scenarios, with coverage tracked via a Coveralls badge. TypeScript types are used throughout with explicit exported interfaces, though a handful of // @ts-ignore and loosely-typed any escape hatches appear where DOM event shapes get awkward. ESLint and Prettier run as pre-commit hooks via lint-staged/Husky, a tsc type-check is part of the lint script, and a CI badge references an automated workflow.

API Design The public surface is a single default export, useOnclickOutside(callback, options?), returning one callback ref — no provider, no context, no class to instantiate. The common case requires only the standard ref prop with zero extra boilerplate, option names are consistent and self-explanatory (ignoreClass, excludeScrollbar, detectIFrame, disabled), and every option defaults sensibly so edge cases are opt-in additions to the same options object rather than separate APIs. Documentation is unusually thorough for a project this size, with a README that walks through every option using runnable code samples and a linked CodeSandbox demo.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search