DEV Community

loading...
Cover image for A Better Way to Structure React Projects

A Better Way to Structure React Projects

krisguzman_dev profile image Kris Guzman ・5 min read

Image for post

First off, if you don’t know what atomic design is, I’ll give a brief primer below but I suggest you go to Brad Frost’s website and check it out.

Atomic design is Brad Frost’s methodology for building design systems. The idea is that we can take the basic building blocks of living things and give our UI a hierarchical structure based on it.

Brad Frost defines five main stages of UI components:

  1. Atoms
  2. Molecules
  3. Organisms
  4. Templates
  5. Pages

Atoms are the simplest form of UI, consisting of things like headers, labels, input fields, buttons, etc.

Molecules are a combination of atoms that form more complex pieces of our UI, such as a search field with a submit button.

Organisms build on top of molecules and orchestrate larger parts of the UI. This can include a list of products, a header, forms, etc. Organisms can even include other organisms.

Templates are where our pages start to come together, giving context to all of our organisms and molecules by giving them a unified purpose. For example, a template for a contact page will have organisms for headers and forms, and molecules for text fields and navigation bars.

Pages, as the name implies, is our final page with all its content. The difference between pages and templates is that templates don’t provide any content.

Goal of This Article

I want to present a way to apply atomic design in your development workflow with the goal of making your code and project structure more maintainable, scalable, and intuitive.

Additionally, atomic design is a framework that can help bridge the communication gap between development and design. It is important that developers understand it so they speak to designers in a unified language.

When components are appropriately categorized, teams will be surprised to see how much more intuitive and organized their UI systems feel. Code is more maintainable, updates and refactors are easier, and on-boarding new designers and developers becomes a more efficient process.

Common Project Structure

Many of you are probably familiar with the folder structure below:

Image for post

The idea here is that we try to keep components as “dumb” and as stateless as possible, while containers orchestrate our components and act as our pages.

I think we should get rid of this whole “container” vs. “component” concept for the following reasons:

  1. It’s not scalable. We only have two buckets to dump our UI in. What happens when we have 200 components and just two categories? Exactly.
  2. It’s not intuitive. What’s a container, really? Is it a page? Or is it just a stateful component? Where do the two meet?
  3. It’s a dev concept, not a design concept. How can we efficiently cooperate with designers if we are speaking a different language? Designers aren’t concerned with stateful vs. non-stateful, they are concerned with application and relationships. How is the component used, and how does it relate to the rest of the UI?

A Better Project Structure

Why don’t we structure our project according to the stages of atomic design?

Image for post

Notice how we put pages outside of our components folder. Intuitively, we don’t put pages in the same category as the other four stages. Pages are the final presentation, not the individual components themselves.

Also, I typically create a /forms folder inside the root organisms folder. Forms are super common so it’s a nice way to make your folder structure a bit more granular.

So, what do we gain here by making this small adjustment? Surprisingly, a lot!

  1. Unified design and dev language. It is much easier to collaborate with your design team now that you are using the same lingo across the board. For my devs using storybook out there, this is even more beneficial.
  2. Scalable folder structure. We now have refined categories for our components. It is much easier to keep our UI organized even if we have 200+ components.
  3. Minimal project context needed. Normally, a new developer needs to have a good amount of context on a project in order to find components they need. With atomic design, the language and structure are more universal, so it is much easier to say: “Well, a header is an organism, so let me check the organism folder.”
  4. Cleaner code. How, you ask? Well, atomic design forces you to better understand the relationship between your components. Even if you start with a big, messy, multi-functional component, refactoring is a breeze when you start thinking: “OK, where do I see organisms, molecules, atoms, etc.”

What’s Next?

Congrats, you have a rock-solid folder structure for your next UI project. Is that all?

Let’s fast-forward a couple of weeks. You have a few atoms, a few molecules, life is good, you’re thinking: “This is working out great! Kris isn’t so dumb after all!”

But then you hit a roadblock… You are building a complex component and aren’t sure whether it’s an organism or a molecule. Can molecules have state? When does a molecule become an organism? For that matter, where is the line between an organism and a template? A template and a page?

The lines between stages can get fuzzy in practice, so in part 2, we will take a look into how we can define these lines in a React project.

That’s all for this part, thanks for reading!

Discussion (14)

pic
Editor guide
Collapse
joelbonetr profile image
JoelBonetR • Edited

Nice way to think about it, I can explain to you my experience.

It's not done like that because atomic design applies to design as the name itself implies.
It's a bad way to structure code on a IT project because you lack dynamism and it makes you generate extra boilerplate and unnecessary overrides on your code.

The best you can do is to generate a SCSS framework according to your atomic design.
The framework can be structured like this but is not convenient, I'm sure you prefer to import a modals.scss instead importing an organisms.scss (which implicitly must need to import all primary files) so you'll be adding all the CSS on every page.

if you generate a modals.scss which contains the necessary items to create a modal you don't need to import the code that deals with... accordions or another item on the same site. Clearer, cleaner and more usable and better for performance.

Moreover the design system is meant to be used on design, not on development, at least not "as is". You'll need to define some primary attributes for each item on the design system but the rest leads to the context on where this items are shown.

For example all buttons can contain a border-radius: 4px; but if you want to create submit button it can be with a light background, a nice regular font and a light box-shadow, then you'll need to create a call to action button you probably will use a more aggressive box-shadow, a different background color and a heavier font. So there's no point on getting an atom called "button".

That's what BEM or other methodologies comes to solve at the end.

Collapse
krisguzman_dev profile image
Kris Guzman Author

Hi, thanks for the thoughtful reply. I’m not quite sure I understand the implications you outlined. I think you made quite a few assumptions about how styling would work with an atomic design component folder structure.

There is no need to have a big organisms.scss file, each component can have its own styling. I don’t personally use scss, I use styled components which works very well with this structure. But the same can be achieved with scss without a problem.

Your point about there being no purpose in having an atom called “button” isn’t really connected to atomic design. That’s a project specific decision. There’s nothing wrong with making a second atom called SubmitButton if you need it, and keeping your buttons in a folder called atoms/buttons if you find that you have several different kinds of buttons.

It sounds like you made the assumption that I’m encouraging a super rigid structure, but my only point was that this is a “better” way to structure a React project in comparison to container / component structures. The “best” way is to utilize various patterns, such as atomic design, and incorporate the best parts into your own workflow.

This folder structure is a good starting point, but of course it can be extended / modified to suite the needs of a specific project. Each of my projects utilize a variation of this pattern, but by no means are they identical.

Thanks for reading!

Collapse
joelbonetr profile image
JoelBonetR • Edited

I'll add an example using a basic common CSS (or Sass) methodology:

buttons.scss

@use './variables.scss';

.btn { 
  border-radius: 4px;
  border: none;
  &.btn-primary {
    background: $primary-background;
    box-shadow: $top-layer-shadow;
    font-family: $bolder-typo;
  }
  &.btn-default {
    background: $default-background;
    box-shadow: $regular-layer-shadow;
    font-family: $regular-typo;
  }
}

home.scss

 @use '../buttons.scss';
  // specific context view (page) styles (usually non reusable)

on HTML:

  <link rel="../scss/home.scss" ... />
  <header>
      <!-- some nice catching banner -->
      <button class="btn btn-primary"> Get yours at 50% discount!   </button>
  </header>
  <!-- informative content  -->
  <footer>
      <!-- some contact / leads secondary/basic form -->
      <button class="btn btn-default"> Submit 
    </footer>

As you can see it's very straightforward and very semantically strict which makes the things easier to devs.
Also on terms of performance you add what you need excepting few vars that hopelessly will come across with variables.scss (which also could be named customize.scss or similar if you build a framework).

Let's try to use atomic design to reach the same result:

../atoms/shadows.scss

 .top-layer-shadow {
    box-shadow: 0 0 5px rgba(20, 20, 20, .8);
 }
 .regular-layer-shadow {
    box-shadow: 0 0 4px rgba(66, 66, 66, .55);
 }

../atoms/backgrounds.scss

.default-background {
  background: #fefefe;    
}
.primary-background {
  background: #fec940;
}

../atoms/fonts.scss

.regular-typo {
  font-family: 'Ubuntu-regular';
}
.bolder-typo{
  font-family: 'Ubuntu-bold';
}

../atoms/buttons.scss

.btn { 
  border-radius: 4px;
  border: none;
}

html:

   <link rel="../scss/atoms/shadows.scss" ... />
   <link rel="../scss/atoms/backgrounds.scss" ... />
   <link rel="../scss/atoms/fonts.scss" ... />
   <link rel="../scss/atoms/buttons.scss" ... />
    <header>
      <!-- some nice catching banner -->
      <button class="btn primary-background bolder-typo top-layer-shadow "> Get yours at 50% discount!   </button>
    </header>
    <!-- informative content  -->
    <footer>
      <!-- some contact / leads secondary/basic form -->
      <button class="btn default-background regular-typo regular-layer-shadow"> Submit   </button>
    </footer>

you can, of course, import the things on the scss like that too as alternative:
home.scss

@use './scss/atoms/shadows.scss'; 
@use '../scss/atoms/backgrounds.scss';
@use '../scss/atoms/fonts.scss';
@use '../scss/atoms/buttons.scss';

and load the home.scss on the html as single item, but this means you'll load across tones of super-componentized style sheets.

I'm not assuming a specific way to organize the code, i'm just translating the atomic design concept into the code.
I've a draft post about CSS methodologies, comparing them all and taking own conclusions which I'll finish to write when I can (I lack enough time to do it at this point, give me some weeks haha) it's not an easy task... on a little project, maybe. On a big project where you have a total line count over 10.000 lines of scss code or more it's a mess not using the right methodologies.

I've tried to translate atomic into scss properly before, the first time it was a mess for being more strict than needed on dev time, then I need to refactor things up and get a more usable code on it (i'm talking about a real project which is into production with 20K+ customers on it and which implies connection between many company Apps as a wrapper to add a service as value to our customers).

In fact I wrote a complete framework which I need to refactor for making it usable for everyone (adding customisation vars on each piece of code instead adding values according to our design system) so I've that pending too on my list and I'll give that free for everyone too so you can collaborate, give feedback and use it on your projects.

TL;DR:
Design has its process. On development, we have ours. It can not be done the same way because the tools we use are too different (on design you can easily copy-paste -> edit an element while on development you need to import -> override, causing overheating, being less able to override in the future and performance issues) so we need to found the best way to work on each development stage instead trying to find out a single way to work for eveyone.

Thread Thread
krisguzman_dev profile image
Kris Guzman Author

I appreciate you breaking down an example! I see what you're saying, you find it difficult to pull out common styling while also having component specific styling.

The only part where I'm confused is why you feel you are forced to structure your scss files that way with an atomic design structure? I personally wouldn't do that either.

For example, I usually using CSS-in-JS libraries, which allows me to setup a "theme". This is where I would put my fonts, shadows, global button styles, etc. Material UI does this pretty well.

In SCSS, I imagine you can do something similar. But if you don't want a global theme file, you can still have a structure where your actual components are in, for example: components/atoms/* and you have a "master" style sheet for all buttons at components/atoms/buttons.scss. If you don't want your SCSS files in your components folder, then you can still do something like scss/buttons.scss no problem.

Maybe I'm misunderstanding, but it looks like you are trying to enforce atomic design principles in your styles. Atomic design is an abstract way to categorize components, but there's no reason you have to apply the same structure to your styles.

Thread Thread
joelbonetr profile image
JoelBonetR

Seems we are on a middle point. We does not use libraries or frameworks to handle styling because we are seeking performance on our projects and those frameworks and libraries are generic tools that solves a need but not in the better way (according to performance).

If you abstract your structure and keep your files on a correct way (semantically) and you inherit what is needed on each place you'll found yourself breaking up the atomic principles, that's because it's a design methodology which does not fit into a development process as is.

The only way to fit atomic design into development is having a greenfield where components look always the same regardless of the context (which is never true because you'll get a strict but unbalanced design) so you can reuse all components as is. Working on big projects makes you think how to handle the things for being nice to handle (or less hell like) for the developers while maintaining the performance and scalability (avoid overrides when possible, handle theming variables for re-desing easily and quickly and so).

The project structure deals with the speed devs can find the exact files they (we) need to edit something, avoid duplicity and avoid any other concern.

Using CSS-in-JS makes you handle a styling for a component directly inside the same component. This, even being ok in most cases must be used together with scss. Scss for the global things, css-in-js for specific component styling so you get an inheritance Sass -> component.

Imagine you have 68 components which uses an outline-color: blue and you need to redesign (by a rebranding process or whatever) that to be green. You'll need to edit it in 68 places instead doing that on a global place which is more correct and brainless.

It always depend on how big is your project and how much it's going to grow so you can analyze, choose and define the right methodology and patterns for it. You can fail and learn, you can success and learn too but there's experience from others that can help you with that too.

Thread Thread
krisguzman_dev profile image
Kris Guzman Author

Maybe it would help if I explain what I typically do with this kind of setup.

I have a components folder with an atomic design structure, and I also have a "domains" folder so that I can create one-off components and business logic heavy components that are specific to a piece of the application. (resolving the limitation you mentioned about having greenfield components).

For styling, you wrongly state that css-in-js makes you handle styling directly in the component. At it's most basic level, yes, but I use a theme object in my projects, which contain styles for shadows, colors, spacing, etc, that can be setup in one place and used anywhere. I also have a globalStyles.ts file (for styled components) where I set global styles for typography and fonts.

I think it's important to remember that there is no universal project structure, and I never said there was. This project structure, like any other, can be adapted, modified, and built upon to suit the needs of a specific project (large and small).

Thread Thread
joelbonetr profile image
JoelBonetR

nice I'm getting more what you tried to explain, thanks for clarifying!

Collapse
gruckion profile image
Stephen Rayner

Use it in production in a large application. Atomic design sounds fancy but it’s not a good solution.

Doesn’t simply the process and contains much boilerplate.

Collapse
gruckion profile image
Stephen Rayner

Totally agree

Collapse
gruckion profile image
Stephen Rayner

Having a single folder full of components just makes me think you haven’t worked on a large project. Even if you divide it into AMO folders you still end up with hundreds of components.

The better solution is the feature folder layout.

Collapse
krisguzman_dev profile image
Kris Guzman Author

I have actually used this with large teams pretty successfully.

I'm not against a feature folder structure, I think it works fantastic in conjunction with an atomic design structure. There's no one size fits all.

The problem with feature folder layouts is that all components become tightly coupled with their domain context, which works okay for slow changing systems, but not so great for fast paced startups.

Atomic design takes way some of that domain specific context, which has its pros and cons. What I personally do is a combination of what you mentioned, a feature folder layout, along with an atomic design structure for more reusable components.

I found that even with devs coming in and out of a project, it generally works fine and keeps things organized. When we build out storybook for a component, it's easy to keep it in sync with what the design team has, and easy for the design team to audit the current state of the UI.

Perhaps my article came across as a bit dogmatic, but I'm aware that a little controversy usually gets a good conversation going :)

Collapse
joelbonetr profile image
JoelBonetR • Edited

We usually make something like that:

having some styling globals 'variables.scss, common.scss, modals.scss, fonts.scss, colors.scss, accordion.scss and so' organised on semantically understandable directory tree.

Then each component has it's own scss where you import (@use) the necessary globals.

We find this was as more suitable for big projects but i'm not sure if it's the best way yet, how do you manage it?

Collapse
gruckion profile image
Stephen Rayner

“Well, a header is an organism, so let me check the organism folder.”

Or you could check feature/header/components

Collapse
krisguzman_dev profile image
Kris Guzman Author

Is a header a "feature"? I thought it was a part of layout? What defines a feature?

But I see your point: there are logical ways to organize these things that can be more intuitive for a developer.

What I'm getting at is that atomic design provides more of a unified language for organization. More so, it's a common framework that designers follow, and could help back and forth communication.

Controversial? Certainly. Can it be improved upon? Hell yeah, that's what part 2 is for :)