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>
}
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>
}
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>
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>
Top comments (2)
Small correction in Svelte code:
Thanks. Somehow I just forgot to use the interface 🤦