UI Elements vs. Product Components

Not everything is a component, so why then is everything a component?

It's because far too many in the web developer community have become "React developers" and have React-ified everything in sight, including things that have nothing to do with JavaScript frameworks, like CSS. As the popular adage goes, "If your only tool is a hammer, every problem looks like a nail."

But as many have learned, going all in on React or any other framework leads to component soup, the modern equivalent of jQuery spaghetti. I recently attempted to spoon out a small portion of a large React app and move it to a lazy-loaded micro-frontend (not a fan of that architecture, but that was the task) and I couldn't. There were too many components with far too many imports all interdependent on each other. Here's a visual of what this small component and its dependencies look like (the two nodes circled in red are the basis for the component):

And to be clear, that's not the app. It's a component with its dependencies. To the user, it's just a simple page with a few charts and other minor bits of content.

It ended up being more practical to start fresh and write the code from scratch. That page will eventually become a single 100% vanilla dependency-free HTML file. It will be 30x faster and drastically easier to maintain because once I reduced everything down to UI elements there was no longer a need for framework-dependent product components.

So, to avoid hammering on every bit of UI with a framework, try making these distinctions when defining and building your components.

UI Element

UI elements are what actually make up the user interface. They are the visual elements on your page that users see and interact with. They are the HTML. Here's some examples:

UI ElementsNot UI Elements
buttondashboard
dialogproject timeline
tablenews feed
inputbilling history
tabsinbox
text editoradmin page
sidebarlogin page

People see and interact with buttons and tables; they do not see or interact with abstract components like QueryClientProvider. And they also don't technically see or interact with more concrete components like a dashboard or a news feed - they see the UI elements controlled by them. Do you see the difference?

And UI elements aren't just the native HTML elements. They include patterns of elements, like four child divs with a parent set to display: grid making a 2x2 responsive layout. They are also higher-level elements like tabs, a sidebar, or a full-on text editor. The Custom Elements API has made such elements possible for many years now.

Regardless of programming language, framework, architecture, build pipeline, or device type, UI elements are always going to ultimately become HTML elements in the DOM 100% of the time. There are no exceptions to this rule because that's how browsers work.

Why then do teams willfully abstract them away into framework-dependent components? It's not easier, faster, more accessible, or more performant. It's because that's what they've been taught to do by framework creators and other influential developers. Influencers have their favorite hammer ready to pound nails, so they teach hammer and nails and not much else.

Vanilla HTML, CSS, and JavaScript have everything you'd want or need to create UI elements and that's what they will eventually become at runtime anyway. They are the essence of frontend - embrace the web and master it!

Product Component

These are abstractions. They often, but not always, benefit from a framework. Their purpose is to bring logic, data, and UI elements together into a meaningful piece of the app. They import dependencies and encapsulate business logic. They manage app state. They provide structure and organization as the app grows in complexity. They're glue. Here's some examples:

Product ComponentsNot Product Components
dashboardbutton
project timelinedialog
news feedtable
billing historyinput
inboxtabs
admin pagetext editor
login pagesidebar

The requirements for these components are such that vanilla just doesn't quite have enough. Patterns alone, like Model-View-Presenter, can do much, but there's often still a need for specialized mechanisms to structure and package lots of code. This is one reason a product component would need a framework.

And the render cycle. Re-rendering a component as state changes is a challenging problem and frameworks have solved it. Many UI elements are static and don't change, and because UI elements are leaf nodes those that do change do so in isolation and are manageable with vanilla JavaScript features like Custom Elements' attributeChangedCallback. Product components, on the other hand, are always internal or root nodes and are managing state and rendering in a way UI elements aren't.

The more leaf nodes, or UI elements, you have, the fewer abstractions and layers are needed and the application becomes much simpler overall. A project then has three clean layers:

And the product components go from complicated interdependent graphs like the screenshot at the beginning of the article, to a very clean tree like this:

At times it is necessary to nest product components. I have found there is never a need to go beyond three components - the App, the Page, and sometimes a section of a page. With UI elements I have never needed a fourth and often only need two. There is no need to incur the costs of the excessive "pyramid of doom" designs so common in React projects.

Well, that's UI elements vs. product components. Do you make a similar distinction? What's worked well for you?