It's a button, it's an airplane, it's polymorphism

Do you want a Button that can switch between <button>, <a> and <Link>? You need a polymorphic component. Ideally you need one with TypeScript support so that you get type safety and autocomplete.

Using an as or component prop is not too difficult in plain JavaScript flavored React code.

export function Button({as, children}) {
    const Component = as || 'button';
    return <Component>{children}</Component>
}

I was not able to find a first party example of how to make a polymorphic component. With the new new react.dev docs, hopefully this should all be easier and better documented.

Here’s how I handle this, based on this original Stackoverflow post by Gabin, with a few minor modifications.

interface ButtonProps<T extends React.ElementType> extends React.PropsWithChildren {
  /**
   * polymorphic prop; supply overriding html element or react component
   */
  as?: T;
}

function Button<T extends React.ElementType = "button">({
  as,
  ...props
}:
  ButtonProps<T>
  & Omit<React.ComponentPropsWithoutRef<T>, keyof ButtonProps<T>>
) {
  const Component = as ?? "button";
  return <Component {...props} />;
}

Now the Button can be flip flopped from <button> to <a> or even <Link> and the appropriate props will be required or optional as necessary.

function Example() {
  return (
    <>
      <Button>button as a button</Button>
      <Button as="a" href="/">button as an anchor</Button>
      <Button as={Link} to="/">button as a Link</Button>
    </>
  );
}

Next up, combining this approach with cva.

Follow me on Mastodon @ryanmr@mastodon.cloud.

Follow me on Twitter @ryanmr.