heroui-inc/heroui · warning

Ref was not connected to DOM element returned by custom `ren

Error message

Ref was not connected to DOM element returned by custom `render` function. Did you forget to pass through or merge the `ref`?

What it means

When a component's `render` prop replaces the default DOM element, HeroUI merges its forwarded ref into the rendered output via mergeRefs. A layout effect checks (in non-production builds) that the ref actually got attached; if the custom render result never receives the ref, the internal element reference stays null and this console warning fires, because features like focus, measurement, or positioning that rely on the ref will silently break.

Source

Thrown at packages/react/src/utils/dom.tsx:45

   * * Only a single root DOM element can be rendered (no fragments).
   * * You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate.
   */
  render?: DOMRenderFunction<E, T>;
}

// eslint-disable-next-line react-refresh/only-export-components
function DOMElement(
  ElementType: string,
  props: DOMRenderProps<any, any> & AllHTMLAttributes<HTMLElement> & {ref?: React.Ref<HTMLElement>},
) {
  const {ref: forwardedRef, render, ...otherProps} = props;
  const elementRef = useRef<HTMLElement | null>(null);
  const ref = useMemo(() => mergeRefs(forwardedRef, elementRef), [forwardedRef, elementRef]);

  useLayoutEffect(() => {
    if (typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production" && render) {
      if (!elementRef.current) {
        console.warn(
          "Ref was not connected to DOM element returned by custom `render` function. Did you forget to pass through or merge the `ref`?",
        );
      }
    }
  }, [ElementType, render]);

  const domProps: any = {...otherProps, ref};

  if (render) {
    return render(domProps, undefined);
  }

  return <ElementType {...domProps} />;
}

type DOMComponents = {
  [E in keyof React.JSX.IntrinsicElements]: (
    props: DOMRenderProps<E, any> & React.JSX.IntrinsicElements[E],

View on GitHub (pinned to 7546fff813)

Solutions

  1. Pass all props through: render={(props) => <MyDiv {...props} />} so ref and other injected props reach the DOM
  2. If the render target is your own component, wrap it in React.forwardRef and attach the ref to its root DOM node
  3. Ensure any intermediate wrapper forwards refs (avoid plain function components that discard ref in React 19- setups)

Example fix

// before
<TooltipTrigger render={(props) => <StyledSpan>{props.children}</StyledSpan>} />

// after
<TooltipTrigger render={(props) => <StyledSpan {...props} />} />
Defensive patterns

Strategy: validation

Validate before calling

// Always forward the full props object to the render target so ref is included:
<Comp render={(props) => <Custom {...props} />} />;

Type guard

// For custom components, ensure forwardRef is used:
const Custom = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
  (props, ref) => <span ref={ref} {...props} />,
);

Prevention

When it happens

Trigger: Passing render={(props) => <MyDiv />} or render={<MyDiv />} where MyDiv does not accept/forward props.ref to its DOM element; forwarding only some props (e.g. spreading {...rest} but omitting ref, or using a component that doesn't forward refs).

Common situations: Using a styled custom element or non-forwardRef function component as the render target; memoized wrappers that drop the ref; converting render props from spread syntax to explicit props and forgetting ref.

Related errors


AI-assisted analysis of heroui-inc/heroui@7546fff813 (2026-08-28). Data as JSON: /api/errors/b92abc6e9775a9f9. Report an issue: GitHub.