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

@quickadui/hooks

v0.1.0

Published

Framework-agnostic utility hooks (useDisclosure, useMediaQuery, useDebounce, and friends).

Downloads

121

Readme

@quickadui/hooks

Framework-agnostic React utility hooks with no styling, no Radix dependency, and no dependency on any other @quickadui/* package — safe to pull into any React 18/19 app on its own, and the foundation several upcoming QuickadUI packages (forms, layout) build on internally.

Hooks

useIsomorphicLayoutEffect

useLayoutEffect in the browser, useEffect during SSR — avoids the React warning useLayoutEffect triggers when it runs on the server, without giving up synchronous pre-paint DOM measurement on the client. Drop-in replacement for useLayoutEffect anywhere a component might be server-rendered.

import { useIsomorphicLayoutEffect } from "@quickadui/hooks";

function Measured() {
  useIsomorphicLayoutEffect(() => {
    // measure the DOM before paint, safe under SSR
  }, []);
}

useMediaQuery(query, options?)

Subscribes to a CSS media query and re-renders when it changes. Built on useSyncExternalStore (not useState + useEffect), so it's correct under concurrent rendering and never shows a stale value for one frame after mount.

import { useMediaQuery } from "@quickadui/hooks";

function Responsive() {
  const isDesktop = useMediaQuery("(min-width: 1024px)");
  return isDesktop ? <DesktopNav /> : <MobileNav />;
}

options.defaultValue (default false) is what's returned before the query can be evaluated — during SSR, or when window.matchMedia isn't available.

useDisclosure(options?)

Open/close boolean state with stable open/close/toggle callbacks and matching onOpen/onClose side effects — the state machine behind a Dialog/Popover/Drawer trigger, decoupled from any particular overlay primitive.

import { useDisclosure } from "@quickadui/hooks";

function Example() {
  const { isOpen, open, close, toggle } = useDisclosure({ defaultOpen: false });
  return (
    <>
      <button onClick={open}>Open</button>
      {isOpen && <Panel onClose={close} />}
    </>
  );
}

Callbacks are referentially stable across renders (useCallback), so passing them into a useEffect dependency array or a memoized child doesn't cause needless re-runs.

useDebouncedValue(value, delayMs)

Returns a copy of value that only updates after delayMs of no further changes — e.g. driving a search request off a text input without firing one per keystroke. The first render always returns value itself; there's no initial delay before the first value is usable.

import { useDebouncedValue } from "@quickadui/hooks";

function Search({ query }: { query: string }) {
  const debounced = useDebouncedValue(query, 300);
  // fetch(debounced) in an effect
}

useControllableState({ value?, defaultValue?, onChange? })

The controlled/uncontrolled duality every interactive component ends up needing (<input value={} onChange={}> vs <input defaultValue={}>) — Radix's own primitives use the same pattern internally, which is exactly why @quickadui/primitives never needed to expose it directly. QuickadUI's own stateful components that aren't themselves Radix wrappers use this instead of reinventing it per component.

import { useControllableState } from "@quickadui/hooks";

function useMyToggle(props: { checked?: boolean; onCheckedChange?: (v: boolean) => void }) {
  return useControllableState<boolean>({
    value: props.checked,
    defaultValue: false,
    onChange: props.onCheckedChange,
  });
}

Passing value (even false/0, anything not undefined) switches the hook into controlled mode: the caller owns the value, and setValue only calls onChange — it does not update what the hook returns until the caller feeds the new value back in via value on the next render. Whichever mode is active on mount is sticky; switching a component between controlled and uncontrolled after the fact isn't specially handled, same as React's own guidance for native form inputs.

Install

pnpm add @quickadui/hooks

react and react-dom (>=18) are peer dependencies.