tailwindlabs / headlessui

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
https://headlessui.com
MIT License
25.81k stars 1.07k forks source link

Type error: incorrect typing of the `as` property of Button #3359

Closed dd-jonas closed 2 months ago

dd-jonas commented 2 months ago

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

v2.1.1

What browser are you using?

N/A

Reproduction URL

https://stackblitz.com/edit/vitejs-vite-jenhnx?file=src%2FApp.tsx

Describe your issue

The Button component has an as property which can be passed an element or component. However, the typing only seems to work when passing it directly to the Button component. When using a wrapping element that accepts the same ButtonProps and passes all props to the HeadlessUI Button, TypeScript shows an error when using the as property (e.g. "div" is not assignable to "button", see repro above). I would expect the Button wrapper to also accept any valid element or component.

I haven't checked this with any other component that accepts an as property.

RobinMalfait commented 2 months ago

Hey!

The ButtonProps default to button as the underlying tag. If you want to create your own component that takes an as prop, you have to make sure that the component is generic.

You can update your component to accept a generic element in the as prop. Also make sure you are using forwardRef:

import { type ElementType, type ForwardedRef, forwardRef } from 'react';

const MyButton = forwardRef(function MyButton<T extends ElementType = 'button'>(
  props: ButtonProps<T>,
  ref: ForwardedRef<HTMLElement>
) {
  return <Button ref={ref} {...props} />;
});

Hope this helps!

dd-jonas commented 2 months ago

@RobinMalfait Thanks for the quick response!

Follow-up question: is there a way to make the Button accept the props of the as component, or is this too complex / not supported? e.g. when passing a React Router link component, the button should accept to="/" etc. It seems to work at runtime, but TypeScript does not know about these props.