How to Type HTML Elements

On a recent project, I had a link that I wanted to turn into a styled component that included an icon afterwards.

Jump to solution

The problem with the first attempt was obvious. I wasn’t typing my React component at all:

const JumpDownLink: React.FC = ({children, ...props}) => {
  return (
    <JumpDownLinkAnchor {...props}>
      {children}
      <JumpDownIcon />
    </JumpDownLinkAnchor>
  )
}

Thus, attempting to use my component and pass in any of the a attributes, such as href, resulted in the error

TS2322: Type '{ children: string; href: string; }' is not assignable to type 
'IntrinsicAttributes & { children?: ReactNode; }'. Property 'href' does not 
exist on type 'IntrinsicAttributes & { children?: ReactNode; }'.

To fix that, I needed to indicate type my component so that any standard a attribute could be set as a prop.

This can actually be done quite easily using React.HTMLProps. That got me closer to a working solution:

const JumpDownLink: React.FC<React.HTMLProps<HTMLAnchorElement>> 
  = ({children, ...props}) => {
  return (
    <JumpDownLinkAnchor {...props}>
      {children}
      <JumpDownIcon />
    </JumpDownLinkAnchor>
  )
}

However, because JumpDownLinkAnchor was a styled component using EmotionJS, I ran into another error: Types of property 'as' are incompatible..

React.HTMLProps extends AllHTMLAttributes, which includes a long list of possible HTML properties, including as, which is defined as a string. Emotion’s CreateStyledComponent also uses an as property but its as needs a React element type.

Since as isn’t a valid attribute on an a element anyway, the solution was to use Typescript’s Omit to remove as from the list of props allowed on the link element.

Solution

const JumpDownLink: React.FC<Omit<
  React.HTMLProps<HTMLAnchorElement>,
  'as'
>> = ({ children, ...props }) => {
  return (
    <JumpDownLinkAnchor {...props}>
      {children}
      <JumpDownIcon />
    </JumpDownLinkAnchor>
  );
};