How to structure a Vue.js app using Atomic Design and TailwindCSS

In this article I will explain what the Atomic Design methodology is about and how we can apply it to our Vue.js project.

To give style to the example I created in this codesandbox, I have used TailwindCSS with its default configuration.

Img

# What is Atomic Design

All the theory we are going to see below is in depth in the book Atomic Design by Brad Frost

As Brad Frost says ‘Atomic design is like mental model to help us think of our user interfaces as both a cohesive whole and a collection of parts at the same time’.

Atomic design is a methodology for creating design systems chemistry-based and there are five distinct levels in atomic design: Atoms, Molecules, Organisms, Templates and Pages. Let’s see them in depth.

# Atoms

It is the smallest unit that composes our application, it is not useful by itself but it allows us to have more control over the application elements.

Imagine that you want to create a basic layout, in it there will be a header, a body and a footer. But, in addition, each of them is made up of smaller elements such as the links you can see in the header and also in the footer.

Well, we’re talking about the atoms being the HTML tags that will be reused throughout the application, as link, heading and svg, among others.

For this example we have defined five atoms that will be useful to us:

AtomButton.vue

As you can see, it's simply a button-style link that expects a property that will contain an object with the name and url of our link.

<a
    :href="link.url"
    class="block text-center py-1 px-4 bg-blue-100 text-blue-500 font-semibold rounded"
>{{ link.name }}</a>

AtomLink.vue

The atom that represents our link will be the same as the previous one but with the base style.

<a :href="link.url">{{ link.name }}</a>

AtomLogo.vue & AtomText.vue

For the logo we will add the SVG that represents it and for the text we will add the tag

.

<!-- AtomLogo -->
<svg width="48" height="48" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"></svg>

<!-- AtomText -->
<p class="pb-6">{{ text }}</p>

AtomTitle.vue

In this case, as the title can have more than one type of tag, h1, h2, ..., we have created a dynamic component to be able to represent the corresponding title introduced by the prop: tag.

<template>
  <component :is="tag" class="text-3xl font-serif pb-2">{{ content }}</component>
</template>

<script>
export default {
  name: "AtomTitle",
  props: ["tag", "content"]
};
</script>

Now that we have reviewed all the atoms needed for our template, let's get on with the molecules!

# Molecules

Molecules, as in nature, are groups of atoms linked together. In our application, molecules are the smallest components composed of one or more repeated atoms, always simple combinations built for reuse.

Having composed a molecule by several atoms already made, make us work with "do one thing and do it well" mentality. Both atoms and molecules encourage the creation of independent and reusable components.

Now, let's apply it to a case study. Imagine what we could do with the atoms we have created, what combinations without too much complexity we can create and reuse? Well, a list of links would be good for both the header and the footer and, also, we can create a card combining title and text atoms.

Let's take a look at the examples we have created:

MoleculeLinks.vue

In this case, we have created a list with the tags ul, li and, to represent the link, we have imported the AtomLink and used it as a component.

<template>
  <ul>
    <li v-for="(link, index) in links" :key="link.name + index">
      <AtomLink :link="link"/>
    </li>
  </ul>
</template>

<script>
import AtomLink from "@/components/AtomLink";

export default {
  name: "MoleculeLinks",
  props: ["links"],
  components: {
    AtomLink
  }
};
</script>

MoleculeCard.vue

In this molecule, to form the card, we have used the AtomTitle and AtomText, both added to the div that gives the style to the card.

<template>
   <div
     class="p-6 border border-blue-200 hover:shadow-lg rounded-sm bg-blue-100 text-blue-800 transition ease-in-out duration-500"
   >
     <AtomTitle tag="h2" :content="card.title"/>
     <AtomText :text="card.text"/>
   </div>
</template>

<script>
import AtomTitle from "@/components/AtomTitle";
import AtomText from "@/components/AtomText";

export default {
   name: "MoleculeCard",
   props: ["card"],
   components: {
     AtomTitle,
     AtomText
   }
};
</script>

MoleculeBanner.vue

For the Banner that will appear under the header we have needed both AtomTitle and AtomText, and also the AtomButton.

<template>
   <div class="px-4 pt-40 pb-12 md:py-32 bg-blue-900 text-gray-100">
     <header class="max-w-sm md:max-w-xl mx-auto">
       <AtomTitle tag="h1" content="Atomic Design in Vue.js"/>
       <AtomText
         text="Etiam in arcu lectus. Nulla facilisi. Nunc viverra vehicula nunc eu tristique. Sed gravida libero sem, sed ornare arcu venenatis at."
       />
       <AtomButton class="md:max-w-xs" :link="{ name: 'Button', url: '#' }"/>
     </header>
   </div>
</template>
 
<script>
import AtomTitle from "@/components/AtomTitle";
import AtomText from "@/components/AtomText";
import AtomButton from "@/components/AtomButton";
 
export default {
   name: "MoleculeBanner",
   components: {
     AtomTitle,
     AtomText,
     AtomButton
   }
};
</script>

# Organisms

Organisms are groups of molecules joined together to form a relatively complex, distinct section of an interface, as the header and the footer.

Now, we can see the final interface beginning to take shape. As in molecules with atoms, organisms can consist of similar and/or different molecule types.

As in the previous sections, we will see the possibilities we have to create the organisms. Let's have a look directly on each one:

OrganismHeader.vue

In order to create the Header organism we need the logo, a list of links, to navigate through the application, and a button for registration, for example. In that case we need the Logo and Button atoms and the Links molecule.

 <template>
     <header class="absolute inset-x-0 text-gray-100 py-4 flex flex-wrap items-center">
       <div class="w-full md:w-3/4 px-4 pb-4 md:pb-0 flex items-center justify-between">
         <AtomLogo/>
         <MoleculeLinks class="flex" :links="links"/>
       </div>
       <div class="w-full md:w-1/4 px-4">
         <AtomButton :link="{ name: 'Button', url: '#' }"/>
       </div>
     </header>
 </template>

 <script>
 import AtomLogo from "@/components/AtomLogo";
 import AtomButton from "@/components/AtomButton";
 import MoleculeLinks from "@/components/MoleculeLinks";

 export default {
     name: “OrganismHeader",
     props: ["links"],
     components: {
       AtomLogo,
       AtomButton,
       MoleculeLinks
     }
 };
 </script>

OrganismGrid.vue

To represent several cards on the same grid we create the OrganismGrid, which will contain MoleculeCard inside a v-for and that will be shape it with flex. Let’s check the code:

<template>
   <div class="flex flex-wrap items-stretch md:-mr-4">
     <div class="w-full md:w-1/3 md:pr-4 pb-4" v-for="card in cards" :key="card.name">
       <MoleculeCard class="h-full" :card="card"/>
     </div>
   </div>
</template>

<script>
import MoleculeCard from "@/components/MoleculeCard";

export default {
   name: “OrganismGrid",
   props: ["cards"],
   components: {
     MoleculeCard
   }
};
</script>

OrganismFooter.vue

As the previous cases, we just need the molecule we want to use. But, in this case, applying a class to the molecule we are using we can represent it vertically, now MoleculeLinks is using flex-col (flex-direction: column).

<template>
   <footer class="flex flex-wrap text-white">
     <div class="w-full md:w-1/4 bg-gray-900 p-4 md:p-12">
       <MoleculeLinks class="flex flex-col" :links="columnOneLinks"/>
     </div>
     <div class="w-full md:w-3/4 bg-blue-900 p-4 md:p-12">
       <MoleculeLinks class="flex flex-col" :links="columnTwoLinks"/>
     </div>
   </footer>
</template>

<script>
import MoleculeLinks from "@/components/MoleculeLinks";

export default {
   name: “OrganismFooter",
   props: ["columnOneLinks", "columnTwoLinks"],
   components: {
     MoleculeLinks
   }
};
</script>

# Templates

Now, we left behind the chemistry-based theory to get into common web language. Templates consist mostly of groups of organisms and/or molecules to form the common structure of a page, what we used to call layout.

At this stage, we already have created every piece of our template, so let's add them together to see how it looks.

We are hardcoding the data in the Template component, but the section below explains that we need to do it in the page stage.

<template>
   <section class="flex flex-col min-h-screen">
     <OrganismHeader :links="links"/>
     <div class="flex-auto flex flex-wrap bg-white">
       <MoleculeBanner class="w-full md:w-3/4 text-center md:text-left"/>
       <div class="container mx-auto px-4 py-8 md:py-16">
         <OrganismGrid :cards="cards"/>
       </div>
     </div>
     <OrganismFooter :columnOneLinks="columnOneLinks" :columnTwoLinks="columnTwoLinks"/>
   </section>
</template>

<script>
import OrganismHeader from "@/components/OrganismHeader";
import OrganismFooter from "@/components/OrganismFooter";
import MoleculeBanner from "@/components/MoleculeBanner";
import OrganismGrid from "@/components/OrganismGrid";

export default {
   name: "TemplateLanding",
   components: {
     MoleculeBanner,
     OrganismHeader,
     OrganismFooter,
     OrganismGrid
   },
   data: () => {
     return {
       links: […],
       cards: […],
       columnOneLinks: […],
       columnTwoLinks: […]
     };
   }
};
</script>

# Pages

Pages are specific instances of templates, where our components are replaced with real data to give an accurate depiction of what a user will see.

This stage help us a lot testing the effectiveness of the design system we've created and check that everything in context is built as we expect. To better understand this part, imagine that you have a heading with three lines instead of one, in this stage you will decide how to change your atom to look as you want in this specific case.

# Conclusion

Now that we know the possibilities offered by structuring our design system with this methodology, we can be sure that the changes will not be a headache and that we will be able to build huge applications.

Don't miss out anything about Vue, your favourite framework.

Subscribe to receive all the articles we publish in a concise format, perfect for busy devs.

Related Articles

Creating UI components based on a Design System in Vue.js

Don't you know the benefits of using a Design System? Not sure how to structure your Vue.js components in an app? Read the article and find it out.

Alex Jover Morales

Alex Jover Morales

Jul 21, 2020

The most modern Pie Chart component using CSS Conic Gradient and Vue.js

Build a Pie Chart component using one of the modern CSS features Conic Gradient

Alex Jover Morales

Alex Jover Morales

Oct 21, 2019

The most modern Carousel component using CSS Scroll Snap and Vue.js

Build a Carousel by using one of the latest CSS features called scroll-snap and bundle it into a Vue.js component

Alex Jover Morales

Alex Jover Morales

Oct 6, 2019

Sponsors

VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.

Silver
StoryBlok
Learning Partner
Vue School