Home Programming Extending the Properties of an HTML Component in TypeScript — SitePoint

Extending the Properties of an HTML Component in TypeScript — SitePoint

Extending the Properties of an HTML Component in TypeScript — SitePoint


On this fast tip, excerpted from Unleashing the Energy of TypeScript, Steve exhibits you prolong the properties of an HTML ingredient in TypeScript.

In many of the bigger purposes and initiatives I’ve labored on, I usually discover myself constructing a bunch of parts which might be actually supersets or abstractions on prime of the usual HTML parts. Some examples embody customized button parts which may take a prop defining whether or not or not that button ought to be a main or secondary button, or possibly one which signifies that it’s going to invoke a harmful motion, similar to deleting or eradicating a merchandise from the database. I nonetheless need my button to have all of the properties of a button along with the props I wish to add to it.

One other widespread case is that I’ll find yourself making a element that permits me to outline a label and an enter subject without delay. I don’t wish to re-add the entire properties that an <enter /> ingredient takes. I need my customized element to behave identical to an enter subject, however additionally take a string for the label and routinely wire up the htmlFor prop on the <label /> to correspond with the id on the <enter />.

In JavaScript, I can simply use {...props} to cross by means of any props to an underlying HTML ingredient. This generally is a bit trickier in TypeScript, the place I have to explicitly outline what props a element will settle for. Whereas it’s good to have fine-grained management over the precise varieties that my element accepts, it may be tedious to have so as to add in kind data for each single prop manually.

In sure eventualities, I would like a single adaptable element, like a <div>, that modifications kinds in response to the present theme. For instance, possibly I wish to outline what kinds ought to be used relying on whether or not or not the person has manually enabled gentle or darkish mode for the UI. I don’t wish to redefine this element for each single block ingredient (similar to <part>, <article>, <apart>, and so forth). It ought to be able to representing totally different semantic HTML parts, with TypeScript routinely adjusting to those modifications.

There are a few methods that we will make use of:

  • For parts the place we’re creating an abstraction over only one form of ingredient, we will prolong the properties of that ingredient.
  • For parts the place we wish to outline totally different parts, we will create polymorphic parts. A polymorphic element is a element designed to render as totally different HTML parts or parts whereas sustaining the identical properties and behaviors. It permits us to specify a prop to find out its rendered ingredient kind. Polymorphic parts provide flexibility and reusability with out us having to reimplement the element. For a concrete instance, you possibly can have a look at Radix’s implementation of a polymorphic element.

On this tutorial, we’ll have a look at the primary technique.

Mirroring and Extending the Properties of an HTML Component

Let’s begin with that first instance talked about within the introduction. We wish to create a button that comes baked in with the suitable styling to be used in our software. In JavaScript, we would be capable to do one thing like this:

const Button = (props) => {
  return <button className="button" {...props} />;

In TypeScript, we might simply add what we all know we’d like. For instance, we all know that we’d like the kids if we wish our customized button to behave the identical method an HTML button does:

const Button = ({ kids }: React.PropsWithChildren) => {
  return <button className="button">{kids}</button>;

You possibly can think about that including properties one by one might get a bit tedious. As an alternative, we will inform TypeScript that we wish to match the identical props that it will use for a <button> ingredient in React:

const Button = (props: React.ComponentProps<'button'>) => {
  return <button className="button" {...props} />;

However now we have a brand new downside. Or, quite, we had an issue that additionally existed within the JavaScript instance and which we ignored. If somebody utilizing our new Button element passes in a className prop, it is going to override our className. We might (and we are going to) add some code to cope with this in a second, however I don’t wish to cross up the chance to point out you use a utility kind in TypeScript to say “I wish to use all of the props from an HTML button besides for one (or extra)”:

kind ButtonProps = Omit<React.ComponentProps<'button'>, 'className'>;

const Button = (props: ButtonProps) => {
  return <button className="button" {...props} />;

Now, TypeScript will cease us or anybody else from passing a className property into our Button element. If we simply wished to increase the category record with no matter is handed in, we might do this in a number of alternative ways. We might simply append it to the record:

kind ButtonProps = React.ComponentProps<'button'>;

const Button = (props: ButtonProps) => {
  const className="button " + props.className;

  return <button className={className.trim()} {...props} />;

I like to make use of the clsx library when working with courses, because it takes care of most of those sorts of issues on our behalf:

import React from 'react';
import clsx from 'clsx';

kind ButtonProps = React.ComponentProps<'button'>;

const Button = ({ className, ...props }: ButtonProps) => {
  return <button className={clsx('button', className)} {...props} />;

export default Button;

We discovered restrict the props {that a} element will settle for. To increase the props, we will use an intersection:

kind ButtonProps = React.ComponentProps<'button'> &  'secondary';

We’re now saying that Button accepts the entire props {that a} <button> ingredient accepts plus yet one more: variant. This prop will present up with all the opposite props we inherited from HTMLButtonElement.

Variant shows up as a prop on our Button component

We are able to add help to our Button so as to add this class as effectively:

const Button = ({ variant, className, ...props }: ButtonProps) => {
  return (
        variant === 'main' && 'button-primary',
        variant === 'secondary' && 'button-secondary',

We are able to now replace src/software.tsx to make use of our new button element:

diff --git a/src/software.tsx b/src/software.tsx
index 978a61d..fc8a416 100644
--- a/src/software.tsx
+++ b/src/software.tsx
@@ -1,3 +1,4 @@
+import Button from './parts/button';
 import useCount from './use-count';

 const Counter = () => {
@@ -8,15 +9,11 @@ const Counter = () => {
       <p className="text-7xl">{depend}</p>
       <div className="flex place-content-between w-full">
-        <button className="button" onClick={decrement}>
+        <Button onClick={decrement}>
-        </button>
-        <button className="button" onClick={reset}>
-          Reset
-        </button>
-        <button className="button" onClick={increment}>
-          Increment
-        </button>
+        </Button>
+        <Button onClick={reset}>Reset</Button>
+        <Button onClick={increment}>Increment</Button>
@@ -32,9 +29,9 @@ const Counter = () => {
           <label htmlFor="set-count">Set Rely</label>
           <enter kind="quantity" id="set-count" identify="set-count" />
-          <button className="button-primary" kind="submit">
+          <Button variant="main" kind="submit">
-          </button>
+          </Button>

Yow will discover the modifications above in the button department of the GitHub repo for this tutorial.

Creating Composite Parts

One other widespread element that I usually find yourself making for myself is a element that accurately wires up a label and enter ingredient with the right for and id attributes respectively. I are likely to develop weary typing this out time and again:

<label htmlFor="set-count">Set Rely</label>
<enter kind="quantity" id="set-count" identify="set-count" />

With out extending the props of an HTML ingredient, I would find yourself slowly including props as wanted:

kind LabeledInputProps =  quantity;
  kind?: string;
  className?: string;
  onChange?: ChangeEventHandler<HTMLInputElement>;

As we noticed with the button, we will refactor it in a similar way:

kind LabeledInputProps = React.ComponentProps<'enter'> & {
  label: string;

Apart from label, which we’re passing to the (uhh) label that we’ll usually need grouped with our inputs, we’re manually passing props by means of one after the other. Can we wish to add autofocus? Higher add one other prop. It might be higher to do one thing like this:

import { ComponentProps } from 'react';

kind LabeledInputProps = ComponentProps<'enter'> & {
  label: string;

const LabeledInput = ({ id, label, ...props }: LabeledInputProps) => {
  return (
      <label htmlFor={id}>{label}</label>
      <enter {...props} id={id} readOnly={!props.onChange} />

export default LabeledInput;

We are able to swap in our new element in src/software.tsx:

  label="Set Rely"
  onChange={(e) => setValue(e.goal.valueAsNumber)}

We are able to pull out the issues we have to work with after which simply cross all the pieces else on by means of to the <enter /> element, after which simply faux for the remainder of our days that it’s an ordinary HTMLInputElement.

TypeScript doesn’t care, since HTMLElement is fairly versatile, because the DOM pre-dates TypeScript. It solely complains if we toss one thing utterly egregious in there.

You possibly can see the entire modifications above in the enter department of the GitHub repo for this tutorial.

This text is excerpted from Unleashing the Energy of TypeScript, accessible on SitePoint Premium and from e book retailers.


Supply hyperlink


Please enter your comment!
Please enter your name here