Why and for what Web Components?
Web Components include a set of web standards that allow you to define your own HTML elements. The great advantage of Web Components is the use of standardized interfaces to communicate with the rest of the website. Accordingly, they can be easily integrated with different technologies. Components from frameworks like Angular or React, on the other hand, are initially trapped in their own cosmos.
Thus, the standards that comprise Web Components can be viewed as an integration and isolation technology. Primarily, there are two major use cases for the use of Web Components: on the one hand, for design elements and widgets that can be reused multiple times, and on the other hand, for the integration and isolation of subareas within the page, e.g. for the implementation of micro frontends.
The use of Web Components therefore makes sense if you want to reuse the same components in different contexts and these may even differ technologically. These can be different SPAs as well as server-generated HTML or content from a CMS.
Since the Web Components API consists of HTML and DOM structures on the outside, it hits exactly the lowest common denominator that actually any framework can handle. This is also referred to as framework-agnostic.
That sounds great, then you can actually build everything with Web Components? You can certainly do that, but unlike Angular, React or Vue, Web Components are not a comprehensive framework, but rather just a standard for specifying your own HTML elements. Web Components are very low-level, a compile target, if you will. There are frameworks like LitElement or Stencil that make it very easy to implement Web Components. But even these frameworks focus more on individual components than on applications as a whole. Web Components really come into their own when they are to be used in different contexts, or when you need some isolation. If neither of these apply, Web Components are probably not the solution to the problem you have.
From the fact that Web Components are particularly useful if they can be reused, it also follows that they usually tend not to have a domain-oriented interface. This is because domain-oriented components are often not useful in different contexts. But if they do, as is the case with a log-in, for example, then they have no API to the outside.
Requirements for a Web Component
From now on, we will start from relatively “dumb” design elements, especially form elements, for Web Components. For these, three requirements can generally be identified that actually always apply to a component:
- Design: Visuals are an important part of a Web Component. This is because they actually always have something to do with visualization in the browser.
- UX: Web components, especially form elements, are often used to interact with the user. Accordingly, things like tab navigation or smart autocomplete for inputs should be considered.
- Maintainability: As for any piece of software, it is especially true for a multi-used component that it should be easily maintainable. This means: simple and compact Public API, sensible cutting of functionality, and avoidance of unnecessary complexity.
Competencies in the team
The requirements of design, UX and maintainability sometimes differ quite a bit in the competencies that should be present in the team. It probably doesn’t make sense to split the implementation of a component between multiple people, but a good review process can ensure that each component is looked at from all three angles.
Among frontend developers, there are two groups of similar size with shades of gray in between. One group of frontend developers has a certain feeling for design and UX or has even been a designer, media designer or similar. The other group of frontend developers consists more of typical techies. They often originally did backend development and have a completely different focus for frontend development: software architecture, performance, code quality and maybe technical gimmickry.
It is certainly an advantage to have a developer from both of the above groups on the team in order to cover a wide range of requirements well.
Design and Styling
Since the look of a Web Component is mostly quite relevant, styling should not be ignored. I often get the feeling that styling is labeled as trivial, which I don’t see that way. Styling is easy if you know how to do it. Since CSS is a bit more extensive, you need a developer who really wants to do it. On the one hand, the developer has to be able to understand rather static designs in the form of screenshots or similar and understand the intention of the designer. And on the other hand, they must also be able to then implement them with the knowledge of technical restrictions. Otherwise, there is a high risk that, firstly, the styling quickly becomes unmaintainable because it only works “somehow” and, secondly, that edge cases are quickly not considered. For example, texts that are too long do not work well in the design. Parts of the Web Components specification are also shadow DOM and slots, with which you can restrict the CSS well to components—but this only prevents effects on the environment, the component itself is still not maintainable.
The maintainability of a Web Component
The maintainability of a Web Component can be divided into two parts. The first is maintainability from the inside: Another developer must be able to easily understand, extend or fix bugs in the component. On the other hand, maintainability from the outside: the maintenance of the Public API. Because when Web Components are used for design elements, they are effectively a hardened library. After all, the great advantage of Web Components, namely that they can be used in different contexts on both the client and server-side, is only given if all-consuming applications can work against a stable API. Otherwise, reusability is not really a given. This is especially true for solutions that are not community-driven, because an ill-considered change can quickly slip through.
But what exactly is the API of a Web Component? If you look at the usage of a Web Component, its API can be divided into three areas:
- HTML API: That is, the HTML element, its attributes and slots. This API can also be used server-side by generating HTML. This API is really static. Custom HTML elements must be registered in the browser, and then the Web Component is rendered initially.
- Styling: If a Web Component also includes CSS, which is usually the case with a design element, then this may also have an influence on other components. When styling, care must be taken to ensure that one’s own Web Component does not “accidentally” influence its environment. With child elements, however, it can sometimes be intentional that there are interactions. In addition, with Shadow DOM one has many possibilities to influence the styling, but there are also various disadvantages. Scoping via BEM or similar should be sufficient in many cases. In addition, one should be careful not to use “outward” styles, i.e. margins or flex item properties.
The challenge of API design
Every software developer knows that designing a good API is not the easiest task. An API should be as simple as possible. This can be achieved, for example, by using simple structures and opaque references in the method parameters and return and property values. In addition, the following also applies: An API should be as small as possible, but not over-generalized. And as with any other API, it’s the same with Web Components: you shouldn’t build a super-component, but rather several special components. As long as you are the only consumer of your API, changes are rarely a problem. It really only becomes unpleasant when there are external consumers, because then the following suddenly applies: “Change is expensive”. In the case of shared Web Components, this is exactly the case. Accordingly, the API of a Web Component should be as stable as possible, or at least remain downward compatible, so that all consumers can update easily. Each special lock for a consumer may have to be maintained for a long time, so it is advisable to say “no” to new features sometimes. Instead, a private special component for a consumer can be built instead.
When you think about all this, you realize that it is quite difficult to design a good API for a Web Component.
Versioning hell or forever backwards compatible?
Once we put our Web Components into production, we actually have two ways to deal with the Public API:
- We version the components—so every change to the API is made available as a new version of the component.
- We remain “forever” backward compatible.
Both are costly. Versioning leads to the need to maintain multiple versions simultaneously and also to provide an upgrade path. In addition, discontinuing a version may be difficult if key consumers cannot update for some reason.
Versioning design elements as Web Components introduces the further complexity that there could be two versions of the same component on the same page. Of course, if the requirement is that consistent design is desired at every point used, it must be ensured that the two versions look identical. The alternative to regular versioning is no versioning at all. Instead, a variant of a rolling release can be used. With the classic rolling release, there is only one version, namely the current one. But even such a procedure is complicated, because the API must never break in such a way that a consumer is broken. A kind of consumer-driven contract could help here to determine which part of the API is still used at all. In addition, as the complexity of the components increases, the risk increases that the implementation will have critical bugs, or even worse: one consumer’s bug is another consumer’s feature. In order to avoid such situations, one should build its components thus if possible in itself closed with a very simple API.
So both versioning and rolling release bring their difficulties.
Someone else has already done it
The rolling release approach seems absurd to many people in the enterprise world: “Well, then you don’t have control anymore.” While this is true, it is still only half the truth. In general, as a consumer, you often have little control over third-party interfaces. But as long as the interface is stable, that shouldn’t be such a bad thing. Then it’s really all about trust. The good thing is that every web application developer generally has to have a lot of trust, for example in the browser manufacturers, because at least in the consumer business they have no control over which browsers are used. This is also often associated with pain, but it usually works well enough by now. It has to, since there is no choice.
This circumstance can also be used to one’s advantage, because one does not have to reinvent the wheel, especially with regard to forms and input fields. The web standards already offer a lot here, and since the browser manufacturers maintain the implementation, one can assume less own maintenance effort.
Accordingly, you can use a simple wrapper around an <input> instead of building your own text input field completely by yourself with your own events, attributes and validation. The advantage here is that, depending on what you want the input field to be able to do, there is effectively no public API, except for the tag name and the slot for the <input>. So the wrapper can communicate with the rest of the application via the <input> and add additional styling or input helpers at the same time:
<fancy-input> <input type="text"/> </fancy-input>
Of course, the same is true for <select> and <option>. Thus, many input elements can be implemented by simply extending wrapper elements to a standard input element. The complexity can be kept very small, because no extensive API of an input element has to be rebuilt.
The use of Web Components is worthwhile if you can also benefit from their advantages, especially framework independence. This is usually the case if you want to use the Web Components more than once (e.g. as design elements) or if you need to isolate areas within a page. If this is the case, you have to be aware of the consequences. This is because the Web Components are then a hardened library of multiple consumers. In addition, there is the complexity that all consumers should actually use the same version if possible. If the decision to use Web Components in a sharded way has been made consciously, it is easiest to keep the API as small as possible in order to avoid maintenance problems from the outset. The best API is “no API”. Especially for form elements, it is a good idea to build a wrapper element around an <input/> that enriches the standard element with functionality and design. The web specifications already provide a lot and you don’t have to invent everything again. By selectively reusing standardized HTML elements, you can keep your own special API smaller. To adequately address both frontend design and styling as well as API design, it makes sense to have both qualities represented in the team.
But if a component isn’t going to be reused by multiple consumers anyway, then Web Components probably don’t make much sense either, and there’s no need to learn a new piece of technology.