Tag
Renders a polymorphic element chosen at runtime, with that element's own props fully typed.
API
Props
| prop | type | default | |
|---|---|---|---|
as? | ElementType | "div" | the element or component to render |
…rest | props of as | — | typed against the chosen element |
Examples
Usage
Everything that is not as is forwarded to the rendered element untouched, and typed against it: with as="a" the compiler knows about href, with as="section" it rejects it.
<Tag as="section" className="prose">
{children}
</Tag>
<Tag as="a" href="/about" rel="noreferrer">
About
</Tag>Details
TagProps
The exported TagProps<T> type builds prop types for your own polymorphic components.
type ButtonProps<T extends ElementType = "button"> = TagProps<T> & { active?: boolean };It takes a second parameter for the props your component adds on top of the element. Those own props win over native ones of the same name: they replace them rather than merging with them, so a component that gives onClick its own meaning does not end up with two conflicting signatures for it.
type ButtonOwnProps = {
active?: boolean;
onClickBlur?: MouseEventHandler<HTMLElement>;
};
type ButtonProps<T extends ElementType = "button"> = TagProps<T, ButtonOwnProps>;Tag.forward
A component built on Tag destructures its own props and has to hand the rest over. Spreading that rest directly does not type-check: TypeScript cannot prove that the leftovers of a generic component match ComponentProps<T>, because T stays unresolved until someone instantiates the component. Tag.forward is where that assertion lives, once, instead of in every component built on Tag.
Tag.forward<T extends ElementType>(props: object, fallback?: ElementType): TagProps<T>;export const Card = <T extends ElementType = "div">({ className, children, ...rest }: CardProps<T>) => (
<Tag {...Tag.forward<T>(rest)} className={cn(cardBaseClassName, className)}>
{children}
</Tag>
);It leaves as alone: whatever the consumer chose passes through untouched. The optional second argument is the element to fall back to when the consumer chose nothing — pass it when your component renders something other than a div by default.
<Tag {...Tag.forward<T>(rest, "button")} onClick={handleClick} />Because the fallback only applies in the absence of as, a Button stays a button by default and still becomes a router Link the moment someone asks for one, without the component knowing anything about routing.
The default element
Without as the element falls back to a div, so a component built on Tag is never in an invalid state while the consumer has not chosen yet.