Make Button default to 'button', but allow swapping the tag via as. Tie props and ref to the chosen element using a generic C extends ElementType. This keeps both props and ref fully typed for links, divs, or custom components.
Example code
import React, { forwardRef } from 'react';
type PolymorphicProps<C extends React.ElementType, P> = {
as?: C;
} & Omit<React.ComponentPropsWithoutRef<C>, 'as'> & P;
type ButtonOwnProps = { variant?: 'solid' | 'ghost' };
type ButtonComponent = <C extends React.ElementType = 'button'>(
props: PolymorphicProps<C, ButtonOwnProps> & { ref?: React.ComponentPropsWithRef<C>['ref'] }
) => React.ReactElement | null;
export const Button: ButtonComponent = forwardRef(
<C extends React.ElementType = 'button'>({ as, variant = 'solid', ...rest }: PolymorphicProps<C, ButtonOwnProps>, ref: React.ComponentPropsWithRef<C>['ref']) => {
const Comp = as ?? 'button';
return <Comp ref={ref} data-variant={variant} {...rest} />;
}
) as any;
// Usage: <Button>OK</Button> | <Button as="a" href="#">Link</Button>