Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#3828·headlessui

Improve ref type safety for polymorphic components when using semantic tags with as

Author: hansolcCreated Dec 11, 2025Updated Jun 9, 2026

What package within Headless UI are you using? @headlessui/react

What version of that package are you using? v2.2.9

What browser are you using? Chrome

Describe your issue Headless UI components currently accept refs using a broad type signature like:

typescript
ref?: Ref<HTMLElement>

This works, but it does not provide strict ref type validation when the as prop is set to a specific HTML tag.

typescript
<Button as="button" ref={useRef<HTMLAnchorElement>(null)} />

This should ideally produce a type error, because:

  • as="button" → the ref should be HTMLButtonElement
  • but the user passed HTMLAnchorElement

However, since Headless UI uses Ref<HTMLElement>, the incorrect ref type is allowed.

Because Headless UI encourages polymorphism (as="button", as="a", custom components, and Fragment), I would like to propose an approach that enables strict ref inference only when as is a semantic HTML tag, without breaking the flexibility Headless UI currently provides.

Proposed solution My suggestion is to enhance this by adding TTag and conditionally enforcing strict ref types only when the tag is a semantic HTML element:

typescript
export type RefProp<
  T extends Function,
  TTag extends ElementType = any,
> =
  // If TTag is an intrinsic HTML element, infer the correct ref type
  TTag extends keyof JSX.IntrinsicElements
    ? {
        ref?: Ref<
          JSX.IntrinsicElements[TTag] extends React.DetailedHTMLProps<
            React.HTMLAttributes<infer TElement>, any
          >
            ? TElement
            : never
        >
      }
    : // Otherwise, fall back to Headless UI's existing behavior
    T extends (props: any, ref: Ref<infer RefType>) => any
      ? { ref?: Ref<RefType> }
      : never;

Then apply the updated RefProp in each component’s internal typing—for example, for Button:

typescript
export interface _internal_ComponentButton {
  <TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
    props: ButtonProps<TTag> & RefProp<typeof ButtonFn, TTag>
  ): JSX.Element
}

Example outcome

typescript
const TestButton = () => {
  const ref = useRef<HTMLButtonElement>(null)

  // ❌ Type error:
  // as="a" → expected HTMLAnchorElement
  return <Button as="a" ref={ref}>test</Button>
}

If you'd like, I can help you write a clean PR description too.

Source: tailwindlabs/headlessui

View original on GitHubView discussion on GitHub