Companies like Airbnb, Apple, Uber, and GitHub have changed the ways they design digital products by incorporating their design language and organizing it into a system that can be used across all employees — and even outside of the company. It quickly became popular in the whole industry: Just google the company’s name and the word “design” next to it, and you’d be surprised by how many companies have something similar. , , , — these are the examples of good design systems. Airbnb Design Apple Design Uber Base Web GitHub Primer We won't be talking today about the reasoning behind having a company-wide design system or the organizational challenges that come with it (though it’ll be definitely covered later, so stay tuned). Instead, we’ll focus on the technical choices and implementation details. Traits of a Good Design System A good design system is a collection of reusable components — guided by clear standards — that can be assembled together to build any number of applications. Let’s try to split this definition into actionable traits we want to prioritize and focus on from a technical perspective. Reusable components Generally speaking, the design system is a set of shared components that’ll be used by everyone. Imagine you have several independent teams in the company, and some of them use vanilla JS. Another team uses React, and even another group is experimenting with Svelte. As a creator of a design system, you have to build components for these frameworks and think about flexibility in the feature, so you won’t lock other developers in a specific ecosystem. Extensive documentation To ensure the smooth adoption of your design system, you’ll have to provide thought-through, easy-to-use documentation with examples and best practices. How do you do this? First, a website with all components and their descriptions would help. Second, it’d be amazing if you could provide guidance about possible arguments and use cases for your components directly in the developer’s IDE. Performance-oriented So you’re about to ask your fellow developers to add this design system as a dependency and probably replace something that’s already working, make sure to keep the number of dependencies as low as possible, preferably zero, so you won't bloat the package size of all projects. Try to rely on a browser API or CSS and be closer to browser-native behavior because less custom JS code means faster components. Maintain the highest quality You also need to ensure your design system won’t break on a new release. Apart from doing code review and manual testing, I’d encourage everyone to look at automated tests. For presentational components, consider . For stateful ones, write integration and end-to-end tests. screenshot testing StencilJS and Web Components to the Rescue If you’re wondering how you’re going to cover everything from the list above without spending a month setting up the architecture, I have something to suggest. is a toolchain for creating reusable, scalable design systems built on top of Web Components. It provides a complete set of tools to start working on your design system right now and makes it easy to work with Web Components. StencilJS The components created with Stencil are fully compatible with browser standards and can be run in any framework, including just a plain HTML file. You can use to add a custom website that’ll showcase your work. It also features (apart from e2e and Jest integration). output targets screenshot testing Let’s dive into it, and see how easy it is to build a simple-yet-stateful component. Introducing the fetch button The core component of any design system is a button. But since just a button would be too simple, we’ll make it powerful enough to also fetch data from any endpoint you specify in its attributes and return data via browser events. To start, run the Stencil generator: . It’ll ask you to pick a starter. Choose Component. That’ll generate a folder with a simple test component so you can start playing with it ( ). npm init stencil npm install && npm start In the folder, you’ll find the folder. Rename it to — as well as the and files inside of it. The CSS file is responsible for the styling of our button, so let’s add some styles: src/components my-component fetch-button css tsx button { : # e; background-image: linear-gradient( deg, #fafbfc %, #eff3f6 %); padding: px px; font-size: px; font-weight: ; line-height: px; cursor: pointer; border-radius: em; } color 24292 -180 0 90 6 12 14 600 20 0.25 Now go to the file. This is the actual component we need to build. tsx { Component, Prop, h, Listen, State, Event, EventEmitter } ; @Component({ : , : , : }) { @Prop() url: string; @State() status: string; @Event() success: EventEmitter; @Event() error: EventEmitter; @Listen( , { : }) handleClick() { .status = ; fetch( .url) .then( response.json()) .then( { .success.emit(data) .status = }) .catch( { .error.emit(error) .status = }) } render() { ( <button> <slot /> </button> <div>Status: {this.status}</div> ); } } import from '@stencil/core' tag 'fetch-button' styleUrl 'fetch-button.css' shadow true export class MyComponent /** * URL to fetch */ /** * The query status */ 'click' capture true this 'pending' this => response ( ) => data this this 'success' ( ) => error this this 'error' return < > div </ > div Before the component class, we need to add a decorator specifying the tag name, a path to the style file. Then inside of the class, we need to define a called . It’ll work as an HTML attribute and allow us to pass data to the component. prop url The next line creates a state variable: Every time we change it, the render function will rerun. The directive registers two events for us: and . @Event onSuccess onError helps us to listen to click events and execute the corresponding function. In our case, we’ll call the native fetch function and update the state and trigger events depending on the outcome. @Listen Finally, the function defines the DOM that‘ll’ be created. The tag will be replaced by children. render <slot /> <fetch-button> That’s it. Now let’s see how we can use it. Fetch Button Demo Fetch my data from vanilla JS <!DOCTYPE html> < = = > html dir "ltr" lang "en" < > head < = > meta charset "utf-8" < = = > meta name "viewport" content "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" < > title </ > title < = = > script type "module" src "/build/designsystem.esm.js" </ > script < = > script nomodule src "/build/designsystem.js" </ > script < = > script src "https://unpkg.com/react@16/umd/react.development.js" crossorigin </ > script < = > script src "https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin </ > script < = > script src "https://unpkg.com/@babel/standalone/babel.min.js" </ > script </ > head < > body < = > fetch-button url "https://jsonplaceholder.typicode.com/todos/1" </ > fetch-button < > script fetchButton = .querySelector( ); fetchButton.addEventListener( , .log) const document 'fetch-button' 'success' console </ > script < = > div id "root" </ > div < = > script type "text/babel" ReactDOM.render( <fetch-button ref={el => el.addEventListener( , console.log)} className= url= onSuccess={console.log} > Fetch data from React </fetch-button>, document.getElementById( ) ); 'success' "react" "https://jsonplaceholder.typicode.com/todos/1" 'root' </ > script </ > body </ > html Apart from the native HTML example, I’ve added integration with React. As you can see, it’s as easy as writing plain elements. div Known Complexities Now, it’s time to talk about the downsides of using Web Components. The most important one is the lack of good framework support. For example, in React, you can’t attach event listeners to your component. As a workaround, you can attach a native event using the property — which is what I did in the example above. The website shows the compatibility Web Components has with popular frameworks. ref Custom Elements Everywhere Conclusion Before starting working on the design system, evaluate the values you want to focus on. Maybe you don’t need to have it at all and just properly organized shared components would be enough for you. If you’ve decided to incorporate the system, Web Components perfectly solves challenges that stand before design systems and is a great technology to build your components upon. StencilJS makes it easy to manage and deliver components for different targets. But with all that in mind, beware and explore the still-standing issues regarding framework support.