DEV Community

Cover image for Proceed with Caution: Atomic Design's Impact on Your Thought Process
Kevin Ross
Kevin Ross

Posted on

Proceed with Caution: Atomic Design's Impact on Your Thought Process

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:

  1. Atoms (made up of HTML tags)
  2. Molecules (made up of atoms)
  3. Organisms (made up of molecules)
  4. Templates (made up of organisms)
  5. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)