React component libraries are becoming increasingly popular, and for good reason. They are great tools that guarantee consistency and reusability of UI elements both within and across digital products, all while having the potential to significantly reduce development time and effort.
In this tutorial, we will outline different approaches to building a custom component library. But first, I have a question for you: do you really need your own component library? Let’s talk about the benefits and trade-offs of customizing one.
Benefits of building a component library
Consistency within code
One of the greatest benefits of using a component library is that it guarantees our components look and behave consistently throughout the app. UI inconsistencies can seriously damage user experience and become the source of frustration.
Components that live in a component library are by definition reusable. This is true both within and between web apps. If a change is needed, we change it once in the component library and it’s then reflected everywhere.
It is important to ensure our web apps are as accessible as possible. With the use of a component library, a basic level of accessibility can (and should) be built directly into our components.
A component library can become a huge time-saver. Having a repository of ready-to-use components means less time to build new features. What’s more, there is less communication overhead between designers and developers because UI behavior is pre-defined.
Component libraries are a great way to create useful and comprehensive documentation about UI components. For more details on this, check out my article on self-documenting React components.
A well-built component library might be useful to others. It can be open-sourced and shared with the web dev community.
Building a custom component library sounds great, doesn’t it? But there is always a flip side.
Trade-offs to building a custom React component library
Building a component library requires a significant cross-functional, upfront effort involving design, product, and development teams. It is a huge undertaking that requires the appropriate level of resources and expertise to get all the benefits.
Maintaining a custom component library is a continuous effort. It requires constant attention to keep it up to date. Involving an open-source community creates even more overhead.
The use of a component library adds complexity to a project setup. Normally, in order to be reusable across projects, the component library lives separately from our apps and is installed as a dependency. That means every time there is a change, the component library needs to be re-published and all dependent apps updated.
A component library by design puts many constraints on what is possible to do with our components. This is usually a good thing. However, there are certain cases when the need for customization is inevitable. Striking the right balance between the flexibility and simplicity of component APIs can be extremely challenging.
To build or not to build a custom React component library?
Given the above, it seems that for most projects, building your own component library from scratch is simply not worth the time and effort. The good news is, you might not have to!
There are a multitude of ways to balance the benefits and trade-offs of creating component libraries, and the React ecosystem is here to help.
Approaches to building a React component library
1. From scratch
As implied by the name, with this approach, we create everything ourselves — from UI rules and patterns to the components themselves. We use React, perhaps together with our favorite CSS-in-JS library (if you are into that kind of thing 😉), but everything else is up to us to build.
- Almost infinite freedom to create, build, and customize as needed
- Minimal dependence on third parties
- Full control over the source code
- Huge cross-functional upfront effort
- Continuous maintenance cost
Building a React component library from scratch is mainly suitable for complex and somewhat idiosyncratic digital products, spanning across multiple web apps, and with a large enough development team to be able to withstand the cost.
2. Introduce constraints with Tailwind CSS, styled-components, and Styled System
Building our own structure and constraints when it comes to the basic building blocks of our component library — colors, typography, spacing, shadows, etc. — is both difficult and, in most cases, unnecessary. Great solutions already exist!
What immediately comes to mind in this context is Tailwind CSS. It allows us to build complex UIs with a limited and constrained-based set of style utilities. Tailwind is easy for developers to adopt and get familiar with and is also extremely customizable and flexible.
If we opt for a CSS-in-JS solution and styled-components in particular, another approach to introducing some useful constraints is to use Styled System. With it, we can structure our theme in extremely useful ways and take advantage of a number of utilities while retaining significant control when it comes to building our components.
- Freedom to create, build, and customize as needed
- The basic constraint-based building blocks provide structure
- We introduce a dependency at the very core of our component library — migrating away from it down the line might prove impossible
- Maintenance costs (while somewhat reduced) remains our responsibility
This approach is suitable for projects with UI components that require a lot of flexibility and freedom to customize. Before settling on a solution, ensure the constraints that are introduced are suitable for the project’s particular use cases and are in line with design and development requirements.
3. Use selected components, such as React date-picker, react-tippy, and Reach UI
There are certain React components that are notoriously difficult to create from scratch. Think of a select component that needs to support features like search, multi-select, creating your own option is non-exists, keyboard control, and responsiveness.
What about a date picker with a calendar, keyboard controls, masked input, and validations that are also accessible? And how about a tooltip that is animated, customizable, and always positions itself correctly? Even widely used components like modal and tabs can be challenging to create properly from scratch.
A great way to save time and effort while creating a component library is to leverage React’s ecosystem for these more complex components. Going back to the list from above, here are some examples.
When it comes to a select component, react-select comes with all of the already mentioned features and more out of the box. For date pickers, there are also some great options, depending on the use case, including react-date-picker, react-datepicker, and AirBnB’s react-dates.
When it comes to tooltips, react-tippy has us covered. Finally, Reach UI is an example of a library that provides truly accessible and customizable components like modals, tabs, menus and so much more. They can all be installed separately, so choose all or only some of them to add to your project.
On the flip side, adding third-party components to your library comes with some trade-offs.
- Saves time and resources by taking advantage of battle-tested UIs while reducing complexity in our own codebase
- Using external components selectively minimizes the impact of the dependencies
- Maintenance cost for the component library is somewhat reduced
- Places significant constraints on the behavior and features of our dependent components
- Introduces a number of third-party dependencies that we have little control over
- External components are sometimes difficult to fully customize
Using this approach takes a huge load off the component library development team. Still, when deciding to integrate third-party components, you must consider the long-term impact and make sure, to the extent that it is possible, that these components correspond to the project needs.
4. Highly customizable React component libraries like Chakra UI
It is often the case that the time and resources we are willing to invest in a component library for our project simply does not equate to what is needed to implement the approaches above. This could be the case if the scope of our project is somewhat limited or, even with bigger projects, if the UI components we need are relatively standard.
We still want to be able to style and somewhat customize our components, but we are happy to rely on the sensible component APIs determined by an external base component library. Luckily, there are several options to choose from in the React ecosystem. While comprehensive lists of React component libraries can be found elsewhere, I consider Chakra UI to be a suitable choice for this approach in the vast majority of cases.
- Reduces significantly the time and effort needed to implement components
- Component styling is easy to customize
- Offloads most of the maintenance cost
- We are fully dependent on our library of choice, so migrating away from it may prove impossible
- There is a risk of project requirements diverging from what is available in the external library over time
Using an existing component library for our project is the way to go if we value development speed while being confident that future requirements will not clash with our choice.
5. Ready-to-use component libraries
Finally, we can build our project with a component library that is ready to use. These solutions, while being somewhat customizable, provide us with everything we need straight away, from color schemas and a theme to a comprehensive collection of components.
A prime example of this is Google’s Material UI. This library is truly extensive, which makes it perfect if you want to start building your React app with minimal setup. Because of this, Material UI is, without a doubt, my tool of choice when building prototypes and proofs of concept. It is also handy during interview tasks when time is of the essence.
There are, however, significant downsides to tightly coupling a real-life project to a strongly opinionated component library. Let’s break it down.
- Easy and simple to get started, meaning minimal upfront effort required
- Minimal maintenance cost
- Customization is difficult from the start
- Most of the functionality and styles are pre-defined. This can make our UI feel generic and similar to others that use the same library
- Both designers and developers must deal with constraints and design principles that they have no control over
The approach we choose when building (or not building) our custom component library is a strategic decision that will impact our React project from start to finish. Understanding the implications of this choice and being able to adequately assess benefits and trade-offs is of paramount importance. I hope the above overview helps us be better equipped for the challenge.
If you found this article useful, follow me on Twitter for more tech content!
LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.