Building a React Component Library
With the product I work on, we have a series of micro-frontends, like independently deployable microservices but instead of server-side endpoints, they’re front-end applications. So far all of these applications are written in React.
We inherited a large codebase from an offshore team and it was littered with, you guessed it, Bootstrap, being used for a lot of the styles being written. This created a problem, to render a button, we needed the Bootstrap markup, class names and the CSS overrides to make it follow the design styleguide; if you’re counting that’s four decencies for a single button.
As we spread out into other micro applications we wanted to maintain the consistency of the UI elements, having the chained dependency like Bootstrap. We had a few guiding principles as we started to develop sharable components:
- Reduce the number of external dependencies
- Every piece of UI should be a pure instance of UI
- Components should have reuse in mind, not exclusively feature focused
- We should document the levels of composition
Early on, we decided to err on the side of if the design team has a Sketch symbol, there is a good chance there should be a corresponding React component.
Conception
We started on a single micro-frontend application fresh. This gave us the very wonderful opportunity to examine the granular pieces of UI without any external dependencies.
The result being something where the essential UI components are available on demand and serve a single instance of UI without muddling markup. For long lived applications, hell even short-lived applications, I value composition over inheritance every time.
import { Card, Button, Input, CheckBox } from "@scoped/ui-core";
const NewForm = ({ onSubmit }) => (
<Card title="User Preferences">
<form onSubmit={onSubmit}>
<Input label="Arbitary User Inputed Value" />
<CheckBox label="Boolean Preference" />
<Button onClick={onSubmit} />
</form>
</Card>
);
In this paradigm, we had a canonical <Button />
and <Input />
that was aligned with the design guidelines and was easy to prototype with when working with other designers.
Result
So we ended up with a file tree like this:
├── Button
│ ├── Button.scss
│ ├── index.js
│ └── readme.md
├── CheckBox
│ ├── Checkbox.scss
│ └── index.js
├── Input
│ ├── Input.scss
│ └── index.js
├── MultiSelect
│ ├── GridSelect.scss
│ ├── SortedListSelect.scss
│ ├── gridSelect.js
│ ├── index.js
│ └── sortedListSelect.js
├── PinningRibbon
│ ├── index.js
│ └── pinning.scss
├── Popover
│ ├── Popover.scss
│ └── index.js
├── Scroll
│ ├── Scroll.scss
│ └── index.js
├── StatePicker
│ ├── Tag.scss
│ ├── index.js
│ └── tag.js
├── Tombstone
│ ├── Tombstone.scss
│ └── index.js
├── UIDropdown
│ ├── UIDropdown.scss
│ └── index.js
├── UIModal
│ ├── ModalLoadingIndicator.js
│ ├── ModalLoadingIndicator.scss
│ ├── UIModal.scss
│ └── index.js
├── UIToolTip
│ ├── UITooltip.scss
│ └── index.js
Documentation
One wonderful outcome of writing components with a strict pattern and maintained convention was that a lot of documentation we got for free. We used React Styleguidist to generate our documentation and by simply having inlined comments, propTypes
and defaultProps
a lot of documentation came out of work we had already done.
Modifying the components that generate the layout of the documentation itself was a little tedious but worth it to gain the control we wanted over our presenting our component library to each other.
The best part however was by including a Markdown file with a code snippet in each components directory, React Styleguidist output a live editable example in our documentation.
Builds
Our style guide components has gone from living as a part of our application’s monorepo, it’s been it’s own standalone repository / package and back again. Every iteration of this collection of components has let us experiment with the builds and distribution. The common threads are always: Babel, require()
statements for Sass files, and an entry file that exposes the components through something like export { Input } from './Input'
A couple of things we noticed were that having lots on non-JavaScript dependencies (like a Sass file or a bitmap image) ended up involving more tooling to make sure the require()
and url()
statements resolved the correct paths. Aliases from webpack made this a little easier. Then sussing out the differences between peerDependencies
vs dependencies
is tricky alchemy but moving them back into the monorepo helped sort that out.
Sidenote, if you ever think, ‘nah my font files can live in with my project’, you are wrong. Your fonts belong in a CDN, trust me, we learned the hard way.
Future
Every person on our team loves working on this project and everyone of us brings a different set of ideas to where to take it next. Fortunately, working on a small agile team, we can really experiment to see what works and doesn’t work. When it works, great; when doesn’t, there’s always next sprint.
Lately we’ve played around with styled-components because being able to identify where your styles relate to your component is highly valuable and strings of classNames
are a weak identifier of that.
We’ve always wanted to experiment with static analysis with Flow and our UI library is a small enough collection and with a large enough surface area to really see any potential gains. And we’ve been feeling like propTypes
are an expensive metric to find out this information at runtime when finding out at compile time.
Key Observations
- Isolating UI to be the purest instance of that component often requires coordination but worth it in the long run.
- I value composition over inheritance every time. Encapsulating styles to their immediate need and immediate need only makes sense.
- Breaking down components into anything like
GridColumns
orFlex
may seem over-engineering but it helps simplify the composition of larger components, especially if those components take props to change their layout eg.<Column md={5} />
or<Flex wrap space='between' />
.