How to structure Storyblok spaces using Atomic Design

Contents

As a part of our live event Stories from the Blok, Alba Silvente presented how to setup Storyblok space according to the Atomic Design methodology.

Introduction

As a developer, it gets easier to structure code, and feel more comfortable making changes, as you gain experience with different projects.

When I start a coding project, I know that I am not the only one who will use it so my structure must fit all users.

This is where Atomic design comes into the picture. 💘

What is Storyblok?

Storyblok is a Headless CMS (Content Management System), the place where you will edit, add and delete your content. Instead of having a markdown file in your FrontEnd project, you will have a website where you will go and start writing content.

A Headless CMS is a back-end only content management system built as a content repository that makes content accessible via an API for display on any device. The term headless comes from the concept of chopping the “head” off the “body”, the “front“ off the “back“ .

What makes Storyblok stand apart from other Headless CMS is that it has a real-time Visual Editor. It provides developers with all the flexibility they need to build reliable and fast websites, while giving content creators with no coding skills the ability to edit content independently of the developer.

What is Atomic Design?

Atomic design is a methodology for creating design systems based on concepts in chemistry. There are five distinct levels to be aware of. They are as follows:

Atoms

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

An atom could be an input or a label, but to have a semantically correct field you would need the combination of both. They are not useful until you combine them at the next level.

Samples of Atoms

Molecules

As in nature, molecules are groups of atoms linked together. In an application, molecules are composed by one or more atoms, building a reusable component.

As mentioned above, you can group the input and the label to create the field.

Samples of Molecules

Organisms

Are groups of molecules joined together to form a distinct section of an interface.

If you take combinations of field-type molecules and put them together with a button-type atom you create a form, which can be thought of as an organism.

Examples of the organism

Templates

Now, chemistry-based theory is set aside to return to the common web language.

Templates can be thought of as the skeleton of your page, made up of groups of organisms and/or molecules to form the common structure of a page.

You can have, for example, a template for the articles on your website (blog-post) and another for the sign forms (register).

Examples of the templates

Pages

Pages are instances of the templates filled of real data.

The structure of the template and the page is the same, but the page will have the final content that you will show to the end user.

Examples of the pages

This is a brief overview of how this methodology works, but if you want to know more details I recommend you read the book Atomic Design by Brad Frost

‘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’ - Brad Frost

Why combine Storyblok with Atomic Design?

In order to answer this question, we need to consider how to structure a FrontEnd project and how content creators can use StoryBlok to do their jobs effectively.

This is where Storyblok really shines!

Storyblok editing capabilities
  • Control over the content

    You have more control over the content you and your team are creating, because you work with a methodology that makes you reuse the components you have created.

  • Easily scale

    It's easy to scale your applications because you have reusable components in place.

  • Align with all team members

    It's easy to align with designers and frontend teams because they already know about design systems and they will understand better how you are structuring your space.

  • Single source of truth

    You will generate a common language and a single source of truth by using the same methodology between teams, creating a design system.

  • Improve reusability

    As you are using reusable components, you will improve reusability, it will be easy to update your application or change the behavior/look'n'feel and you will have less components to fight with.

Now that we've outlined Storyblok's advantages, let's look at a real-world example! 🚀

Structuring our Storyblok space

Let's see how to create each level, which composes Atomic Design, in our Storyblok space and/or our FrontEnd project.

Atoms

This level is only part of our code.

In this case, the molecules are the components that fill the atoms through properties. So, the atoms don't need to be in our space, the molecules will do the work for them.

Note:

Although this methodology is applicable to any framework/library- for these examples I will use Vue.

Now imagine that we have created a Vue project; the next step is to go to the components folder and add a folder to store our atoms, called atoms.

One of our atoms could be Paragraph.vue, where we will create a <p> html tag that will receive a property with the content.

Paragraph.vue

<template>
  <p>
    {{ content }}
  </p>
</template>

<script>
export default {
  props: {
    content: {
      type: String,
      required: true,
    },
  },
}
</script>

But obviously, an atom can have more than one property and have different styles. For example, in this component Heading.vue, we can apply different font styles for each type of tag: h1, h2, h3, ..., having the control of how it will be shown, wherever it is called.

Heading.vue

<template>
  <component
    :is="tag"
    :class="classes[tag]"
    class="font-title font-bold text-gray-900 pb-4"
  >
    {{ content }}
  </component>
</template>

<script>
export default {
  props: {
    tag: {
      type: String,
      required: true,
    },
    content: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      classes: {
        h1: 'text-5xl md:text-7xl capitalize',
        h2: 'text-3xl md:text-5xl capitalize',
        h3: 'text-xl md:text-3xl capitalize',
        h4: 'text-lg md:text-xl capitalize',
        h5: 'text-base md:text-lg',
        h6: 'text-md md:text-base',
      },
    }
  },
}
</script>

If you want to see more examples of atoms, here is the repository created for this demo https://github.com/Dawntraoz/atomic-design-storyblok/tree/main/components/atoms.

Molecules

This level will affect your code, creating a component that defines how to call the atoms, and the structure of your space, creating a component that awaits the content of the atoms.

Let's look at it more closely with a case study. We'll try to create a molecule that imitates this look and feel:

Example design for molecule

In Storyblok

These are the steps we need to follow to create a molecule in our space:

1. Define a folder to group our molecules

To do that, go to the Components section, which you can find in the left menu, and create a group called molecules, by filling in this input and pressing the add button:

Folder for molecule components

2. Create a new component

Click the New button, in the top right corner, and give your molecule a name, for example, heading-section, and leave it as Nestable, per default.

3. Define its properties

Once we have the component created, we can define the fields that the content creator will fill in and which we will receive in our code.

For this example we need two text type fields, named title and subtitle, and a textarea field, named intro.

Schema of heading section

In the Code

To create a molecule in our Vue project, we'll follow a process similar to the one we have done with atoms. First, create a folder called molecules and inside create a component called HeadingSection.vue, which will contain 2 Paragraph atoms and a Heading atom.

This time the component will receive the blok property, which is part of the response returned by the Storyblok API, and will contain the title, subtitle and intro properties already defined.

We only have to pass the content that comes from the API to the properties of each atom, as you can see below:

HeadingSection.vue

<template>
  <header v-editable="blok" class="flex flex-col items-center text-center">
    <Paragraph
      v-if="blok.subtitle"
      class="font-title font-bold text-gray-900 pb-4 text-base md:text-lg tracking-widest text-blue-600 uppercase"
      :content="blok.subtitle"
    />
    <Heading v-if="blok.title" tag="h2" :content="blok.title" />
    <Paragraph
      class="text-gray-600 md:text-lg max-w-2xl md:pt-2"
      :content="blok.intro"
    />
  </header>
</template>

<script>
export default {
  props: {
    blok: {
      type: Object,
      required: true,
    },
  },
}
</script>

To see more molecules examples, check out this folder inside the repo: https://github.com/Dawntraoz/atomic-design-storyblok/tree/main/components/molecules

Organisms

As with molecules, this level affects our code and our space.

For this level, consider the idea of encompassing several molecules together, one of them being the heading-section described above:

Example design of the organism

In Storyblok

1. Define a folder to group our organisms

Being in the Components section, we create a group called organisms, by the form:

Folder for organism in Storyblok components.

2. Create an organism.

Click the New button again and give your organism a name according to the name of the section. Because we are representing a number of services let's call it services and, again, leave it as Nestable.

3. Define its fields.

Once we have the molecules needed for this organism, we can define the fields that will expect a molecule as the content.

For this example, we need two blocks fields called heading, with a heading-section molecule, and services, with one or more service-card molecules.

hint:

Blocks fields allow you to add another component created in the Storyblok space as content, instead of adding a text or a link for example. Read more about them in the Storyblok developer guides.

Schema of services for organism

To make it easier to understand let's see what the property heading contains. We can see, in the screenshot below, that the type of this field is blocks and it only allows heading-section components to be inserted. Actually, only 1, as specified by the maximum allowed.

Whitelisting of bloks.

In the Code

When it comes to creating an organism in our Vue project, it is even easier.

First, create a folder called organisms. Inside, create a component called Service.vue, which will contain 2 dynamic components. These dynamic components will be calling our molecules coming from the API response.

As you can see in the snippet below, this time the component will receive the blok property but, also, will fill in the blok property from the molecules.

The blok object will contain an array with only one item in heading, since we have set the maximum allowed to 1. But, in the case of services, we will have an array of 4 elements at most.

Each element of the arrays will contain the type of component (heading.component): heading-section.

Service.vue

<template>
  <section v-editable="blok" class="py-6 container">
    <component
      :is="heading.component"
      v-for="heading in blok.heading"
      :key="heading._uid"
      :blok="heading"
    />
    <div class="flex flex-wrap items-stretch md:-mr-6 py-8 md:py-16">
      <div
        v-for="service in blok.services"
        :key="service._uid"
        class="w-full md:w-1/2 lg:w-1/3 xl:w-1/4 md:pr-6 pb-6"
      >
        <component :is="service.component" :blok="service" />
      </div>
    </div>
  </section>
</template>

<script>
export default {
  props: {
    blok: {
      type: Object,
      required: true,
    },
  },
}
</script>

To see other organisms in place, check out this other folder: https://github.com/Dawntraoz/atomic-design-storyblok/tree/main/components/organisms.

Template

Again, this level will affect the code and our space.

But this time the component type on our space will no longer be Nestable (the default) but Content-type.

hint:

Content Types allow you to create templates for your content. Examples of common Content Types are blog-post, page. Read more about them in the Storyblok developer guides.

In Storyblok

We don't need a folder anymore, instead, we can create the template in the main folder All.

1. Create the template.

Go to the New button and give your template a name, according to the content it will receive. Since we are representing a common page in our application let's call it page-template. Then check Act as content type and exclude Nestable.

Creation of the page template

2. Define the body field.

Now that we have the molecules, which receive the content that will reach the atoms, and the organisms, which can already call the molecules they need, we need to create the template that calls those organisms.

To do this, we created a block type body field that will only allow us to call the components that are inside the organisms folder. In this way, we achieve a maximum of three levels of nesting depth in our panel of contents (page > organism > molecule).

Schema of the page template in Storyblok

In the Code

In this case, the template component could be created inside the components folder, **without creating a new folder.

Create a component called PageTemplate.vue, which will contain a dynamic component, that represents the organisms that the content creators have added to our content panel.

The blok object contains an array with the organisms inside of the body.

Each element of the arrays will contain the type of component (block.component): services (the organism).

PageTemplate.vue

<template>
  <div v-editable="blok">
    <header class="container text-center">
      <Heading v-if="blok.title" tag="h1" :content="blok.title" />
    </header>
    <component
      :is="blok.component"
      v-for="blok in blok.body"
      :key="blok._uid"
      :blok="blok"
    />
  </div>
</template>

<script>
export default {
  props: {
    blok: {
      type: Object,
      required: true,
    },
  },
}
</script>

Page

The last level will only be a part of our code (view: eg. index).

This is the view that will call the Storyblok API, with a fetch or axios call, and receives the data.

This data is then passed to the corresponding template, in this example page-template. The template will in turn pass the data to the organisms, then to the molecules, and, finally, end up with the atoms.

Passing the data from parent to child.

index.vue

<template>
  <section>
    <component
      :is="story.content.component"
      v-if="story.content.component"
      :key="story.content._uid"
      :blok="story.content"
    />
  </section>
</template>

<script>
export default {
  asyncData(context) {
    // Axios call
  },
  data() {
    return {
      story: { content: {} },
    }
  },
}
</script>

Conclusion

Using Atomic design, we have created an ecosystem that enables content creators to re-use the organisms we defined and fill their related molecules with content.

Every developer uses methodologies that suit their project needs. In this case, I have explained a methodology that will help you to build a Design System. If this makes sense for you too, give it a shot, and let me know how it went!