How to build a scalable React UI Kit
At Travix we have a single product with 4 different themes. Each theme is an independent brand and each brand has some websites. Currently, we have more than 45 websites running on our platform.
To keep everything working we have something close to 40 frontend developers working on the product and it’s quite hard to keep the codebase and design consistent.
If you work in a big React project you probably have some kind of UI Kit integrated into your codebase. The common Button component, a Modal generic implementation, a calendar which you use all the time, common components that don’t contain any business logic.
The main goal of this article is to help you to develop and organize your UI Kit, so you can improve your development process and the design consistency of your project.
As you can imagine, this library will not depend only on your development team. You should also involve the designers. It doesn’t matter how skilled you are or which technologies you pick if your design team asks for 9 different buttons and 22 font sizes.
If you are lucky, this will not be a big problem. It’s becoming quite common in design teams to have a style guide. But if luck is not your best skill, you can have something like grayCantBeLighterThanThis variable name in your theme (true story).
Technology
As the title says we are using React to build our application. Most of the tips in this article can easily be used in different architectures since they have similar tools and libraries.
Storybook
Storybook will be your development playground and component gallery. It helps to think in your component as an isolated package and to disassociate them to your product, so you can have generic and well-described properties.
Not reusable component
Generic component
Storybook is also great at providing documentation. There is an addon called storybook-addon-info which automatically adds the component JSX and prop-types description to the component page.
Running your whole application probably requires few steps and can be really slow. Storybook is usually faster to start, so it improves your development speed. You can develop your UI components mocking all the properties (storybook-addon-knobs is perfect for mocking) in a completely isolated playground without running your main application.
Styled Components
You will also need something to deal with theming. We tried pure CSS variables before and it worked well. Your bundle will have two CSS files, one with the library component and another for variables declaration. If you have more than one theme, you just need to switch the theme file and it should work.
In our new library, we decided to go with Styled Components. It already provides a theming solution and CSS in JS works pretty well with React. You also have scoped CSS by default which solves the biggest problem in Computer Science, naming.
This is a great way to avoid any unexpected CSS override and also prevents developers to intentionally override CSS rules. Your component behaviour is coupled attached to it's own properties definition and no one can globally override styles.
It also helps you to better define conditional rules in your styles because you don't need to extend CSS classes or to manage them in your element. You basically pass props to a view component and this component can use it to deal with edge cases.
At Travix, we created a Storybook addon to easily change our theme. If you have played with Styled Components ThemeProvider, you know that we basically have to change the theme property and it works like a charm. This addon helps us to test all brands while developing and also helps Product Owners (POs) and Designers to validate the implementation.
TypeScript
We had few issues to implement TypeScript. We spent at least two weeks updating our solution to support it and more two weeks migrating everything. If you decide to use it, I highly recommend to start your project with TypeScript from the scratch.
But it definitely pays off and auto-completion of properties and values helps a lot while developing. It also helps you to define better contracts to your component since any exotic property type definition turns on a red light during the review process.
Eslint
To ensure the consistency of the codebase we have a super strict eslint configuration. We decided to go full strict here so we don’t waste time reviewing minor details in the code.
Our linter automatically runs before any push with an auto-fix being amended to the last commit. It also runs in our CI machine, so if someone decides to skip the validation, it will fail during the build.
Development process
The most important part for us did not relate to the technology itself, but the process. We spent a lot of time trying to keep it as strict as possible, so we can guarantee the quality of the library in the long term. This is quite important in a big company because developers leave, developers join, but your codebase will stand the test of the time.
RFC pull request
RFC means “request for comments”. In this pull-request, we don’t implement a single line of code. We basically add a markdown file describing our component. Use cases, properties, callbacks, possible side effects.
Your new library will be used by the whole company and will have a lot of edge cases and minor details to adjust. Pull requests can easily become huge threads to discuss if the component should be a HOC or renderProps, something more composable or more strict, controlled or uncontrolled, and so on. So RFC helps you to not waste time implementing something that will be completely changed after the review.
Pull-request review
Our pull request review requires few automated steps, three developers approvals, and the PO/Designers reviews. Nothing is merged if one of this steps fails.
CI Steps:
- Run eslint and tests.
- Check if code coverage doesn’t decrease.
- Generate a unique storybook environment (it also updates the environment if you commit something new).
The developers reviewing your pull request only have to check if the implementation matches the RFC already approved and if the design is correct.
Designers and PO’s can review your implementation using the storybook environment created by your CI.
Releasing a new version
To control our release cycle we decided to use Semantic Release, so we just have to add “patch”, “minor” or “major” in our merge commit message and it will automatically release a new version to our private NPM. Our CI also updates our master storybook environment.
Conclusion
Building a UI Kit is not an easy task and will require a lot of effort from your team, but it will definitely help you to scale your application and to keep it consistent.
Any major UI update is easily achieved since we keep our UI components isolated and it’s also quite easy to add any new brand, we just need to add a new theme file.
Another benefit of moving our theme declaration to an external library is that our main application doesn’t care about theming at all. It’s stateless in that sense because our UI library takes cares of all our theming stuff.
And if you have a team of designers who code (also called Unicorns) they can start to prototype using this library. Is it the most high-fidelity prototype or not?
If you want to use something similar to what we are using at Travix I extract part of the codebase and created a small boilerplate with Styled Components, TypeScript and Jest. It was quite annoying to setup everything, so I hope you like it: https://github.com/leandrooriente/react-ui-kit-boilerplate.
Thanks for reading.