Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

Building Web Components with Stencil

Published on March 9, 2018
Last Updated on November 30, 2022
Application Development, News

Over the past few years, web development standards have evolved so much that many of us have had a hard time catching up. Even now, there’s a new javascript framework being created somewhere in the world that’ll go live in the next few months. Yet keeping up to date is crucial in the software industry and lagging behind is not an option.


NEW RESEARCH: LEARN HOW DECISION-MAKERS ARE PRIORITIZING DIGITAL INITIATIVES IN 2024.

Get Report


As web standards have evolved, many new APIs, features, frameworks and tools have been introduced. Be it ES6, TypeScript, Webpack or libraries like Polymer, Stencil, etc. With evolving standards, one of the prominent targets of web and hybrid-mobile frameworks is to use Web Components.

What is Stencil?

Stencil is an open-source compiler that generates standards-compliant web components. Team Ionic announced Stencil during the Polymer Summit 2017. Stencil’s approach to web components specifically is using Custom Elements. The amazing thing about Stencil is that it works along with the best tools out there for developing amazing apps that can be shipped to production without much hassle. Stencil compiles components into pure web components which can be used in other frameworks like Preact, React, and even with no framework at all.

If you are familiar with React or Angular, you might find yourself at ease while developing apps with Stencil compared to those who are new to JSX and TypeScript.

Why use Stencil?

We can build web components using vanilla JS of course. But Stencil provides some syntactic sugar with TSX (JSX with TypeScript) that makes it a lot easier to build web components with cleaner, reduced code. Stencil’s core API can be used to create components, manage state with the component lifecycle methods, and inputs and outputs to pass attributes into components and emit events from the component respectively.

Here is what a stencil component looks like:

import { Component, Prop, Event, EventEmitter } from '@stencil/core';

@Component({
  tag: 'nice-alert',
  styleUrl: 'nice-alert.scss'
})
export class NiceAlert {
  // input passed to the component
  @Prop() message: string;
  // event emitted from the component
  @Event() alertDismissed: EventEmitter;

  // triggers on `Yes` button click
  yes() {
    this.alertDismissed.emit(true);
  }
  // triggers on `No` button click
  no() {
    this.alertDismissed.emit(false);
  }
  render() {
    return (
      <div>
        <div class="message"> {this.message} </div>
        <div class="actions">
          <button onClick={() => this.yes()}>Yes</button>
          <button onClick={() => this.no()}>No</button>
        </div>
      </div>
    )
  }

Let’s discuss the elements from the core API used in the above component:

  1. tag in the @Component decorator defines the element tag to use this component in HTML.
  2. styleUrl points to the styles (file) of the component.
  3. @Component decorator wraps the class around and registers as a component for Stencil.
  4. @Prop decorator is used for handling input (attributes data) for the component
  5. @Event decorator is used to create event emitters to emit events outside from the component.

Building Web Components with Stencil

We are going to build a stop-watch-box component using Stencil. We will use the stencil-component-starter by the Ionic team to get started. This is a great way to start building web components with Stencil since it provides the tooling and configuration out of the box.

Clone the starter app from the repository:

git clone https://github.com/ionic-team/stencil-component-starter.git stop-watch

Navigate to the folder and install the dependencies:

cd stop-watch
npm install

Remove the repository origin as you’re going to push your code into your own git repository:

git remote rm origin

Run the dev server:

npm start

This should bring up the server at http://localhost:3333/.

Creating the StopWatch Component

First, we will create a stop-watch component. Create a folder inside src/components named stop-watch. Create the tsx (TypeScript) and css files for the component. The folder structure should look like this:

Building Web Components using Stencil, Stop Watch Component
stop-watch component

The code below represents the StopWatch component and goes inside the stop-watch.tsx file:

import { Component, Prop } from "@stencil/core";

@Component({
  tag: "stop-watch",
  styleUrl: "stop-watch.css"
})
export class StopWatchComponent {
  @Prop() hours: string;
  @Prop() minutes: string;
  @Prop() seconds: string;
  @Prop() milliseconds: string;

  render() {
    return (
      <div class="watch-wrapper">
        <div class="watch">
          <div class="unit">{this.hours}</div>
          <div class="sep"> : </div>
          <div class="unit">{this.minutes}</div>
          <div class="sep"> : </div>
          <div class="unit">{this.seconds}</div>
          <div class="sep"> : </div>
          <div class="unit">{this.milliseconds}</div>
        </div>
      </div>
    );
  }
}

The below styles for the component go inside stop-watch.css:

.watch-wrapper {
  background: #2196F3;
  padding: 20px;
  display: block;
  font-family: monospace;
  box-shadow: 0 16px 16px 0 rgba(0,0,0,0.1);
}

.watch{
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-evenly;
  z-index: 2;
}

.watch .unit, .watch .sep{
  font-size: 32px;
  color: #FFEB3B;
}

We have created some markup for the watch component that will display milliseconds, seconds, minutes, and hours. We have used @Prop for the variables so we will be passing these from our container/parent component. Let’s create the parent component now.

Creating the StopWatchBox Component

This component will contain the logic for starting, stopping and resetting the stop watch. For this, we will have a button for each action. To create the component, create a folder named stop-watch-box in the components folder as we did earlier for the stop-watch component. Create the tsx and css files and paste the code snippets from the files below:

import { Component, State } from "@stencil/core";
import { WatchService } from "../../services/watch-service";

@Component({
  tag: "stop-watch-box",
  styleUrl: "stop-watch-box.css"
})
export class StopWatchBoxComponent {
  private hh = 0;
  private mm = 0;
  private ss = 0;
  private ms = 0;
  @State() hours = '00';
  @State() minutes= '00';
  @State() seconds= '00';
  @State() milliseconds= '00';
  timer: any = null;
  @State() isTimerRunning = false;
  watchService = new WatchService();
  /**
   * @author Ahsan Ayaz
   * @desc Starts the timer, updates ever 10 milliseconds
   */
  start() {
    this.isTimerRunning = true;
    this.timer = setInterval(() => {
      this.updateTime();
    }, 10);
  }

  /**
   * @author Ahsan Ayaz
   * @desc Updates the value of the units in for the watchf
   */
  updateTime() {
    this.ms++;
    if (this.ms >= 100) {
      this.ms = 0;
      this.ss++;
      if (this.ss >= 60) {
        this.ss = 0;
        this.mm++;
        if (this.mm >= 60) {
          this.mm = 0;
          this.hh++;
        }
      }
    }
    this.setTime();
  }

  /**
   * @author Ahsan Ayaz
   * @desc Updates the time for the watch component.
   * Applies the detected changes.
   */
  setTime() {
    this.hours = this.watchService.getTimeString(this.hh);
    this.minutes = this.watchService.getTimeString(this.mm);
    this.seconds = this.watchService.getTimeString(this.ss);
    this.milliseconds = this.watchService.getTimeString(this.ms);
  }

  /**
   * @author Ahsan Ayaz
   * @desc Stops the watch.
   */
  stop() {
    this.isTimerRunning = false;
    clearInterval(this.timer);
  }

  /**
   * @author Ahsan Ayaz
   * @desc Clears the time of the watch.
   */
  clear() {
    this.hh = 0;
    this.mm = 0;
    this.ss = 0;
    this.ms = 0;
    this.setTime();
  }

  render() {
    return (
      <div class="watch-box">
        <div class="watch-container">
          <stop-watch hours={this.hours} minutes={this.minutes} seconds={this.seconds} milliseconds={this.milliseconds}></stop-watch>
        </div>
        <div class="actions-container">
          <button onClick={ () => this.start()} disabled={this.isTimerRunning}>Start</button>
          <button onClick={ () => this.stop()} disabled={!this.isTimerRunning}>Stop</button>
          <button onClick={ () => this.clear()} disabled={this.isTimerRunning}>Clear</button>
        </div>
      </div>
    );
  }
}
.watch-box {
  display: block;
  height: 300px;
  width: 300px;
  margin: 0 auto;
  padding-top: 20px;
}

.watch-box .watch-container {
  padding: 20px;
  font-family: system-ui;
}

.watch-box .actions-container {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-evenly;
}

.watch-box .actions-container button {
  border-width: 0;
  padding: 10px;
  outline: none;
  border-radius: 2px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
  background-color: #333;
  color: #ecf0f1;
  cursor: pointer;
}

.watch-box .actions-container button:hover {
  background-color: #555;
}

.watch-box .actions-container button[disabled] {
  background-color: #dcdcdc;
  color: black;
  opacity: 0.3;
  cursor: not-allowed;
}

Notice that we are using @State for making sure that our updated variables get rendered. The difference between @State and @Prop is that the view is not re-rendered if something changes in a @Prop. But if any of the @State models change, the view is rendered again.

While the code (tsx) might seem self-explanatory, there are two important things to note:

  1. We’re passing the state properties to the stop-watch component as:
     <stop-watch hours={this.hours} minutes={this.minutes} seconds={this.seconds} milliseconds={this.milliseconds}></stop-watch>
    
  2. We have imported WatchService in our component file, so create the watch service in the src/services folder. Now the folder structure should look like this:
    Building Web Components using Stencil, WatchService Structure

The code below for the WatchService goes inside watch-service.ts:

export class WatchService {
  /**
   * @author Ahsan Ayaz
   * @desc Calculates the units and sets in string format.
   * @param unit value of the unit in numbers
   * @return {string} the string representation of the unit's value with at least 2 digits
   */
  getTimeString(unit: number): string {
    return (unit ? (unit > 9 ? unit : "0" + unit) : "00").toString();
  }
}

To see it working, open the index.html file and add the below script tag inside the head tag:

<script src="/build/stopwatchbox.js"></script>

Next, use the component inside the body tag as below:

<my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>
  <!-- using the stop watch box component -->
  <stop-watch-box></stop-watch-box>

The stop watch box should be working as follows:

Building Web Components with Stencil, Stop Watch

You may notice that there are some errors on the console saying GET http://localhost:3333/build/stopwatchbox.js 404 (Not Found).

By default, the component starter targets the mycomponent.js file since it uses my-component. We need to use the stop-watch-box component and target stopwatchbox.js. To do that, remove the my-component folder and its file and set the stop-watch component as the main component for the build. Follow the steps below:

  1. Open stencil.config.js and replace mycomponent with stopwatchbox.
  2. Open package.json and replace all instances of mycomponent.js with stopwatchbox.js.
  3. Remove the script tag with src="/build/mycomponent.js" from the index.html in the src folder.
  4. Change the name property in package.json to stop-watch-box.
  5. Remove the usage of my-component from the index.html.
  6. Stop and restart the dev server. The errors should be gone now.

Distributing the Component

To distribute the component, We are going to publish this on npm. If you’re writing your own components, you can publish them on npm as well. To do that, follow the steps below:

  1. Build the component for production by running npm run build from the project root.
  2. Once the build is done, we need to publish it. Make sure the name property of the package represents your component’s name. In my case, it is stop-watch-box.
  3. Follow the guidelines from npm docs to create a user if you don’t have it already.
  4. Publish the package by running npm publish --access=public

Viola! We have our web component published and we can now use it in any framework or with no framework at all. And this is not the end. We could still add features to it like adding laps and perhaps changing the clock’s background and text colors after each lap. The possibilities are limitless! Do try Stencil to build your own web components and paste the demo links in the comments. We can’t wait to see what you build!

What’s next?

Building a web component was a breeze for me using Stencil and I could get it completed and published within a few hours. Check out the code from this repository.  It also contains the hover effect implementation.

Make sure to check out Stencil‘s docs for building more complex web components. The docs have information on state management, events, methods, forms and almost everything that should get you started.

While Stencil provides a great way to build web components, there are other choices as well. For example, StakeJS, Polymer, and other libraries as well as frameworks like Angular and Vue are also on the way to allow developers to build Web Components.

Posted in Application Development, News
Share this

Ahsan Ayaz

Ahsan Ayaz is a Google Developer Expert in Angular. During his time at Modus, Ahsan worked as a Software Architect. Apart from building web and hybird-mobile apps based on Angular, Ionic & MEAN Stack, Ahsan contributes to open-source projects, speaks at events, writes articles and makes video tutorials.
Follow

Related Posts

  • Web Components Introduction
    Web Components Introduction

    Web Components Introduction Web Components is a standard that is getting really popular these days,…

  • Web Components Introduction
    Web Components Introduction

    Web Components Introduction Web Components is a standard that is getting really popular these days,…

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
IT modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Our story
    • Careers
  • Let’s talk
  • EN
  • FR