In search for the component

There is a tendency to make everything a component that isn't.

Components help us structure and organize our code in a logical way. We use them as easily replaceable building blocks and we have developed the thinking that anything that isn't build by such components won't scale well. To reduce our costs we have learned to assemble the desired functionality from inexpensive, easily available parts. But there is a danger that by reusing existing components we constrain our thinking in certain ways that stay in the way of embedding our own thinking into the project. Instead, we spend the majority of our energy trying to understand someone else's thinking, when we have no access to their domain knowledge that influenced the decisions we see.

Components help to make our projects more manageable. The nesting levels in the code help us to quickly find the parts that need to change. In many cases, instead of using the find function, we can rely on the logical ordering of the code to arrive to the component we want. But the more components we have, the less effective this becomes. With hundreds of them, we would need to have a separate list that enables us to jump to the place where they were defined. Branching and deep nesting can create complex and hard to follow structures where we can easily lose ourselves. But reducing the number of levels would mean to extract even more components. Sometimes it can be easier to think hierarchically rather than sequentially and an attempt to convert between the two may create more problems than it solves.

Functions can be seen as components. We take some input, do some complex transformation on it and return the result. Since this is a multistep process, it makes sense to create a reusable component. All operations then fit nicely into a single capsule. Given how many components we have to deal with, we often strive to preserve the same level of granularity throughout the project. This is not only hard to achieve, but can often be suboptimal. If we have too few components, they can grow too big and hard to maintain; if we have too many, we have to keep track of many labels, which at a given point may become semantically redundant or mentally impossible to discriminate. When functions aren't enough, we organize them into a class—a component at a higher level of abstraction. (Some languages allow the definition of a class within a function, but we normally prefer using only “good parts”.) And if classes themselves are not enough, we organize them into packages.

Unfortunately, not every component can be described well through a multistep process. It is not enough to say that an HTML fragment, which has a couple of classes with clear labels attached to the topmost element is automatically a component (in the same way as preceding a variable name with an underscore doesn't automatically make it private). Choosing a naming convention for these classes (consider “menu__item menu__item_visible menu__item_type_radio”) can make the code unreadable out of the desire to seek the component in it. Adding a couple of data attributes also wouldn't make this HTML fragment a real component, albeit creating the impression that it has its own data, just without the ability to operate on it independently. Shadow DOM was intended to create UI components by masking the underlying structure, so that we get the impression of having a single HTML element. But this doesn't change the fact that the structure although not rendered, still needs to be loaded by the browser. And if we can embed styles directly at component level, we may inadvertently duplicate the code, because we are no longer separating HTML from CSS. Mixing the two then makes the code hard to maintain. On the other hand, if we choose to apply the styles to the root element through JavaScript, then we are mixing CSS and JS. Using this extra JS code to make the component work is another cost factor worth considering, especially in large code bases.

The desire to make CSS look as if it consists of components also has an interesting nature. Each CSS selector relies on a single operation instead of the many we saw in the case of the various programming languages. The selector matches against the DOM tree, which means that it needs to suggest hierarchy in a way that allows for sequential organization. The first can be achieved by using descendent selectors (empty space), while the second by visually separating the styles belonging to the various HTML fragments (empty line). We could also use comments to improve clarity if needed. This may seem wrong as they don't directly contribute to value, but it helps to decrease the cost of maintenance. Using a solution that involves more than simple comments will likely add more complexity than is necessary. By writing CSS we develop blurry, unstable relationships instead of in-place, cohesive definitions. Such relationships are hard to present as standalone components without some kind of redundancy. By appending many classes next to each other and avoiding style overwrites, we can treat an element as a component, giving it a certain look. We could reuse the classes for other elements/purposes, but this makes the logical structure of the CSS code hard to follow. Even when it is well-organized, the HTML fragment will still have multiple classes, all visible to the end user. This shows how attempting to extract components at one place can lead to suboptimal code elsewhere if we don't keep a holistic perspective.

It is then not a good idea to try and bring every existing technology under a common denominator for improved consistency within a single system.

That we need to design exclusively with components is also a mindset that needs to change. Reusing the same components will make most websites very alike, especially over time. Their spread reduces the need for custom solutions and the incentives to innovate, which will harm everyone in the long term. Choosing from what's available and creating something original are two very different things and it's unfair to treat them as if they are equally valuable. If we need more components to organize our work, chances are that we'll spend our time trying to weave them together in specific ways rather than seeking better solutions for the specific case (which may sometimes involve analog decisions as strange as this sounds). We risk to develop and improve the components instead of the situation of the client. Having an endless choice of components makes the human aspect of the work negligible, e.g. we agree to give up on what makes us special by the time we choose to use them. If our workflow is dominated by preprocessors, version control systems or build systems and the existing components for them, we'll be left with less time for design decisions and we'll start to see the design itself as a component that needs to be chosen. But great design is not a component and it will never be. Then it's probably not such a great idea to search for the component in everything.