Skip to content

composeRefs

Compose multiple React refs into a single ref callback. Essential when a component needs an internal ref and must also forward an external ref prop.

composeRefs merges any number of React refs into one callback. It is the low-level utility used to construct a composed ref. In component render call sites, use useComposedRefs so the callback identity stays stable across renders.

It is used internally by select, menu, tabs, radio, checkbox, navigation-list, and command-palette and is installed automatically when you add any of those components.

Installation

Info:

Before publication: Diffgazer packages are not yet published to npm, so there is no dgadd bin until you install one. Follow the canonical Copy-first mode procedure to build and pack @diffgazer/add, then install that packed tarball in the target app. The target-app install is what puts dgadd on pnpm exec.

$pnpm exec dgadd add ui/compose-refs
[Installs to]src/lib/compose-refs.ts[Item]ui/compose-refs

dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.

UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.

The file is placed at lib/compose-refs.ts in your project.

The render-time recipes below use the companion hook. Install it with pnpm exec dgadd add ui/composed-refs; its registry item includes compose-refs.


API

ts
function composeRefs<T>(
  ...refs: Array<React.Ref<T> | null | undefined>
): React.RefCallback<T>

Parameters

ParameterTypeDescription
...refsArray<Ref<T> | null | undefined>Any number of refs to compose. Each can be a callback ref, a ref object (useRef), null, or undefined.

Returns

A single RefCallback<T> that, when called with a DOM element (or null on unmount), forwards the value to every ref in the list.


Usage

Forward an external ref while keeping an internal one

tsx
import { useRef } from "react";
import { useComposedRefs } from "@/hooks/use-composed-refs";

interface ListProps {
  ref?: React.Ref<HTMLDivElement>;
}

function List({ ref, ...props }: ListProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const composedRef = useComposedRefs(containerRef, ref);

  return <div ref={composedRef} role="listbox" {...props} />;
}

Merge a context ref with an external ref

tsx
import { useComposedRefs } from "@/hooks/use-composed-refs";
import { useMyContext } from "./context";

function PaletteList({ ref }: { ref?: React.Ref<HTMLDivElement> }) {
  const { listRef } = useMyContext();
  const composedRef = useComposedRefs(listRef, ref);

  return <div ref={composedRef} role="listbox" />;
}

Three refs at once

tsx
function List({ ref }: { ref?: React.Ref<HTMLDivElement> }) {
  const localRef = useRef<HTMLDivElement>(null);
  const { listRef } = useMyContext();
  const composedRef = useComposedRefs(localRef, listRef, ref);

  return <div ref={composedRef} />;
}

Notes

  • Stable render-time refs — use useComposedRefs when the composed callback is passed from a component render. Calling composeRefs inline creates a new callback, so React detaches and reattaches it on every render.
  • Low-level construction — use composeRefs when callback identity is already stable, including inside the useComposedRefs implementation.
  • No forwardRef needed — both utilities support the React 19 ref-as-prop pattern.
  • Null-safenull and undefined refs are silently skipped. You can pass an optional consumer ref without guarding.
  • Order-independent — refs are called left-to-right, but all receive the same element value, so order only matters if you have side effects in a callback ref.
  • No runtime dependencies — pure TypeScript, no React peer API beyond the Ref types.