DEV Community

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

Posted on • Edited 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 { ComponentProps, splitProps } from "solidjs"

interface ButtonProps extends 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

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

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 🤦

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more