In web development, Atomic design is a methodology for creating design systems.
It suggests breaking up your components into 5 levels, Atoms are your most basic building block & each level combines elements from levels below it:
- Atoms (made up of HTML tags)
- Molecules (made up of atoms)
- Organisms (made up of molecules)
- Templates (made up of organisms)
- Pages (filled in template)
I believe this is a great way for designers to think, they can break down a design into reusable & configurable pieces. I've seen this work well for creating quick & consistent Figma designs.
However, I think there are some problems with how this methodology can encourage developers to think.
Rigid abstraction levels, unnatural categorisation & bike-shedding
Atomic design suggests 5 core levels of abstraction, but to me this is an arbitrary set of component categories.
One problem with having these fixed categories is that it can encourage developers to break down their components into levels that are unnatural for the domain you may be working in, just to them fit into the suggested categories.
We've all learnt a new pattern or technique & then excitedly attempt to use it everywhere, when applying Atomic Design you can easily waste time trying to break down your code into these levels instead of just adding the feature.
The act of categorisation can itself be a task.
For example, I might want a page with this structure:
- Page
- TableForFinance
- TableRow x n
- TableCell x n
The TableCell
would be a td
& the TableRow
a tr
which would make them Atoms in Atomic design, but tr
tags must wrap td
tags.
So does that make TableRow
an Atom or Molecule? (feels like a pretty small Molecule)
The TableForFinance
feels like an organism, but if we do decide that TableRow
& TableCell
are Atoms, does that make it a Molecule? (seems like a pretty big "Molecule")
These are questions that you are forced to ask yourself constantly with this methodology; the reality is there is probably a near infinite (in theory) spectrum of abstraction levels for your components.
So why try to force it into one of five?
You & your team will consume mental effort & time bike-shedding about how to classify anything but the most trivial components.
I think the reason component categorisation efforts like Atomic Design exist is because people crave some form of organisation / folder structure early in a project & want to know where to start.
It's better to start with a simple /components
folder & let its structure evolve naturally as patterns start to emerge.
Encourages making half-baked components "Public"
Atomic Design's separation of components into Atoms, Molecules, Organisms etc encourages developers to create components that are abstract enough for "generic usage", again the idea is that you create composable pieces to assemble into what you need & you get to "reuse" these pieces generously.
The problem with this is when you add a new "generic" atom, molecule etc..., you typically only have a single use case to work from.
By attempting to make a "generic" component so early & with so few usecases you are guessing what the requirements of that component might be in the future and potentially adding features you might not need.
Whatever component you come up with is likely to be half-baked & will almost certainly have to change to accommodate new use cases in future.
Now, a half-baked component isn't necessarily a bad thing if its usages is limited (e.g. to a single file) because work on it can be completed quickly, and with limited usage you can refactor it at will.
A problem arises when you expose half-baked components to be used freely in the rest of your application by dropping them in a /components/...
folder.
Take our desired page from earlier:
- Page
- TableForFinance
- TableRow x n
- TableCell x n
Imagine we implement this by having a separate component file for each level:
- TableForFinance.jsx (organism)
- TableRow.jsx (molecule)
- TableCell.jsx (atom)
We've created "generic" TableRow
& TableCell
components that we can reuse & placed them in the appropriate Atomic Design /components/...
folder.
Later, it seems correct to add 4 other tables that also make use of TableRow
& TableCell
:
- TableForBills
- TableForSales
- TableForEmployees
- TableForEquipment
Hurrah! This seems like a great application of DRY & a win for reuse, but what you actually have is a set of dependencies (TableRow
/ TableCell
) that tightly couple all of the tables that use them together.
Imagine if bit by bit TableForBills
, TableForSales
& TableForEmployees
start to diverge in terms of feature set & the new features require changes to TableRow
/ TableCell
.
Any changes made to these components will affect all tables that use them & what typically happens at this point is implementation details about the parent component start to "leak" into the children, think if (tableType == 'Sales') ...
. This will lead to a more complex TableRow
/ TableCell
component & increasingly complex test permutations.
Imagine this in a real world situation with a much more complex dependency chain... painful.
A better way to approach this is to:
Define a single TableForFinance.jsx
file that has components defined inside it for TableRow
& TableCell
, these are internal to the TableForFinance
& are considered part of its implementation, we don't expose them to the rest of the App
When we need a new similar table, e.g. TableForBills
, we copy TableForFinance.jsx
wholesale & change what is necessary
This allows TableForFinance
& TableForBills
or any other component to evolve independently without being coupled, their internal logic will be simpler & easier to test
Only when we have more examples to spot potential patterns, is when we should extract whatever logic is common between all use cases into a new component
If you or your team is actively using Atomic Design
Come to an agreement about what should be classified as an Atom, Molecule, Organism, Template, Page, this should help limit how much time you spend on these discussions
Ask yourself "Am I breaking down this feature into these levels because it makes sense or to 'fit' into the Atomic Design structure"
Let new components "bake" for longer by keeping them internal initially, then copy and pasting code into more use cases.
Create a better abstraction only once you have enough usecases to spot patterns. It might be handy to remind yourself that this could be refactored later with a ticket, it may or may not be done in future
If you are considering using Atomic Design
Consider if you actually need this structure yet.
You might be able to start with a /components
folder.
Let your components evolve naturally depending on what features you need & extract new "reusable" components once you have enough usecases to spot patterns
Once you have enough components to justify some more rigid organisation, consider adopting a more structured folder strategy (possibly Atomic Design)
At this point, you will hopefully have plenty of components to spot opportunities for refactoring & ways to group related components
Finally
Organising your code into coherent files & folders can be even more mentally taxing than writing feature code & you will almost certainly get it wrong the first time - doing this is easier with hindsight.
If you can, you should treat it as an entirely separate refactoring task & give it the appropriate time and attention.
Top comments (0)