How to organize your components using the Atomic Design
Tags: javascript, vue, showdev, architecture
Published: 2019-11-12 21:35:14

Often, when you start the development of an application, you can get in trouble trying to understand how to organize your components.

In this post I'm going to explain to you how to do it (or how you could do it). It's the approach we adopted in [MotorK](https://www.motork.io) for both the Design System and Single Page Applications. It's called: [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/).

---

## Table of Contents

1. [Atomic Design](#atomic-design)
2. [A real example](#a-real-example)
   - [Folder structure](#folder-structure)
   - [Our atoms components](#our-atoms-components)
     - [Button](#button)
     - [Input](#input)
     - [Text](#text)
     - [Textarea](#textarea)
   - [Our molecules components](#our-molecules-components)
     - [CheckboxField](#checkboxfield)
     - [InputField](#inputfield)
     - [TextareaField](#textareafield)
   - [Our organisms components](#our-organisms-components)
     - [ContactForm](#contactform)
     - [SignupForm](#signupform)
3. [But why?](#but-why)
   - [Better organization](#better-organization)
   - [Better design](#better-design)
   - [No boundaries](#no-boundaries)

---

## Atomic Design

> Break entire interfaces down into fundamental building blocks and work up from there. That’s the basic gist of atomic design.
>
> <cite>[Brad Frost](https://bradfrost.com/)</cite>

The Atomic Design principle is to split your UI parts into "small" and reusable components in order to have a better reusability.
Like chemistry, you can organize your components in **atoms**, **molecules** and **organisms**.
In addition there are also **templates** and **pages**, but I won't talk about them because I want to keep the focus on small applications' architectures.

So, let's start talking about _atoms_, _molecules_ and _organisms_.

### Atoms

Atoms are the smallest components of your application. Basically, they can be texts, buttons, form inputs and so on.
The golden rule is: _if you can't split a component into smaller components then it must be an **atom**_.

### Molecules

Molecules are combinations of atoms bonded together. For example, if you have `Text` and `Input` atoms, you can combine them into a `InputField` (or whatever name you want) **molecule** .

### Organisms

Organisms are combinations of molecules: if you mix two or more molecules you get an **organism**.

---

## A real example

Let's try to craft an application using the Atomic Design. The final goal is to create two different forms:

- **ContactForm**
- **SignupForm**

**N.B.** _I'm going to use Vue, but you can use whatever language/framework you prefer._

### Folder structure

First of all we have to create our **folder structure** in which store our components.
So, let's create a `src` directory which will contain all the JS files and, inside it, a `components` folder. After that, we need to create `atoms`, `molecules` and `organisms` folders inside `components`.

The result should be something like this:
![Folders structure](https://thepracticaldev.s3.amazonaws.com/i/awscls7f96tnxew2kwzb.png)

`App.vue` it's our entry point.

Since we have our folder structure, we can proceed creating our components starting from **atoms**.

We'll set the **Ad** namespace in each component.

### Our atoms components

Let's create the following atoms components:

- Button
- Input
- Text
- Textarea

#### Button

```html
<template>
  <button class="a-button" :type="type">{{label}}</button>
</template>

<script>
  const _buttonTypes = ["submit", "button"];

  export default {
    name: "AdButton",
    props: {
      type: {
        type: String,
        required: true,
        default: "button",
        validator: (value) => _buttonTypes.includes(value),
      },
      label: {
        type: String,
        required: true,
      },
    },
  };
</script>
```

#### Input

```html
<template>
  <input
    v-model="value"
    :type="type"
    :id="id"
    :name="name"
    :placeholder="placeholder"
    class="a-input"
  />
</template>

<script>
  const _inputTypes = ["text", "email", "password", "checkbox"];

  export default {
    name: "AdInput",
    data() {
      return {
        value: "",
      };
    },
    props: {
      id: {
        type: String,
        required: true,
      },
      name: {
        type: String,
        required: true,
      },
      placeholder: {
        type: String,
        required: false,
        default: null,
      },
      type: {
        type: String,
        required: true,
        default: "text",
        validator: (value) => _inputTypes.includes(value),
      },
    },
  };
</script>
```

#### Text

```html
<template>
  <component :is="tag" :for="getForProp" class="a-text">
    {{content}}
  </component>
</template>

<script>
  const _tagTypes = ["h1", "h2", "h3", "p", "span", "label"];

  export default {
    name: "AdText",
    props: {
      tag: {
        type: String,
        required: true,
        default: "span",
        validator: (value) => _tagTypes.includes(value),
      },
      content: {
        type: String,
        required: true,
      },
      for: {
        type: String,
        required: false,
        default: null,
      },
    },
    computed: {
      // Rendered only if the tag is a label
      getForProp() {
        return ["label"].includes(this.tag) ? this.for : null;
      },
    },
  };
</script>
```

#### Textarea

```html
<template>
  <textarea
    v-model="value"
    :id="id"
    :name="name"
    :placeholder="placeholder"
    class="a-textarea"
  ></textarea>
</template>

<script>
  export default {
    name: "AdTextarea",
    data() {
      return {
        value: "",
      };
    },
    props: {
      id: {
        type: String,
        required: true,
      },
      name: {
        type: String,
        required: true,
      },
      placeholder: {
        type: String,
        required: false,
        default: null,
      },
    },
  };
</script>
```

### Our molecules components

And then the following molecules:

- CheckboxField
- InputField
- TextareaField

#### CheckboxField

```html
<template>
  <div class="m-checkbox-field">
    <ad-input :id="id" :name="name" type="checkbox"></ad-input>
    <ad-text tag="label" :for="id" :content="label"></ad-text>
  </div>
</template>

<script>
  import AdText from "../atoms/Text";
  import AdInput from "../atoms/Input";

  export default {
    name: "CheckboxField",
    components: {
      AdText,
      AdInput,
    },
    props: {
      id: {
        type: String,
        required: true,
      },
      name: {
        type: String,
        required: true,
      },
      label: {
        type: String,
        required: true,
      },
    },
  };
</script>
```

#### InputField

```html
<template>
  <div class="m-input-field">
    <ad-text tag="label" :for="id" :content="label"></ad-text>
    <ad-input
      :id="id"
      :name="name"
      :placeholder="placeholder"
      :type="inputType"
    ></ad-input>
  </div>
</template>

<script>
  import AdText from "../atoms/Text";
  import AdInput from "../atoms/Input";

  const _inputTypes = ["text", "email", "password"];

  export default {
    name: "InputField",
    components: {
      AdText,
      AdInput,
    },
    props: {
      id: {
        type: String,
        required: true,
      },
      name: {
        type: String,
        required: true,
      },
      label: {
        type: String,
        required: true,
      },
      placeholder: {
        type: String,
        required: false,
        default: null,
      },
      inputType: {
        type: String,
        required: false,
        default: "text",
        validator: (value) => _inputTypes.includes(value),
      },
    },
  };
</script>
```

#### TextareaField

```html
<template>
  <div class="m-textarea-field">
    <ad-text tag="label" :for="id" :content="label"></ad-text>
    <ad-textarea
      :id="id"
      :name="name"
      :placeholder="placeholder"
      type="text"
    ></ad-textarea>
  </div>
</template>

<script>
  import AdText from "../atoms/Text";
  import AdTextarea from "../atoms/Textarea";

  export default {
    name: "TextareaField",
    components: {
      AdText,
      AdTextarea,
    },
    props: {
      id: {
        type: String,
        required: true,
      },
      name: {
        type: String,
        required: true,
      },
      label: {
        type: String,
        required: true,
      },
      placeholder: {
        type: String,
        required: false,
        default: null,
      },
    },
  };
</script>
```

### Our organisms components

And finally we can develop our two organisms:

- ContactForm
- SignupForm

#### ContactForm

```html
<template>
  <form method="POST" class="o-contact-form" autocomplete="off">
    <ad-text tag="h1" content="Contact us!"></ad-text>

    <ad-input-field
      id="name"
      name="name"
      label="Insert your name"
      placeholder="Name"
    ></ad-input-field>

    <ad-input-field
      id="surname"
      name="surname"
      label="Insert your surname"
      placeholder="Surname"
    ></ad-input-field>

    <ad-input-field
      id="email"
      name="email"
      label="Email"
      input-type="email"
      placeholder="Insert your e-mail"
    ></ad-input-field>

    <ad-textarea-field
      id="textarea"
      name="textarea"
      label="Leave a message"
      placeholder="This post is amazing!"
    ></ad-textarea-field>

    <ad-checkbox-field
      id="checkbox"
      name="checkbox"
      label="Privacy policy"
    ></ad-checkbox-field>

    <ad-button type="submit" label="Submit your request"></ad-button>
  </form>
</template>

<script>
  import AdText from "../atoms/Text";
  import AdButton from "../atoms/Button";
  import AdInputField from "../molecules/InputField";
  import AdCheckboxField from "../molecules/CheckboxField";
  import AdTextareaField from "../molecules/TextareaField";

  export default {
    name: "AdContactForm",
    components: {
      AdText,
      AdButton,
      AdInputField,
      AdCheckboxField,
      AdTextareaField,
    },
  };
</script>
```

#### SignupForm

```html
<template>
  <form method="POST" class="o-signup-form" autocomplete="off">
    <ad-text tag="h1" content="Sign up!"></ad-text>

    <ad-input-field
      id="name"
      name="name"
      label="Insert your name"
      placeholder="Name"
    ></ad-input-field>

    <ad-input-field
      id="surname"
      name="surname"
      label="Insert your surname"
      placeholder="Surname"
    ></ad-input-field>

    <ad-input-field
      id="username"
      name="username"
      label="Insert your username"
      placeholder="Username"
    ></ad-input-field>

    <ad-input-field
      id="email"
      name="email"
      label="Email"
      input-type="email"
      placeholder="Insert your e-mail"
    ></ad-input-field>

    <ad-input-field
      id="password"
      name="password"
      label="Password"
      input-type="password"
      placeholder="Insert your password here"
    ></ad-input-field>

    <ad-input-field
      id="confirm-password"
      name="confirm-password"
      label="Confirm password"
      input-type="password"
      placeholder="Confirm your password"
    ></ad-input-field>

    <ad-checkbox-field
      id="amazing-checkbox"
      name="amazing_checkbox"
      label="Privacy policy"
    ></ad-checkbox-field>

    <ad-button type="submit" label="Join us!"></ad-button>
  </form>
</template>

<script>
  import AdText from "../atoms/Text";
  import AdButton from "../atoms/Button";
  import AdInputField from "../molecules/InputField";
  import AdCheckboxField from "../molecules/CheckboxField";
  import AdTextareaField from "../molecules/TextareaField";

  export default {
    name: "AdSignupForm",
    components: {
      AdText,
      AdButton,
      AdInputField,
      AdCheckboxField,
      AdTextareaField,
    },
  };
</script>
```

### Let's consume our organisms

Now that we have our organisms, it's time to use them!

Open the `App.vue` file and import the forms:

```html
<template>
  <div id="app">
    <!-- You shouldn't use them together in the same page -->
    <ad-contact-form></ad-contact-form>
    <ad-signup-form></ad-signup-form>
  </div>
</template>

<script>
  import AdSignupForm from "./components/organisms/SignupForm";
  import AdContactForm from "./components/organisms/ContactForm";

  export default {
    name: "App",
    components: {
      AdSignupForm,
      AdContactForm,
    },
  };
</script>
```

The `<ad-contact-form />` renders this:
![Contact form](https://thepracticaldev.s3.amazonaws.com/i/pvdbcbdjcdazuy7x504c.png)

and the `<ad-signup-form />` renders this:
![Signup form](https://thepracticaldev.s3.amazonaws.com/i/18udyzipsiz4gmmu2jdm.png)

---

## But why?

Maybe, at this point, you're asking yourself: "Ok, I got how it works... But why should I use the Atomic Design?"
I'm not the source of the truth, but I tell you why I like this approach.
Essentially for three reasons:

- Better organization
- Better design
- No boundaries

### Better organization

As you have seen so far, this methodology can help you to organize your files and your components in a understandable and predictable way: you know where to put your components and how to organize them. And, following this pattern, your development process will become quicker.

### Better design

With _Better design_ I don't mean a _better UI design_ but a **better architectural design**. If you start thinking of components as _atoms_, _molecules_ and _organisms_, during the bootstrap of your application, you are enforced to project your software following this pattern putting your focus on the reusing of your components.

### No boundaries

Being the Atomic Design a methodology, it is not strictly bound to particular technologies: you can apply it to different languages and frameworks: PHP, CSS, Laravel, Symfony, React, Angular, Vue and so on.

---

### Follow me on

- 👾 [GitHub](https://github.com/Sanfra1407)
- 📷 [Instagram](https://www.instagram.com/Sanfra1407/)

_If you liked the post, you might offer me a_ ☕️ _on [PayPal](https://www.paypal.me/giuseppesanfrancesco)._ 🙂

---