UK based website and application support - contact form.

Blog.

Web Components using Lit

Cover Image for Web Components using Lit

Web Component Example

When considering a good use case for a web component, I decided to create a stamp duty calculator. It’s a simple component with a few inputs and outputs, and it’s a great example of how to use web components in a real-world application.

Creating new component using Lit

Creating a new component using Lit is straightforward. Inside your project, create a new JavaScript or TypeScript file (e.g., StampDuty.js):

Most likley you will have to add styles or style library to your component. In this example I am using Tailwind CSS.

Next you can define your component properties. In this case, I have a single property called price.

firstUpdated() is a lifecycle method that is called after the component's first update. This is a good place to initialize any state or perform any other setup that requires the component's properties to be defined.

render() is a required method that returns a template literal that defines the component's HTML structure. In this case, the component renders a form with a few inputs and outputs. In mu example every time the component inputs change new value is calculated and displayed.


import { LitElement, html, css } from 'lit';
import { TWStyles } from '../../../static/twlit.js';


export class StampDuty extends LitElement {
  static get styles() {
    return [TWStyles];
  }

  static get properties() {
    return {
      price: { type: String },
    };
  }

  firstUpdated() {
    this.calculateStampDuty();
  }

  calculateStampDuty() {
   ...
  }

  updateStampDuty(event) {
    ...
  }


  render() {
    return html` <h3 class="mb-6">Stamp Duty Calculator</h3>
      <form
        @change=${this.updateStampDuty}
        @input=${this.updateStampDuty}
        class="mb-6 md:flex"
      >
        <div class="w-full md:w-1/3 mb-4 pr-4">
            <label
                for="type"
                class="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
                >I am:
            </label>

            <select
                name="type"
                id="type"
                @change=${event => this.updateStampDuty(event)}
                class="select w-full rounded shadow-sm select-bordered border-gray-300 outline-none p-2.5 focus:ring-gray-500 border-0 bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset sm:text-sm sm:leading-5"
            >
                <option value="ftb">First Time Buyer</option>
                <option value="rtb">Next Home</option>
                <option value="shb">Additional property</option>
            </select>
        </div>
        
        <div class="w-full md:w-1/3 mb-4 pr-4">

        <label for="property_price"
            class="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
            >Property price
            </label>

          <div class="relative mb-4">
            <div
              class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
            >
              <span class="text-gray-500 sm:text-sm">£</span>
            </div>
            <input
              type="text"
              name="price"
              id="property_price"
              class="block w-full rounded-md border-0 bg-white p-2.5 pl-7 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-5"
              placeholder=${this.price}
              value=${this.price}
              @input=${event => this.updatePrice(event)}
            />
          </div>
        </div>

        <div class="w-full md:w-1/3 mb-4">
        <label
            for="stampduty"
                class="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
                >Stamp duty:
            </label>

        <div name="stampduty" class="ml-2 flex align-middle items-center h-10"><span id="stamp-duty"></div>

         </div>
      </form>
   `;
  }


customElements.define('stampduty', StampDuty);

Usage

Easiest way to use this component is to add it to your html file.

<html>
<head>
...
<script type="module" src="./StampDuty.js"></script>
</head>
<body>
<stampDuty price="100000"></stampDuty>
</body>
</html>

Web Component Stamp Duty Calculator Example 1

Web Component Stamp Duty Calculator Example 2

Testing

I decided to use Playwright for my component library. Testing web componets can be tricky as in most cases we need to access shadow dom.

Playwright 1.30 introduced a new method elementHandle.evaluateHandle() which allows us to access shadow dom.

import { test, expect } from '@playwright/test';
import { html } from 'lit';

test.describe('first time buyers', () => {
  const testCases = [
    { price: '100000', expected: '0' },
    { price: '260000', expected: '0' },
    { price: '420000', expected: '0' },
    { price: '426000', expected: '50' },
    { price: '625000', expected: '10,000' },
    { price: '626000', expected: '18,800' },
    { price: '925000', expected: '33,750' },
    { price: '926000', expected: '33,850' },
    { price: '1000000', expected: '41,250' },
    { price: '1500000', expected: '91,250' },
    { price: '1501000', expected: '91,370' },
  ];

  for (const { price, expected } of testCases) {
    test(`${price}`, async ({ page }) => {
      await page.setContent(`
        <html>
          <head>
            <link rel="stylesheet" href="../static/tailwind.css">
          </head>
          <body>
            <ds-tw-stampduty price="${price}"></ds-tw-stampduty>
          </body>
        </html>
      `);

      const shadowContent = await page.locator('ds-tw-stampduty');

    });
  }
});

test.describe('moving house', () => {
  const testCases = [
    { price: '100000', expected: '0' },
    ...
    { price: '1501000', expected: '91,370' },
  ];
  
  ...
});
← Back to homepage

Looking for website and application support in the Midlands? I specialize in providing comprehensive website maintenance and support services.
Whether you need hosting solutions, web application development, security updates, or assistance with in-house or remote teams, I can help.

My expertise covers React, Next.js, JavaScript, TypeScript, Web Components, Lit, Stencil.js, Node.js, RESTful APIs, Docker, Kubernetes, and Amazon Web Services.

Get in touch!