-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
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:
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.
<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:
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:
export interface _internal_ComponentButton {
<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
props: ButtonProps<TTag> & RefProp<typeof ButtonFn, TTag>
): JSX.Element
}Example outcome
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.