DEV Community

Cover image for Typescript and HTML attributes in various frameworks
Bao Huynh Lam
Bao Huynh Lam

Posted on

Typescript and HTML attributes in various frameworks

If you have worked with Typescript in a JavaScript framework - React, Vue, Solid, or Svelte - then you might have needed/will need to create wrapper/mirror components that encompasses native HTML elements.

For example: An enhanced <Link> wrapper over the <a> element. Or a custom <Button> component that behaves like a <button> - and be able accept all relevant attributes like type, style, aria-, etc. - but also allow passing in your own custom variant prop.

In such case, you would want to Typescript your component's props to extend the list of attributes that the underlying HTML component accepts.

Fortunately, all frameworks offer type helpers to achieve this.

React

React provides multiple helpers, including ComponentProps, or the more specific ComponentPropsWithoutRef if you don't want to accept a ref prop. They both requires one type parameter which is a string corresponding to an HTML element.

interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
  specialProp?: string;
}

export function Button(props: ButtonProps) {
  const {specialProp, ...rest} = props;
  return <button {...rest}>{Button Text}</button>
}
Enter fullscreen mode Exit fullscreen mode

Solid

Solid provides similar a helper with its JSX.ComponentProps type

import { JSX, splitProps } from "solidjs"

interface ButtonProps extends JSX.ComponentProps<'button'> {
  specialProp?: string;
}

export function Button(props: ButtonProps) {
  const [local, others] = splitProps(props, ['specialProp']);
  return <button {...others}>{Button Text}</button>
}
Enter fullscreen mode Exit fullscreen mode

Svelte

Svelte takes a bit different approach where you can import the direct interface from "svelte/elements" without using a type parameter

<!-- ButtonComponent.svelte -->
<script lang="ts">
  import { HTMLButtonAttributes } from 'svelte/elements'
  interface ButtonProps extends HTMLButtonAttributes {
    specialProp?: string;
  }

  const {specialProp, ...rest}: ButtonProps = $props()
</script>

<button {...rest}>Button Text</button>
Enter fullscreen mode Exit fullscreen mode

Vue

Vue has automatic "fallthrough" attribute, so any prop that you don't explicitly define will automatically get passed through the root element in the template. No Typescript involved!

<script lang="ts">
  import { defineComponent } from 'vue';

  export default defineComponent({
    name: 'MyComponent',
    props: {
      customProp: String, // Just need to define the custom prop
    },
   });
</script>

<template>
  <!--
   Attributes not matching the `props` object above
   will automatically fall to our button
  -->
  <button>Button Text</button>
</template>
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Small correction in Svelte code:

<!-- ButtonComponent.svelte -->
<script lang="ts">
  import { HTMLButtonAttributes } from 'svelte/elements'
  interface ButtonProps extends HTMLButtonAttributes {
    specialProp?: string;
  }

  const {specialProp, ...rest}: ButtonProps = $props()
</script>

<button {...rest}>Button Text</button>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
baohuynhlam profile image
Bao Huynh Lam

Thanks. Somehow I just forgot to use the interface 🤦