Hooks
Custom React hooks exported from @ngrok/mantle/hooks.
Overview
useBreakpoint
The largest Tailwind breakpoint the viewport width matches, or "default" below 2xs. Shares one app-wide subscription to the min-width media queries.
useIsBelowBreakpoint
true while the viewport is narrower than the given breakpoint. Accepts a TailwindBreakpoint ("2xs", "xs", "sm", "md", "lg", "xl", "2xl").
useCallbackRef
Returns a memoized callback that always refers to the latest callback passed to the hook. Useful when passing a callback that may or may not be memoized to a child component without causing re-renders.
useComposedRefs
Composes multiple React refs (callback refs and RefObjects) into a single stable callback ref using useCallback. Useful when a component accepts a ref prop while also keeping an internal one.
useCopyToClipboard
Returns a stable async function that copies a string to the clipboard. Uses the Clipboard API when available and falls back to a polyfill for older browsers. await the call to observe success; it throws when copying fails.
useDebounce
Returns a debounced copy of a value: it only updates after the value has stopped changing for waitMs milliseconds. Rapid successive changes collapse into a single trailing update. To debounce a function instead of a value, use useDebouncedCallback.
useDebouncedCallback
Creates a debounced version of a callback function. Delays execution until a period of inactivity has passed (options.waitMs). The debounced callback is stable and safe to use in dependency arrays.
useFocusTrap
Keeps keyboard focus inside the element ref points at while active is true (the default). On
activation, when focus is outside the element, it moves focus to the element's first tab stop, else to the
element itself, which gets tabindex="-1" for the trap's lifetime when it has none. An element that opens
with a transition can hold nothing focusable in the commit that activates the trap, so the move retries for
up to a second of frames until focus lands. While active, Tab on the last tab stop wraps to the first and
Shift+Tab on the first wraps to the last, in document order. A child that prevents the default on Tab
keeps it. On release the trap leaves focus where it is, so the caller decides where focus returns.
It traps the keyboard only. Focus that a pointer or a script moves out of the element stays out, so a menu,
a popover, or a dialog that portals out of the element keeps working. Pair it with
useInertSiblings to block pointer focus, the way
AppLayout.SidePanel does when it covers the page. Flip active
to engage and release the trap on one mounted element: a panel that covers the page at one viewport width
and sits beside it at another keeps its DOM.
useInertSiblings
Sets inert on every other child of the element's parent while active is true (the default), so
nothing beside the element is clickable, focusable, or read by assistive technology. A sibling that mounts
while the hook is active goes inert too. On release it clears inert from the siblings it set and leaves
alone one that was inert already.
It reaches siblings only, not the rest of the document, so it fits a surface that covers one row of a
layout in place: a panel that covers the card and the rail beside it, with a notice above the row still
usable. Pair it with useFocusTrap to keep keyboard focus inside the element; the example
above does both.
useIsHydrated
Returns whether the component tree has been hydrated on the client. Returns false on the server and true after hydration on the client. Uses useSyncExternalStore to prevent hydration mismatches. To gate a CSS transition on a post-hydration state correction, use useIsHydratedAfterPaint instead. This hook flips in the same commit as the correction, so the transition runs.
useIsHydratedAfterPaint
Returns false on the server, in the hydration render, and in the mount commit, then true from the next animation frame.
Use it to gate a CSS transition that must not run on a post-hydration state correction. useIsHydrated flips in the hydration commit. A useLocalStorage or any other useSyncExternalStore correction lands in that same commit. CSS starts a transition from the commit's after-change style, so the transition runs. One frame later the correction has painted, so a gate keyed on this result opens on settled values. For every other client-only branch, useIsHydrated is the right hook: it costs no extra render.
useIsomorphicLayoutEffect
Uses useLayoutEffect on the client and useEffect on the server. Avoids SSR warnings about useLayoutEffect doing nothing on the server.
useLocalStorage
SSR-safe localStorage-backed string state. Server rendering returns the default and never touches localStorage. Values are JSON-encoded on write and decoded on read. The setter keeps every same-key hook instance in the tab in sync, and cross-tab changes arrive via the native storage event. Missing or corrupt entries resolve to the default instead of throwing.
Reads and writes never throw. If the browser denies storage access, the value is the default. If a write fails (a full origin, Safari private browsing, blocked site data), the hook keeps the value in memory, so it still advances until the page reloads. A later write that succeeds, or a storage event from another document, restores storage as the source of truth.
useSessionStorage
The per-tab sibling of useLocalStorage with the same contract, backed by sessionStorage: values persist for the rest of the tab session and every same-key hook instance in the tab stays in sync.
useMatchesMediaQuery
Subscribes to and returns the result of a CSS media query string. Uses window.matchMedia and useSyncExternalStore for concurrent rendering compatibility.
usePrefersReducedMotion
Returns true when the user has opted out of animations (i.e., prefers reduced motion). Defaults to true during SSR and before hydration: animations are suppressed until the real preference is known on the client.
getPrefersReducedMotion
Imperatively reads the current prefers-reduced-motion preference. Useful in event handlers and plain functions where a hook cannot be called.
Returns true when the user has opted out of animations. Defaults to true in SSR environments (no DOM available), matching the conservative default of usePrefersReducedMotion.
useScrollBehavior
Returns a ScrollBehavior ("auto" or "smooth") that respects the user's reduced motion preference. Returns "auto" when the user prefers reduced motion, otherwise "smooth".
useSwipeToDismiss
Makes the element handle points at a swipe handle that drags target toward one edge and calls
onDismiss when the drag crosses threshold of the target's size or ends in a flick. A shorter drag
springs back, and a drag the wrong way meets rubber-band resistance. The target's own transition on the
property animates both the spring back and the slide the caller runs after onDismiss, so a
motion-reduce: rule on the target turns them into cuts.
The target follows the pointer one to one: while the pointer is down the hook writes the property and
transition: none on the target's inline style, and clears both on release. translate slides the target;
flex-basis shrinks a flex item from its size toward nothing, the way a lane collapses. A tap on a control
inside the handle stays a tap: the drag starts after 8px of travel along the direction, a gesture that
travels further across it never starts, and a release after a drag swallows the click that follows it. Give
the handle touch-action: pan-y (touch-pan-y) for a horizontal swipe, or pan-x for a vertical one, so
the other axis still scrolls. AppLayout.SidePanelHeader
is the handle for the side panel, with property picked at pointer down: translate while the panel
floats and flex-basis while it sits in the lane.
useInView
React hook that tracks whether a DOM element is visible within the viewport (or a scrollable container).
Returns true when the element is in view, otherwise false.
Trigger once on first entry:
useInView options:
Do not combine once: true with initial: true. With both set, the hook reports in view on mount and never observes the element.
useInView example
Live demo for the useInView hook. A scrollable container where the observed element transitions between in-view and out-of-view states as you scroll.
↓ scroll down
↑ scroll up
useUndoRedo
Generic undo/redo hook that maintains two history stacks (undo and redo). The hook does not own your application state — it helps you snapshot it. Call push(snapshot) before mutating state, then undo(current) or redo(current) return the snapshot to apply, or undefined when their stack is empty. Pushing a new snapshot clears the redo stack, matching standard editor semantics.