npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usefy/hooks

v1.1.0

Published

A collection of useful React hooks

Readme


✅ Stable & production-ready — @usefy/hooks follows semantic versioning; breaking changes only ever land in a new major version.

🚧 Actively developing: new hooks are added regularly, and the umbrella re-exports every one.


Overview

usefy is a collection of production-ready custom hooks designed for modern React applications. All hooks are written in TypeScript, providing complete type safety, comprehensive testing, and minimal bundle size.

✨ Why usefy?

  • 🚀 Zero Dependencies — Pure React implementation with no external dependencies
  • 📦 Tree Shakeable — Import only the hooks you need to optimize bundle size
  • 🔷 TypeScript First — Complete type safety with full autocomplete support
  • ⚡ SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
  • 🧪 Well Tested — High test coverage ensures reliability and stability
  • 📖 Well Documented — Detailed documentation with practical examples
  • 🎨 Interactive Demos — Try all hooks in action with our Storybook playground

Installation

All-in-One Package

Install all hooks at once:

# npm
npm install @usefy/hooks

# yarn
yarn add @usefy/hooks

# pnpm
pnpm add @usefy/hooks

Individual Packages

You can also install only the hooks you need:

# Example: Install only use-toggle
pnpm add @usefy/use-toggle

# Install multiple packages
pnpm add @usefy/use-debounce @usefy/use-local-storage

Peer Dependencies

All packages require React 18 or 19:

{
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  }
}

Packages

📦 Available Hooks

| Hook | Description | npm | Coverage | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | @usefy/use-toggle | Boolean state management with toggle, setTrue, setFalse | | 100% | | @usefy/use-counter | Counter state with increment, decrement, reset | | 100% | | @usefy/use-debounce | Value debouncing with leading/trailing edge | | 92% | | @usefy/use-debounce-callback | Debounced callbacks with cancel/flush/pending | | 93% | | @usefy/use-throttle | Value throttling for rate-limiting updates | | 100% | | @usefy/use-throttle-callback | Throttled callbacks with cancel/flush/pending | | 100% | | @usefy/use-local-storage | localStorage persistence with cross-tab sync | | 95% | | @usefy/use-session-storage | sessionStorage persistence for tab lifetime | | 95% | | @usefy/use-cookie | Browser cookie as React state, SSR-aware | | 96% | | @usefy/use-click-any-where | Document-wide click event detection | | 92% | | @usefy/use-copy-to-clipboard | Clipboard copy with fallback support | | 88% | | @usefy/use-event-listener | DOM event listener with auto cleanup | | 96% | | @usefy/use-on-click-outside | Outside click detection for modals/dropdowns | | 98% | | @usefy/use-unmount | Execute callback on component unmount | | 100% | | @usefy/use-init | One-time initialization with async, retry, timeout | | 96% | | @usefy/use-timer | Countdown timer with drift compensation and formats | | 84% | | @usefy/use-geolocation | Device geolocation with real-time tracking and distance | | 90% | | @usefy/use-intersection-observer | Element visibility detection with Intersection Observer | | 94% | | @usefy/use-signal | Event-driven communication between components | | 98% | | @usefy/use-memory-monitor | Real-time browser memory monitoring with leak detection | | 90% | | @usefy/use-hover | Element hover detection with delay support | | 92% | | @usefy/use-key-press | Keyboard key, shortcut, and combination detection | | 93% | | @usefy/use-map | Map data structure state with immutable updates | | 100% | | @usefy/use-set | Set data structure state with immutable updates | | 100% | | @usefy/use-list | Array state with push/filter/sort/insertAt/updateAt | | 100% | | @usefy/use-queue | FIFO queue state with enqueue/dequeue and immutable updates | | 100% | | @usefy/use-history-state | Undo/redo state history with time-travel | | 100% | | @usefy/use-step | Multi-step navigation for wizards, forms, carousels | | 100% | | @usefy/use-window-size | Window size tracking with debounce/throttle and SSR | | 100% | | @usefy/use-is-client | True once hydrated on the client (SSR guard) | | 100% | | @usefy/use-isomorphic-layout-effect | SSR-safe useLayoutEffect | | 100% | | @usefy/use-previous | Previous distinct value (last render it changed) | | 100% | | @usefy/use-latest | Ref that always holds the latest value | | 100% | | @usefy/use-event-callback | Stable callback that sees the latest state | | 100% | | @usefy/use-update-effect | useEffect that skips the first render | | 100% | | @usefy/use-mount | Run a callback once on mount | | 100% | | @usefy/use-is-first-render | True only on the first render | | 100% | | @usefy/use-media-query | Match CSS media queries (matchMedia, SSR-safe) | | 100% | | @usefy/use-preferred-color-scheme | System color scheme (prefers-color-scheme) | | 100% | | @usefy/use-reduced-motion | Reduced-motion preference (a11y) | | 100% | | @usefy/use-dark-mode | Dark mode: system/light/dark, persistence, DOM apply | | 94.8% | | @usefy/use-document-title | Set document.title with restore-on-unmount | | 92% | | @usefy/use-controllable-state | Controlled/uncontrolled state primitive (Radix/Mantine pattern) | | 100% | | @usefy/use-merged-refs | Merge multiple refs into one (forwardRef helper) | | 100% | | @usefy/use-disclosure | open/close/toggle state for modals, drawers, popovers | | 100% | | @usefy/use-measure | Reactive element bounds (x, y, width, height, top, right, bottom, left) via ResizeObserver | | 100% | | @usefy/use-mutation-observer | Watch an element for DOM mutations (childList/attributes/characterData) via MutationObserver | | 99% | | @usefy/use-scroll-position | Throttled scroll offset (x, y) of the window or an element | | 100% | | @usefy/use-scroll-lock | Lock body scroll for modals/drawers — iOS-aware, nested-lock counted, with scroll-position restore | | 100% | | @usefy/use-hotkeys | High-level keyboard shortcuts — combos, sequences, mod alias, scoping, input-field guard | | 100% | | @usefy/use-focus-trap | Trap keyboard focus inside a subtree (modals/dialogs) — Tab cycling, initial focus, restore on close | | 100% | | @usefy/use-focus-within | Track whether keyboard focus is anywhere within a subtree — reactive :focus-within with onFocus/onBlur edges | | 98% | | @usefy/use-long-press | Long-press ("press and hold") gestures for mouse and touch — time threshold, movement cancellation, onStart/onFinish/onCancel | | 100% | | @usefy/use-async-fn | Run a manual-trigger async function with idle/pending/success/error lifecycle, race-safe stale-response guarding, and unmount safety | | 100% | | @usefy/use-async | Full async task lifecycle — object-style state, immediate auto-run, and AbortController cancellation | | 100% | | @usefy/use-polling | Poll an async function on an interval — non-overlapping ticks, pause/resume, an enabled gate, and exponential backoff | | 97% | | @usefy/use-raf-state | A drop-in useState that batches updates to requestAnimationFrame — rapid scroll/resize/pointer/animation updates coalesce to at most one commit per frame | | 100% | | @usefy/use-object-state | Object state with immutable partial updates (patch/merge) and reset | | 100% | | @usefy/use-stack | LIFO stack state with push/pop/peek and immutable updates | | 100% | | @usefy/use-selection | Multi/single selection state for lists and tables — Set-based, checkbox-ready with indeterminate support | | 100% | | @usefy/use-infinite-scroll | Sentinel-driven infinite loading built on IntersectionObserver — fires loadMore once per intersection, respects hasMore/loading/enabled | | 100% | | @usefy/use-pagination | Headless pagination state machine — controlled/uncontrolled current page, derived pageCount, a slice-ready 0-based range, and an ellipsis-aware items pager model | | 100% | | @usefy/use-network-state | Online/offline status + Network Information API (effectiveType, downlink, saveData), SSR-safe via useSyncExternalStore | | 95% | | @usefy/use-page-visibility | Track tab/window visibility (foreground vs. background) via the Page Visibility API, with optional onChange and SSR support | | 92% | | @usefy/use-idle | Report user inactivity after a timeout, with throttled activity listeners, visibility awareness, and SSR support | | 98% | | @usefy/use-permission | Read Permissions API status with live updates — { state, status, isSupported, error }, keyed on descriptor contents, SSR-safe, accepts any permission name | | 100% | | @usefy/use-script | Load an external script with idle/loading/ready/error status, <script> deduplication across components, and ref-counted cleanup — SSR-safe and StrictMode-safe | | 96% | | @usefy/use-user-media | Camera and microphone streams — getUserMedia lifecycle, device enumeration and switching, torch, normalized errors, and a teardown guarantee (no track outlives the component) | | 97% |


Quick Start

Using the All-in-One Package

import {
  useToggle,
  useCounter,
  useDebounce,
  useLocalStorage,
  useCookie,
  useCopyToClipboard,
  useEventListener,
  useOnClickOutside,
  useIntersectionObserver,
  useHover,
  useKeyPress,
  useMap,
  useSet,
  useList,
  useQueue,
  useHistoryState,
  useStep,
  useWindowSize,
  useNetworkState,
  usePageVisibility,
  useIdle,
  usePermission,
  useScript,
  useSignal,
  useUnmount,
  useInit,
  useIsClient,
  useIsomorphicLayoutEffect,
  usePrevious,
  useLatest,
  useEventCallback,
  useUpdateEffect,
  useMount,
  useIsFirstRender,
  useMediaQuery,
  usePreferredColorScheme,
  useReducedMotion,
  useDarkMode,
  useDocumentTitle,
  useControllableState,
  useMergedRefs,
  useDisclosure,
  useMeasure,
  useMutationObserver,
  useScrollPosition,
  useScrollLock,
  useHotkeys,
  useFocusTrap,
  useFocusWithin,
  useLongPress,
  useAsyncFn,
  useAsync,
  usePolling,
  useRafState,
  useObjectState,
  useStack,
  useSelection,
  useInfiniteScroll,
  usePagination,
} from "@usefy/hooks";

function App() {
  // Boolean state management
  const { value: isOpen, toggle, setFalse: close } = useToggle(false);

  // Counter with controls
  const { count, increment, decrement, reset } = useCounter(0);

  // Debounced search
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 300);

  // Persistent theme preference
  const [theme, setTheme] = useLocalStorage("theme", "light");

  // Copy functionality
  const [copiedText, copy] = useCopyToClipboard();

  // Lazy loading image
  const { ref: imageRef, inView } = useIntersectionObserver({
    triggerOnce: true,
    rootMargin: "50px",
  });

  return (
    <div data-theme={theme}>
      {/* Modal */}
      <button onClick={toggle}>Open Modal</button>
      {isOpen && (
        <div className="modal">
          <button onClick={close}>Close</button>
        </div>
      )}

      {/* Counter */}
      <div>
        <button onClick={decrement}>-</button>
        <span>{count}</span>
        <button onClick={increment}>+</button>
      </div>

      {/* Search */}
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />

      {/* Theme Toggle */}
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
        Toggle Theme
      </button>

      {/* Copy */}
      <button onClick={() => copy("Hello World!")}>
        {copiedText ? "Copied!" : "Copy"}
      </button>

      {/* Lazy Loading */}
      <div ref={imageRef}>
        {inView && <img src="large-image.jpg" alt="Lazy loaded" />}
      </div>
    </div>
  );
}

Using Individual Packages

import { useToggle } from "@usefy/use-toggle";
import { useDebounce } from "@usefy/use-debounce";

function SearchModal() {
  const { value: isOpen, toggle } = useToggle(false);
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) {
      searchAPI(debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <>
      <button onClick={toggle}>Search</button>
      {isOpen && (
        <input value={query} onChange={(e) => setQuery(e.target.value)} />
      )}
    </>
  );
}

Features

🔄 State Management

const { value, toggle, setTrue, setFalse, setValue } = useToggle(false);

Perfect for modals, dropdowns, accordions, and switches.

const { count, increment, decrement, reset } = useCounter(0);

Ideal for quantity selectors, pagination, and score tracking.

import { useMap } from "@usefy/use-map";

const [users, { set, setAll, remove, reset, clear, get }] = useMap<string, User>([
  ["1", { id: "1", name: "Alice" }],
]);

set("2", { id: "2", name: "Bob" }); // add / overwrite
remove("1");                          // delete a key
setAll(entries);                      // replace everything
reset();                              // back to initial

Immutable updates (new Map on every change), a ReadonlyMap return type, stable action identities, lazy initialization, and no-op skipping. Perfect for keyed collections, caches, form field maps, and feature flags.

import { useSet } from "@usefy/use-set";

const [selected, { add, remove, toggle, has, clear, reset }] = useSet<string>(["1"]);

toggle("2");           // flip membership
toggle("3", true);     // force add (like DOMTokenList.toggle)
has("1");              // membership check
remove("1");           // delete

Immutable updates (new Set on every change), a ReadonlySet return type, toggle with an optional force argument, stable action identities, lazy initialization, and no-op skipping. Perfect for multi-select, tag filters, and tracking selected ids.

import { useList } from "@usefy/use-list";

const [todos, { push, removeAt, updateAt, sort, filter, reset }] = useList<Todo>([]);

push({ id: 1, text: "Hi", completed: false }); // append
updateAt(0, { ...todos[0], completed: true }); // replace at index
removeAt(0);                                    // delete at index
sort((a, b) => a.id - b.id);                    // immutable sort

Immutable updates (new array on every change), a readonly T[] return type, a rich action set (set/push/filter/sort/clear/removeAt/insertAt/updateAt/reset), set with an updater function, stable action identities, lazy init, and no-op skipping. Perfect for todo lists, editable tables, and ordered collections.

import { useQueue } from "@usefy/use-queue";

const [queue, { add, remove, peek, clear, reset }] = useQueue<Task>([]);

add(task);              // enqueue to the back (variadic: add(a, b, c))
const next = remove();  // dequeue the front and return it (undefined if empty)
peek();                 // read the front without mutating

FIFO semantics (add to the back, remove/peek from the front), immutable updates (new array on every change), a readonly T[] return type, remove that returns the dequeued item, stable action identities, lazy init, and no-op skipping. Read first/last/size directly from the queue (queue[0], queue[queue.length - 1], queue.length). Perfect for task runners, print/job queues, message buffers, and breadth-first traversals.

import { useHistoryState } from "@usefy/use-history-state";

const { state, set, undo, redo, canUndo, canRedo, goTo, history } =
  useHistoryState(initialCanvas, { limit: 50 });

set((s) => ({ ...s, dirty: true })); // record a new entry (value or updater)
undo();                               // step back
redo();                               // step forward
goTo(0);                              // jump to any point on the timeline

Undo/redo/goTo time travel over an immutable timeline, set with value-or-updater (like useState), canUndo/canRedo flags, the full history array + currentIndex, an optional limit to bound memory, clear/reset, stable control identities (safe as effect deps), and no-op skipping. Perfect for editors, drawing/design canvases, form builders, and any Ctrl/Cmd+Z experience.

import { useStep } from "@usefy/use-step";

const [step, { goToNextStep, goToPrevStep, canGoToNextStep, canGoToPrevStep, setStep, reset }] =
  useStep(4); // steps 0..3

goToNextStep();     // advance (no-op on the last step)
setStep(2);         // jump to a step (value or updater, clamped)
setStep((s) => s + 1);

0-based indexing with automatic range validation, goToNextStep/goToPrevStep plus ready-made canGoToNextStep/canGoToPrevStep flags for disabling buttons, setStep (value or updater), reset, stable control identities (safe as effect deps), resilience to a changing step count, and no-op skipping at the edges. Perfect for multi-step forms, wizards, onboarding flows, and carousels.

import { useObjectState } from "@usefy/use-object-state";

const [form, patch, reset] = useObjectState({
  name: "",
  email: "",
  subscribe: false,
});

patch({ name: "Alice" });                     // shallow-merge a partial (immutably)
patch((prev) => ({ subscribe: !prev.subscribe })); // functional updater form
reset();                                       // back to the initial object
reset({ name: "Bob", email: "", subscribe: true }); // reset to a provided object

A useState-style [state, patch, reset] tuple that sits between useState (replace the whole value) and useReducer (write a reducer). patch shallow-merges a Partial<T> immutably ({ ...prev, ...partial }) — update one field without spreading the whole object — or takes a functional updater; untouched keys are preserved by reference. reset() restores the captured initial value (lazy init is cached once), or reset(next) swaps in a provided object. patch/reset are referentially stable (safe as effect deps), and it's SSR- & StrictMode-safe. Shallow merge only — nested objects are replaced, not deep-merged; for array state use useList. Perfect for form state, settings panels, and filter/query objects.

import { useStack } from "@usefy/use-stack";

const [stack, { push, pop, peek, clear, reset }] = useStack<Snapshot>([]);

push(snapshot);       // push onto the top (variadic: push(a, b, c))
const last = pop();   // pop the top and return it (undefined if empty)
peek();               // read the top without mutating

The LIFO sibling of useQueue — identical shape, but push and pop operate on the same end (the top). LIFO semantics, immutable updates (new array on every change), a readonly T[] return type, pop that returns the popped item, stable action identities, lazy init, and no-op skipping. Read top/bottom/size directly from the stack (stack[stack.length - 1], stack[0], stack.length). Perfect for undo history, navigation/back stacks, expression evaluation, and depth-first traversals.

import { useSelection } from "@usefy/use-selection";

const {
  selected,             // selected items (derived from the current `items`)
  isSelected,           // isSelected(item) → boolean
  toggle,               // toggle(item)
  selectAll,            // select every item (no-op in single-select mode)
  clear,                // deselect all
  isAllSelected,        // every item selected (false for an empty list)
  isPartiallySelected,  // some-but-not-all → drive a header checkbox's indeterminate
} = useSelection(users, { getKey: (u) => u.id });

Set-backed selection for checkbox lists and data tables. The backing Set stores keys (via getKey, default identity for primitives), so a selection survives new object identities across renders — rebuild items every render and the selection sticks. Item-facing values (selected, isAllSelected, …) are derived from the current items, so removing a row makes it drop out automatically. Ships toggle/select/deselect/selectAll/clear, the isAllSelected/isPartiallySelected/isNoneSelected flags for an indeterminate header checkbox, and a single-selection mode (multiple: false) that replaces the selection. Immutable Set updates, stable action identities, no-op skipping, and SSR/StrictMode safe.

⏱️ Timing Utilities

const debouncedValue = useDebounce(value, 300, {
  leading: false,
  trailing: true,
  maxWait: 1000,
});

Best for search inputs, form validation, and API calls.

const debouncedFn = useDebounceCallback(callback, 300);

debouncedFn(args); // Call debounced
debouncedFn.cancel(); // Cancel pending
debouncedFn.flush(); // Execute immediately
debouncedFn.pending(); // Check if pending
const throttledValue = useThrottle(value, 100, {
  leading: true,
  trailing: true,
});

Perfect for scroll events, resize handlers, and mouse tracking.

const throttledFn = useThrottleCallback(callback, 100);
import { useTimer, ms } from "@usefy/use-timer";

const timer = useTimer(ms.minutes(5), {
  format: "MM:SS",
  autoStart: false,
  loop: false,
  onComplete: () => console.log("Time's up!"),
});

// Controls
timer.start();
timer.pause();
timer.reset();
timer.addTime(ms.seconds(10));
timer.subtractTime(ms.seconds(5));

// State
timer.time; // "05:00"
timer.progress; // 0-100
timer.isRunning; // boolean

Perfect for countdown timers, Pomodoro apps, kitchen timers, and time-based UIs with smart render optimization.

import { useTimeout } from "@usefy/use-timeout";

const { reset, clear, isPending } = useTimeout(() => {
  console.log("fired");
}, 3000);

// Pass null to disable; reset() restarts, clear() cancels.

Automatic cleanup on unmount, latest-callback ref, and conditional execution via a null delay.

import { useInterval } from "@usefy/use-interval";

const { start, stop, toggle, isRunning } = useInterval(() => {
  tick();
}, 1000);

// Pass null to disable; { immediate: true } fires once immediately;
// { autoStart: false } starts stopped.

StrictMode-safe (no leaked/duplicate timers), latest-callback ref, and automatic cleanup on unmount.

💾 Storage

const [value, setValue, removeValue] = useLocalStorage("key", initialValue, {
  serializer: JSON.stringify,
  deserializer: JSON.parse,
  syncTabs: true,
  onError: (error) => console.error(error),
});

Supports cross-tab synchronization and custom serialization.

const [value, setValue, removeValue] = useSessionStorage("key", initialValue);

Data persists during tab lifetime, isolated per tab.

const [value, setValue, remove] = useCookie("theme", {
  initialValue: "light",
  maxAge: 60 * 60 * 24 * 7, // one week
  sameSite: "strict",
});

Reads/writes document.cookie with a useState-like tuple. SSR-aware, JSON (de)serialization with a raw-string fallback, and full cookie attributes (expires/maxAge/path/domain/secure/sameSite). Same-document instances sync; cross-tab writes need polling (no storage event for cookies).

📡 Communication

import { useSignal } from "@usefy/use-signal";

// Emitter component
function RefreshButton() {
  const { emit, info } = useSignal("dashboard-refresh");
  
  return (
    <button onClick={() => emit()}>
      Refresh All ({info.subscriberCount} widgets)
    </button>
  );
}

// Subscriber component
function DataWidget() {
  const { signal } = useSignal("dashboard-refresh");
  
  useEffect(() => {
    fetchData(); // Refetch when signal changes
  }, [signal]);
  
  return <div>Widget Content</div>;
}

// With typed data payload
interface NotificationData {
  type: "success" | "error";
  message: string;
}

function NotificationEmitter() {
  const { emit } = useSignal<NotificationData>("notification");
  
  return (
    <button onClick={() => emit({ type: "success", message: "Done!" })}>
      Notify
    </button>
  );
}

function NotificationReceiver() {
  const { signal, info } = useSignal<NotificationData>("notification");
  
  useEffect(() => {
    if (signal > 0 && info.data) {
      toast[info.data.type](info.data.message);
    }
  }, [signal]);
  
  return null;
}

Perfect for: Dashboard refresh, form reset, cache invalidation, multi-step flows, and event broadcasting.

⚠️ Note: useSignal is NOT a global state management solution. It's designed for lightweight event-driven communication. For complex state management, use Context, Zustand, Jotai, or Recoil.

🖱️ Events

// Window resize event (default target)
useEventListener("resize", (e) => {
  console.log("Window resized:", window.innerWidth);
});

// Document keydown event
useEventListener(
  "keydown",
  (e) => {
    if (e.key === "Escape") closeModal();
  },
  document
);

// Element with ref
const buttonRef = useRef<HTMLButtonElement>(null);
useEventListener("click", handleClick, buttonRef);

// With options
useEventListener("scroll", handleScroll, window, {
  passive: true,
  capture: false,
  enabled: isTracking,
});

Supports window, document, HTMLElement, and RefObject targets with full TypeScript type inference.

// Basic usage - close modal on outside click
const modalRef = useRef<HTMLDivElement>(null);
useOnClickOutside(modalRef, () => onClose(), { enabled: isOpen });

// Multiple refs - button and dropdown menu
const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
useOnClickOutside([buttonRef, menuRef], () => setIsOpen(false), {
  enabled: isOpen,
});

// With exclude refs
useOnClickOutside(modalRef, onClose, {
  excludeRefs: [toastRef], // Clicks on toast won't close modal
});

Perfect for modals, dropdowns, popovers, tooltips, and context menus with mouse + touch support.

useClickAnyWhere(
  (event) => {
    if (!ref.current?.contains(event.target)) {
      closeMenu();
    }
  },
  { enabled: isOpen }
);

Ideal for closing dropdowns, modals, and context menus.

const [copiedText, copy] = useCopyToClipboard({
  timeout: 2000,
  onSuccess: (text) => toast.success("Copied!"),
  onError: (error) => toast.error("Failed to copy"),
});

const success = await copy("text to copy");

Modern Clipboard API with automatic fallback for older browsers.

⌨️ Keyboard

import { useKeyPress } from "@usefy/use-key-press";

// Single key — true while held
const escapePressed = useKeyPress("Escape");

// Combination (mod = Ctrl on Win/Linux, Cmd on macOS)
useKeyPress("mod+k", {
  preventDefault: true,
  onPress: () => openCommandPalette(),
});

// Alternative bindings — array is OR
useKeyPress(["ctrl+s", "meta+s"], {
  preventDefault: true,
  onPress: () => save(),
});

// Physical keys for game controls (layout-independent)
const forward = useKeyPress("w", { matchBy: "code" });

// Custom predicate
const digitPressed = useKeyPress((e) => /^[0-9]$/.test(e.key));

// Ignore shortcuts while typing in inputs
useKeyPress("f", { ignoreInputElements: true, onPress: openFilter });

Perfect for command palettes, editor shortcuts, modal dismissal, and game controls — with cross-platform mod, onPress/onRelease callbacks, auto-repeat handling, and blur-safe held state.

import { useHotkeys } from "@usefy/use-hotkeys";

// Command palette (mod = Ctrl on Win/Linux, Cmd on macOS)
useHotkeys("mod+k", () => openCommandPalette(), { preventDefault: true });

// Multiple bindings for one handler (array is OR)
useHotkeys(["mod+s", "ctrl+enter"], save, { preventDefault: true });

// A Gmail-style sequence: press "g" then "i"
useHotkeys("g i", () => navigate("/inbox"));

// Scoped to an element, allowed inside inputs, only while enabled
const ref = useRef<HTMLDivElement>(null);
useHotkeys("Escape", close, { target: ref, enableOnFormTags: true, enabled: isOpen });

The high-level shortcut layer over the keyboard: combos ("ctrl+shift+p", "shift+?"), space-separated sequences ("g i", "g g") with a configurable reset timeout, arrays of bindings on one handler, a cross-platform mod alias (overridable for tests), exact modifier matching ("a" never fires on Ctrl+A), and an input-field guard that ignores editable targets by default. Bind to document/window/an element/a ref. The handler is stored in a ref (no memoization needed), and it's SSR-safe and StrictMode-safe with full timer/listener cleanup.

📍 Location

import { useGeolocation } from "@usefy/use-geolocation";

// Basic usage - get current position
const { position, loading, error } = useGeolocation();

// Real-time tracking
const { position, watchPosition, clearWatch } = useGeolocation({
  immediate: false,
  watch: false,
  onPositionChange: (pos) => console.log("Position updated:", pos),
});

// Distance calculation
const { position, distanceFrom, bearingTo } = useGeolocation();

// Calculate distance to New York (in meters)
const distance = distanceFrom(40.7128, -74.006);

// Calculate bearing/direction to London (0-360 degrees)
const bearing = bearingTo(51.5074, -0.1278);

// High accuracy mode
const { position } = useGeolocation({
  enableHighAccuracy: true,
  timeout: 10000,
});

// Permission tracking
const { permission } = useGeolocation({
  onPermissionChange: (state) => {
    console.log("Permission:", state); // "prompt" | "granted" | "denied" | "unavailable"
  },
});

Perfect for location-based apps, maps, navigation, distance tracking, and geofencing with built-in Haversine distance calculation and bearing utilities.

👁️ Visibility

import { useIntersectionObserver } from "@usefy/use-intersection-observer";

// Basic usage - detect when element enters viewport
const { ref, inView, entry } = useIntersectionObserver();

// Lazy l