Table of contents
As a developer, you’re working with components - self-contained, reusable pieces of code that make up the user interface. When you first start a project, writing a single component for a function often seems like the right thing to do. You begin to create components without knowing all the variants - versions of a component that have specific visual or functional attributes - they will end up supporting.
Over time, when new pieces of code are added or edited, you end up with large, self-contained units of code that handle multiple aspects of the user interface. We call these monolithic components.
Slowly, your original components become unrecognizable, their internal logic so complex that maintaining them becomes a daunting task. When a change request arrives, you’re forced to rethink the structure of the component. It’s a nightmare!
To avoid disaster, you might create several similar components to support each variant. But proliferating components like this quickly becomes uncontrollable. There must be a better way.
Spoiler: there is.
A masterclass in composition
Composition allows developers to create more flexible components, known as “composable” components. Unlike monolithic components, that tend to group multiple functionalities within a single block of code, composable components use smaller, self-sufficient, reusable parts – “subcomponents'' – and combine them in different ways, shaping countless different “parent” components and layouts. This leads to cleaner code, making interface maintenance easier and boosting app performance.
Now, when a designer requests a new variation, developers can more easily make edits to meet that request instead of rewriting the entire component structure.
Everyone is happy (or close enough).
But first, compounding
This article is all about composable components. However, it’s important to acknowledge another approach to component creation first: compounding.
Like composable components, compound components rely on subcomponents to form a working structure. However, compound subcomponents are not self-sufficient; they need the whole orchestra to function.
Alright, let’s get back to business.
At Belka, we use both composable and compound components - that’s why step 1 is always deciding which approach to use.
Step 1: Evaluate your needs
Composition is complex and requires a substantial initial investment of time and cognitive effort. So, before you go all-in, think about where it would be most useful.
It’s not necessary to apply composition to every component. The smaller ones like badges won’t require the flexibility it offers, while the most complex – those that have numerous variants and modifications – would highly benefit from it.
Sometimes, you’ll know straight away when a component needs to be composable. A card component, for example, can be built to contain a number of variable content types, such as titles, bodies, images, and tags. Covering and maintaining all that with a monolithic component would cost your team many sleepless nights.
Other times, the complexity of a component becomes apparent only during development, prompting a shift in your approach.
Another important criteria for your decision is the type of library you are using. If your library already supports composable components, it’s only practical to follow their approach. For libraries that don’t support composable components, adapting to the existing framework is more practical.
Step 2: Identify subcomponents
Once you’ve decided to use composition and selected your components, it’s time to decide how to do it.
Take a good look at your component. Can you see a logical way to divide it into subcomponents? Are they reusable in other contexts? If the answer is “yes!”, these subcomponents should be separated from the parent component.
Let’s look at an example: creating dialog. A header, body, and footer is a good starting point. However, dialog design can be more intricate if it includes different types of content – like a title, some text, or an image.
It’s best to create one subcomponent to manage each of these elements autonomously – DialogTitle, DialogImage, etc. This way, subcomponents are easily reusable across different dialogs, while maintaining consistency and increasing the tempo of development.
Step 3: Compose your components
Finally, it’s time to actually build your composable component. To support you, we prepared a GitHub repository, so you can have a sneak peek into Belka’s code.
The repo contains a composable dialog component (using Radix for headless UI primitives, styled with Tailwind CSS and cva), including all the ready-to-use subcomponents like DialogTitle and DialogImage.
Now, back to composing! The first thing to do is create the parent component:
Before coding subcomponents, consider whether you need to create a context – a way to pass information between the parent component and its subcomponents. It’s not essential, but it is very useful when the parent component has a lot of data to pass to the subcomponents.
One way to do this is prop drilling – passing data through many nested components. Prop drilling, however, can get cumbersome and confusing when too many subcomponents are involved. It’s like passing a message through each musician one by one. Instead, context works like a group chat: it’s a central place for the whole orchestra to share values, so all subcomponents can access it directly. Simple!
You can find an example of context in our repo.
Next, it’s time to create the individual subcomponents. Creating the subcomponents for our dialog can look something like this:
This is when you define the style, variants and component’s business logic - the logic that determines how data is processed, how decisions are made, and how the app behaves - for every subcomponent.
With all your subcomponents ready, assemble the dialog component where it is needed most - either in a dedicated file or directly within the application. The component file will look something like this:
This is quite far from a monolithic dialog component that’s stuffed with if-statements and complex business logic.
The harmony of composable components
Composition, compared to creating monolithic components, certainly requires more effort in the beginning. It involves careful planning, abstraction, and structuring to get the orchestra of components and subcomponents ready. However, this initial investment pays off in the long run through increased flexibility, easier maintenance, and overall better code organization and performance.
Next, take another look at our GitHub repository to see composable components in action.
∗ ∗ ∗
Want more insights?
- A design system to build design systems
- What’s the recipe for a successful handoff?
- Why your first attempt at a design system is likely to fail
Want a safe pair of hands to help with your own design system?
Check Design System Audit, our service to make sure your design system brings you results.