Atomic Design Thinking
Better and Faster React Apps with Atomic Design
Our common goal is clear, we want to build better and faster React Apps 🚀 Better in terms of architecture 🏰 (simpler design, non-duplicated, testable and more reliable code), and faster in terms of performance 🏎(faster loading and transition between states).
React is a component-based JavaScript library that helps us build great UIs. With React we can break down the UI into reusable components and then ensemble them together to create UIs that meet our needs. This is exactly the theory behind Atomic Design.
My purpose in this article is to explain:
- The methodology of Atomic Design
- How Atomic Design is applied to React apps
- The architectural and performance impact of Atomic Design on React apps
Atomic Design
We’ll go through the basics of Atomic Design, created by Brad Frost.
Moving away from building pages
We created web apps by building pages, which worked well for a long time. Then, it got more complex as pages needed to be served to a variety of devices. It was necessary to break these giant responsibilities into smaller and easier to manage elements.
Imagine a Lego house 🏠, built with only one big piece. This architecture, only allows us to build that house since we only have one Lego piece (page). However, if we break down the house into pieces as brick, door, and so on… We end up with a set of Lego pieces that will help us build literally anything.
How does Atomic Design work?
Innovation is looking at things from a different angle. And that’s exactly what Brad Frost did when he took a totally different approach based on another field: chemistry.
In chemistry, atoms combine together to form molecules. Then, molecules can combine further to form relatively complex organisms.
Atomic design is a methodology to create user interfaces. It helps us think of our user interfaces as both a cohesive whole and a collection of parts at the same time. It is composed of five elements:
- Atoms: The basic HTML elements as label or button. They can’t be broken down any further.
- Molecules: A composition of atoms functioning together as a unit. For example, a label and two radio buttons can create together a form item molecule.
- Organisms: They are relatively complex UI components composed of groups of molecules and/or atoms and/or other organisms. For example, a header, list or footer organisms.
- Templates: They provide context for these molecules and organisms by focusing on the page’s underlying content structure (page’s skeleton) rather than the page’s final content. For example: How the header, list, and footer would look like together.
- Pages: Specific instances of templates that show what a UI looks like with real content in place. Example: we would have a page with the template shown above and real content.
Atomic Design in React Apps
I learned Atomic Design almost at the same time that I learned React, which helped me big time. Soon, I got to the conclusion that React was meant to be used that way. React breaks down the UI into components and even the React logo is the Atom symbol ⚛️. “All makes sense” 🤔 — I told myself.
However, I have lost my focus sometimes and I ended up in the old building pages approach, that led me to have duplicated code and performance issues.
This reminded me what our purpose is: Build faster and better web applications, and for that Atomic Design is great.
How to apply Atomic Design to React
Let’s see how to use Atomic Design Thinking when building a React app.
We are going to build this app: Fish App Atomic Design
This is a simple app which has a Form to add fishes to a scrollable LinearList. The fishes have a name and a direction. Once the fish is added you can change the direction with the Reverse Direction button under the fish image.
Let’s get to it:
- We will start by breaking down the UI into atoms, molecules, organisms, templates and pages.
- Then, we will code our components from atoms to pages. Keep in mind that atoms will be used to make molecules and molecules to make organisms. Therefore, for every component, we will need to ask ourself this key question: How can this component be reusable?
- Once we have our atoms, molecules, organisms implemented and our template defined we can create the specific components for our page.
Additionally: Refactor to make sure that we are not missing any molecule or organism that can lead us to bad architecture and/or performance issues.
Break down the UI into atoms, molecules, organisms, templates and pages.
Atoms: The atoms are UI elements that can’t be broken down any further. In our example we have:
- Button:
<button>
- Icon:
<span role=”img”>
- Image:
<img>
- Label:
<label>
- TextInput:
<input type=”text”>
- RadioButton:
<input type=”radio”>
Molecules: A composition of atoms functioning together as a unit. In our example we have:
- FormItem:
<Label> + <Icon> + <TextInput>
- ImagePanel:
<Label> + <Image> + <Button>
- RadioButtonGroup:
<RadioButton> + <RadioButton>
Organisms: They are relatively complex UI components composed of groups of molecules and/or atoms and/or other organisms. In our example we have:
- Form:
<Label> + <FormItem> + <FormItem> + <Button>
- LinearList:
<ImagePanel> + <ImagePanel> + <ImagePanel> + ...
Template: They provide context for these molecules and organisms by focusing on the page’s underlying content structure (page’s skeleton). In our example, we have a template composed of a Form organism on top of a LinearList organism.
Pages: Specific instances of templates that show what a UI looks like with real content in place. In our example:
Let’s continue with the implementation of these components.
Note: Templates are normally used in the design phase, to see the possibilities that the set of components offer, so there will be no implementation of it.
We create our components from atoms to pages.
How can this component be reusable?
This is the key question. It will determine whether we are building an architecture of reusable components or just pages plenty of copy-paste code that can’t be reused to build anything.
Let’s see how to create one component per atomic element ( i.e. an Atom, a Molecule and an Organism ) with Atomic Design Thinking.
Atoms: How to create a Label atom
If I were a page builder 🙆🏻♂️ I would use the <label> html element as my atom, no need for a new component 💁🏻♂️. So, every time I need a Label in my app I do:
<label className=”fish-label”>Fish name:</label>
Where I define a className=”fish-label” and the text itself.
With this approach, I’m not only duplicating the same code all over my app but exposing my app to give different classNames that can lead to design inconsistencies across the app. And of course, not mentioning that I would need to test the same line of code a thousand times, once per instance.
However, I will use Atomic Design Thinking 💪💪 , so… How can this component be reusable? 🤔
function Label(props) {
return <label className=”fish-label”>{props.children}</label>;
}
Answer: 👉 New Atom 👈 By passing a text as props.children into a function component that returns a Label 🧙♂️
Now we have a Label Atom. The same code will be used plenty of times instead of copy-pasted. We’ll always have the “fish-label” className and we only have to test this single line of code. This is what I meant with better architecture (less code, non-duplicated, testable and more reliable).
Note: Sometimes our designs require to have different variants of the same Atom. But that’s no reason to fall into building different and specific atoms. We can make our Atom able to support variants.
Molecules: How to create a FormItem molecule
This is a good one🙅🏻♂️. The FormItems in our design have a Label, an Icon that displays a tooltip Label on hover and finally a form atom as TextInput in this case.
If we don’t think atomically we will create a Form of several FormItems and all of them will have the same code for these four different Atoms. Which means four times more code than necessary.
But wait! That’s why we think atomically 💪💪 So… How can this component be reusable? 🤔
function FormItem(props) {
const [showHint, setShowHint] = useState(false);
return (
<div className=”fish-formitem”>
<div className=”fish-formitem-label”>
<Label>
{props.label}
<Icon
type=”🤔”
ariaLabel=”question-help-icon”
onMouseEnter={() => setShowHint(!showHint)}
onMouseLeave={() => setShowHint(!showHint)}
/>
</Label>
<div className=”fish-hint”>
{showHint && <Label>{props.hint}</Label>}
</div>
</div>
{props.children} // TextInput instance or other
</div>
);
}
Answer: 👉 New Molecule 👈 By passing the label text, hint text as props and the TextInput as a child into a function component that returns all atoms combined.
So now, we have a FormItem component that will serve for any Form atom as RadioButtonGroup or Checkbox.
And this is how we will instantiate it:
<FormItem label="Fish name:" hint="Give me a name">
<TextInput
value={fishName
name="fish-name
onChange={handleFishNameChange}
/>
</FormItem>
Amazing, we don’t even need to handle the hint icon since that is done by the FormItem internally. This is also unit tested only once and we feel confident to use this molecule many other times. Also worth to mention that we save 12 lines of code per FormItem which can be massive in forms of 10+ items.
Organisms: How to create a LinearList organism
We need to create a Linear list that contains these PicturePanel molecules.
We use Atomic Design Thinking and we ask ourselves the magic question 🤔 which tells us how to create a reusable component to be reused in the future for any other picture type (e.g. birds).
function LinearList(props) {
return (
<div className="fish-linear-list">
{props.children.map((item, key) => (
<div className="fish-linear-list-item" key={key}>
{item}
</div>
))}
</div>
);
}
Answer: 👉 New Organism 👈 A LinearList functional component that receives children as props (PicturePanel molecules). However, we prepared this Organism to render a LinearList of absolutely anything 🤩
Templates:
No need to implement anything here, we have our template designed which is a Form organism on top of a LinearList organism 🔮
Pages:
Now that we have our atoms, molecules and organisms in place. We can go into the specifics and build our pages.
Firstly, we create the instance of the Form organism, named FishForm, that will be responsible for the Form logic:
function FishForm(props) {
const [fishName, setFishName] = useState('');
const [fishDirection, setFishDirection] = useState(null);
const [formError, setFormError] = useState(false);
const handleFishNameChange = event => {...};
const handleFishDirectionChange = event => {...}
const handleSubmit = event => {
...
props.onSubmit({ fishName, fishDirection });
}return (
<Form
name="fish-form"
label="ADD A FISH"
handleSubmit={handleSubmit}
formError={formError}
errorMessage="Set name and direction"
>
<FormItem label="Fish name:" hint="Give me a name">
<TextInput
value={fishName}
name="fish-name"
onChange={handleFishNameChange}
/>
</FormItem>
<FormItem
label="Fish direction:"
hint="Direction the fish will head"
>
<RadioButtons
name="right-left-radio"
options={['Left', 'Right']}
valueSelected={fishDirection}
onChange={handleFishDirectionChange}
/>
</FormItem>
</Form>
);
}
This function component will only receive the onSubmit function as props, which will be used to add fishes to an Array of fishes in the MainPage component.
Secondly, we create the instance of the LinearList organism, named FishList:
function FishList(props) {
if (!props.fishes || props.fishes.length === 0) return null;return (
<LinearList>
{props.fishes.map((fish, key) => (
<PicturePanel
key={key}
name={fish.fishName}
direction={fish.fishDirection}
src={fishImage}
/>
))}
</LinearList>
);
}
This function component will only receive the Array of fishes as props, which will be defined in the MainPage component.
And finally 🎉, we have our MainPage component:
function MainPage() {
const [fishes, setFishes] = useState([]);const handleSubmit = newFish => {
setFishes([...fishes, newFish]);
};return (
<>
<FishForm onSubmit={handleSubmit} />
<FishList fishes={fishes} />
</>
);
}
This function component manages the main state, which is an Array of fishes named fishes. The handleSubmit function receives a newFish and updates the fishes state with it. The MainPage component will return the FishForm and FishList molecules.
And that’s it 🎉 we have all our fishes in place looking for Nemo 🐟
Architectural impact
With Atomic Design Thinking we can create better apps in terms of better architecture:
- Modular and Scalable: It provides a collection of independent and reusable web components that can be assembled together to build any user interface in a better and faster manner.
- Unduplicated code: The atoms, molecules, and organisms make sure that we only build these components once.
- Consistent: The code and the UI will look consistent across the app.
- Tested and Reliable code: The components are independent and once tested we are confident to use them anywhere.
Performance impact
And also, faster apps in terms of performance (loading and transitions between states):
- Faster loading: Faster loading is all about the code size. The less code the better. If we follow the Atomic Design properly we are going to save quite a lot of code.
Note: We can split the code and just load the needed files, but even then, the less code the better. If your only load your main page with has plenty of duplicated code, its size will be much bigger and it will take longer to load.
- Transitions between states: By breaking down the app properly we will make sure that we re-render only the specific components that need to. Some components perform API calls to a server. If we don’t break this down properly when the app is going from one state to the next, it might render more components than it should which would entail to more and unnecessary API calls.
For example, suppose the following scenario in our Fish app:
- The Image component needs to make an API call to get info about the fish every time it renders.
- We missed the ImagePanel molecule and the fish images are built inside the LinearList component, which manages the state for all fishes. This means that all images will be part of the same whole and share state.
- Therefore, every time we reverse any fish picture, the shared state in LinearList will change and all the pictures will unnecessarily re-render.
- The result, many and unnecessary API calls.
- Expected, only that single fish picture component should call the API.
Wrap up!
Atomic Design is a powerful methodology that provides a collection of independent and reusable web components that can be assembled together to build any user interface in a better and faster manner.
We need to identify properly the atoms, molecules, and organisms and then, always ask ourselves the magic question:
How can this component be reusable?
Atomic Design will impact our architecture by adding modularity, scalability, consistency, and reliability as well as our performance, by having much less code and handle the transitions between states more efficiently.
Check the app code 👩💻
See the app running 🚀