Skip to content

Encapsulation in UI components on the Web

Author's photo
6 min read ·

One of the hardest-to-explain skills you acquire as an engineer is the ability to properly set boundaries between code areas—to encapsulate the complexity of underlying logic into a clear, straightforward, easy-to-use interface.

Encapsulation

Encapsulation is essential not only for making hard things simple to use, but also for protecting developers on the consuming side from making mistakes. Ideally, there shouldn't be a way to use your code improperly.

This principle is often referred to as Poka Yoke:

Poka-yoke (ポカヨケ), meaning "mistake-proofing" or "error-proofing" in Japanese, is a technique used to prevent errors from occurring in the first place. It's a key element in lean manufacturing and the Toyota Production System, focusing on designing processes and tools to make mistakes virtually impossible.

While the term encapsulation was initially tied to Object-Oriented Programming, the concept in its fundamental form applies to almost any piece of software: an entity, a library, a component, an API, or a system as a whole.

The Curse of CSS

In HTML/CSS, this problem becomes even more challenging — primarily because CSS was originally invented to style documents: a homogenous blob of typically single-level content. But now we use it for something fundamentally different: styling components in web applications.

The difference is striking. In CSS, there is no way to set boundaries between a component being consumed and the code that consumes it. CSS rules pierce through the entire HTML document, applying styling properties to every matching element in the tree. That hasn't aged well in the current era of web apps.

There was once hope that Web Components and Shadow DOM would help with this, but the whole Web Components thing seems to be dead as of 2025 (and nobody cares).

The best tool we have so far is our developers' discipline. 🤷‍♂️

It seems to me that part of the increasing popularity of Tailwind — apart from super-easy, quick setup and the most frictionless prototyping — is the fact that it fixes "the CSS problem". You can only* apply styles to the elements you can pass classes to, defying the whole nature of all-piercing global CSS rules.

Separation of Styles

The applied styling is seemingly just a flat list of CSS properties, and we developers have to decide which styles the component should be responsible for— while still allowing outer code to modify its behavior in a way that doesn't increase our maintenance burden in the long run.

💡 My rule-of-thumb answer is:

  • The component is responsible for styling everything that is inside its border, including the border (see the green area on the image below)

  • The outer code is only allowed to modify things outside the component's border (e.g. margin, position, z-index, etc).

  • The inner content is special: it can either be the component's responsibility, or it can be passed from the outside (think button labels, panel contents, etc).

A picture showing HTML element box model with nested boxes: outer margin, then border, then inner padding, and content.

The above may sound obvious, but it's surprising how often this doesn't happen. Sometimes it's due to lack of experience, other times it's just the temptation to cut corners and get the shit done (on time).

But every time a component's boundary is violated, it becomes much harder to modify that component without breaking the UI everywhere it's used.

Display: What?

One property that creates an unfortunate exception for the above rule-of-thumb is display.

The problem with display is that it simultaneously controls both the element's outer and inner flow modes.

For example, if the outer code needs the component to be displayed as inline, you can only achieve that by knowing the component's internal flow mode:

  • for block you use inline-block
  • for flex you set it to inline-flex
  • for grid you set inline-grid
  • and so on

The only okay workarounds I see here are:

  • either wrap the component with an extra div container
  • or move this outside of CSS by passing inline={true} flag to the component and let it handle the rest

Conclusion

It's incredible what we can now build with web technologies. Web apps are becoming increasingly sophisticated, steadily pulling resources away from traditional native cross-platform development.

But it's also clear that the technology is still in its infancy, held back by some early design decisions.

I'm looking forward to a future where web technologies will help us work faster and make less mistakes without relying on people's discipline to work around the limitations of it.


Let me know what you think by pinging me on socials or sending me an email.

Cheers! 🖖

End of article
Got any comments?