Atomic Design and Our Component Library

Dan Cherouny
inturn-eng
Published in
5 min readAug 24, 2018
Photo by Joel Filipe on Unsplash

Prereqs

Intro

Over the past several months, we have been in the process of moving our React components over to a component library (CL). This library, imported as a node package, is seen as an agnostic third-party library from which we import components for use in our main app. As we grew this CL, two things happened: our design team asked for an easy way to view all the components available to them (more on that later), and we also noticed some patterns emerge in terms of component hierarchy.

Pattern 1:

Many of our components followed Atomic Design principles. We had our own base components that overwrote HTML elements, other small components that used those base components, and so on, up to our page views.

Pattern 2:

While we were trying to keep our components agnostic, there was inevitably some business logic required for select components. Since we were keeping our CL sterile, these components did not seem to have a place there. Looking at these two patterns, we had lots of questions. Let’s take a look at some of these questions and walk through the answers we found.

How do we define the different Atomic stages?

Atomic Design defines five component stages:

Atoms:

These are the basic building blocks of the application. Just like atoms IRL, everything else is built with them. After looking at our component directory, we noticed that the most basic component that can be built on is a native HTML element. Since those already exist, we turned to components that overwrite existing HTML functionality. This includes our <Button />, <Icon />, <Input /> components, and a few more. Every other component will be built on these Atoms.

Molecules:

Molecules are basic elements that combine Atoms and organize them in a way that makes sense for given designs. Continuing with the same logic, we saw that about half of our components in the CL were simple and grouped multiple Atoms together. This posed a few more questions for us, which I’ll tackle in the next section, but generally this seemed to make sense.

Organisms:

Organisms are more complex components that combine Molecules. This one we struggled with. At first, it made sense that Organisms were made up of Molecules. As we went down this road of thought, we came up with the restraint that components were only allowed to import components which were a lower stage than themselves. This brought up a lot of discussion due to the fact that, depending on the component, you could have n number of stages due to how components might be assembled. For instance, if you have a <Table /> that has <Row /> components, and that row renders a <Cell />, which itself has an <Input /> inside of it, how are all those defined in this Atom, Molecule, Organism pattern? We realized that if we are fitting our components into a discrete number of stages, we could not use this pattern. After a lot of white-boarding, we decided to define our Organisms as complex components which are more defined visually than by looking at the code. In the example above, the <Table />would be an Organism, but the <Row /> would be a Molecule. We couldn't determine a hard rule for how to discriminate between Organisms and Molecules, but since context is important here, we are ok with that.

Templates:

We decided to ignore Templates for our purposes. Since Atomic Design was made for designers, Templates are defined as more of a deliverable and can be ignored.

Pages:

Pages are the highest-level component that contains all your Organisms, Molecules, and Atoms. Pages, or Views as we call them, are the main component that your router loads when rendering. This was a very easy distinction.

What limitations should we place on components in different stages of the Atomic Design hierarchy?

Ok, so we know what our stages are, but how do our definitions line up with the actual components that we have developed? A lot of it was obvious. Our <Button /> should be an Atom, but what about our <Well /> component?

const Well = ({
children,
style,
}) => (
<div style={{
padding: 16,
backgroundColor: ${getColor('neutral', 3),
border: `1px solid ${getColor('neutral', 2)}`,
…style
}}>
{children}
</div>
);

It’s rendering a div with some style attached to it. (Actually, it renders a styled component, but for this purpose, we used a React style prop.) Does this make it an Atom because it only renders a base HTML element? Does it make it a Molecule because it adds functionality to the div? This is where we decided to make a few rules:

  • Atoms cannot import other Atoms or Molecules
  • Atoms will only include base functionality that we are adding to our application. Adding these two rules seemed to make everything fall into place. In the case of our <Well />, we are not replacing the functionality of a div, we are simply decorating it. Because of this, it gets put into the Molecule bucket. If we were to ever write a <Div /> component, that would be added to our Atoms. Other examples of Atom components could be an <Animation /> or <Text />. This was a big distinction for us that became clear after defining Rule 2. In the case of Rule 1, we had realized there might be some confusion between Atoms and Molecules, and decided to add that rule to make a clear separation between the two. If you import a Molecule, you are Molecule or higher. With these two rules, we felt that we had a clear distinction for all components in our CL.

Where do components with business logic belong?

After some discussion, we came to the realization that not all components belong in the CL. If we continuously use a component that looks something like this:

const inturnContainer = ({ body }) => (
<div>
<H3>INTURN</H3>
<H4>{translate('select_favorite_apple')</H4>
<Paragraph>{body}</Paragraph>
</div>
)

Does that really belong in our CL? It looks very much like this component is not agnostic to business logic, based on two headers having set text. Where do components like this belong? Well, the answer is they belong in our app. It sounds obvious, but we realized that some components belong in our app instead of the CL because they are specific to our use case. This is also the case for our Pages or any other container component. This is not to say that we couldn’t make a base <Page/> component in our CL, but the specific implementation of that Page should be built in our app.

Moving Forward

As we implement these changes, we are also making sure that we add all of our components to an instance of Storybook that we set up. This ensures that our design team will have access to all of the components available, allowing us to reduce waste by minimizing the amount of new and one-off components that are developed. So, to answer the question of, "What's the point of doing this?" we can add, "Improve product development lifecycle," to the list. Growing our CL will now be as simple as recognizing where a new component fits in this cycle, developing the component proper, and adding it to our Storybook instance. The result will be cleaner component directories, clearer division of the moving parts in our dev lifecycle, and better communication with our designers. So, all in all, happier employees and better processes. Sounds like a win to me!

--

--