Creating and Integrating Design Systems with StencilJS

A step-by-step guide to building and distributing a design system built with StencilJS

Photo by Danae Paparis on Unsplash

I’ve recently written an article about the fundamentals of StencilJS and how to get started with it to write reusable Web Components.

In this article, instead, I want to deep-dive on the use-case that led to the creation of Stencil by the Ionic team: creating reusable design systems.

While it’s entirely possible to build fully-featured apps with Stencil alone, this toolchain makes an ideal companion for bigger frameworks for creating truly reusable components.

Indeed, it allows us to build components that are:

  • extremely small
  • performant out-of-the-box
  • reusable with any framework, or no framework at all
  • future-proof

In this article, we will see:

  • how to scaffold and build a Stencil components project
  • the details and the anatomy of a Stencil design system
  • leverage CSS variables to theme our components
  • how we can integrate the component with other frameworks such as Angular

Scaffolding a Components Project

The Stencil CLI helps us scaffold a new project without having to manually create any configuration.

To start, we run the following command:

npm init stencil

The CLI will prompt you what sort of project you want to build, and we can choose component.

Once the project is created, we can simply run npm start and start tinkering with our components.

Anatomy of a Stencil Components Project

Let’s take a look at what Stencil generated for us, so we can understand better how it works under the hood.

The most important places to look at from the image on the left are the following:
/src/components
/src/components.d.ts/stencil.config.js

The folder src/components contains all the components we’re going to build with Stencil.

The file components.d.ts contains the Typescript type declarations automatically generated by the Stencil compiler.

The Stencil configuration is used for setting up the outputs and passing some information to the compiler.
It needs almost no manual intervention.

In the next paragraphs, we will see how the distribution folder is created and how we can integrate it with other existing codebases.

Creating a Collection of Components

Running the project for development

In order to run and try out the components, we don’t need to set up anything else. You can simply run the following command:

npm start

This command will call the Stencil CLI in watch and dev mode and will serve the file /src/index.html that we can use to test our components.

Creating our Component: my-button

Let’s start creating our first component, called my-button. This is the standard reusable button in our design system.

We create two files inside the folder components/button:

  • button.tsx
  • button.scss
import { Component, h } from "@stencil/core";

@Component({
tag: 'my-button',
styleUrls: ['./button.scss'],
scoped: true
})
export class ButtonComponent {
render() {
return (
<button>
<slot />
</button>
);
}
}

For the styles, let’s go with something minimal and simple:

button {
background-color: #42a5f5;
color: #fff;
padding: 12px 24px;
border-radius: 4px;
outline: none;
font-size: 1em;
border-color: transparent;
font-weight: 500;
font-family: sans-serif;
cursor: pointer;
}

Importing and Reusing Components

One of the most important things do take into account when building a design system is to create atom components that are composed to build larger complex components.

If you’re used to having to import or register components in order to instantiate them, then forget everything you know.

Stencil allows to seamlessly use components without the need to import them anywhere. We just need to create a class (export one single class per component) and the toolchain will take care of it.

To test how the button looks like, we open the file index.html and add the code required to display the component we just created:

<!-- index.html --><my-button>
Click Here
</my-button>

That’s pretty much it! Below you can see the result:

Button with Light Theme

Theming your Design System

Stencil promotes CSS Variables as a way to style components and create themes. If you have never used a CSS Variable, you’re in good company. I hadn’t used them until a couple of months ago, but I was impressed with their power.

Stencil allows to define a global style typically called global.scss. This file will normally contain styles that will be available to every element, but also to define the variables for our components.

CSS Variables are an ideal way to:

  • reuse values and ensure consistency among all our components
  • easily create multiple themes
  • allow consumers to easily customize with their own styles

Adding a Global SASS Style

In order to use this, we need to first install the SASS plugin:

npm i -D @stencil/sass

We add the plugin and the global.scss file to the configuration:

import { sass } from '@stencil/sass';export const config: Config = {
...
plugins: [sass()],
globalStyle: './src/global.scss'
}

CSS Variables: a quick introduction

If you have used SASS variables in the past, there’s not much else to know except for slightly different syntax details.

To define a CSS variable, we simply define a value prefixed with --:

:root {
--primary-color: #42a5f5;
--text-color: #323232;
}

Once defined, we can use these anywhere in our CSS files by wrapping the variable name with var():

button {
background-color: var(--primary-color);
}

Use Case: Light Theme vs Dark Theme

A common use-case of design systems is to provide multiple themes. Using CSS variables makes it an extremely easy task.

First, we change the properties of the button to use variables:

button {
background-color: var(--button-background-color);
color: var(--button-color);
...
}

Then, we can define the variables based on the selected theme. In order to achieve that, we use :host-context(selector). This pseudo-class function allows us to select the host based on the selector passed as a parameter.

:host-context(.light-theme) {
--button-background-color: var(--primary-color);
--button-color: #fff;
}

:host-context(.dark-theme) {
--button-background-color: #424242;
--button-color: var(--primary-color);
}

Changing themes is a matter of switching the correct theme class on one of the ancestors. For example, we can add the class dark-theme to the body element.

Button with Dark Theme

Building the project for distribution

The Stencil compiler will intelligently generate many small files and will automatically import polyfills for browsers that don’t support certain features (for example, IE does not support CSS variables).

In order to run the project for distribution, we can run the following command:

npm build

Below you can see the structure of the generated files:

That’s a lot of files, but you only need to import three of them from your code:

  • the ESM bundle
  • the ES5 bundle
  • the CSS

How does Stencil help us ship fast bundles?

  • Stencil does not (currently) create a single bundle. Instead, it creates many small bundles that are requested by the components loaded on a page. This helps with loading only the resources that are actually needed. It’s also ideal to take advantage of HTTP/2.
  • Differential Bundling: Stencil will generate 2 bundles, one for modern browsers and one for older ones (ESM and ES5). Modern browsers will not download the polyfills needed to run cutting-edge features.
<script type="module" src="/build/design-system.esm.js"></script>
<script nomodule src="/build/design-system.js"></script>

Integrating Components with other Frameworks

There is still work to do, but I believe that nowadays we have the tools and enough browser-support to start writing our design systems entirely with Web Components, and Stencil is a great tool for doing it.

Whether your app is built with React, Angular or any other framework, one of the best ways to future-proof and reuse your components is to leverage Web Components and write wrappers (through slots, props, and events) in your framework(s) of choice.

Once your design system is ready to be consumed by your larger applications (built with anything else), it’s time to include the components generated by the Stencil compiler.

There are different ways to do that depending on how you structured your applications:

  • if you’re using a monorepo, you can import the bundles manually from the build folder
  • if you’re using multiple repositories, you can fetch your design system bundles your files via a CDN, or by using Bit

The Stencil Documentation has an exhaustive (yet brief, as it is a pretty simple task) for implementing Stencil projects with the major frameworks.

I’ve been working with Stencil for a few months now for creating a reusable and small-sized components library, and I can’t recommend Stencil enough.

Do you use Stencil, or any other Web Components library? I’m very curious to hear about your experience with these tools!

If you need any clarifications, or if you think something is unclear or wrong, do please leave a comment!

I hope you enjoyed this article! If you did, follow me on Medium, Twitter or my website for more articles about Software Development, Front End, RxJS, Typescript and more!

The Startup

Get smarter at building your thing. Join The Startup’s +786K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Giancarlo Buomprisco

Written by

UI Consultant & Technical Writer. I write about JS, TS, Rx, Angular & all things Front End 🇬🇧 🇮🇹 — write me at gc@frontend.consulting!

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +786K followers.

Giancarlo Buomprisco

Written by

UI Consultant & Technical Writer. I write about JS, TS, Rx, Angular & all things Front End 🇬🇧 🇮🇹 — write me at gc@frontend.consulting!

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +786K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store