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

Alex Jover Morales

Alex Jover Morales

Jul 21, 2020
11 min read
Share on Twitter or LinkedIn

In the previous part of this guide you created a basic Nuxt project, full-static ready including TailwindCSS. In this one you'll take it from that to start designing some components, but before you get to that, I want to tell you how we structured VueDose Design system.

Organizing Vue.js components can be quite difficult if you're not very experienced with it. It took me a while to learn how to do it in a decent way, and that was through a lot of try and fail approaches.

Nowadays, frontend technologies, paradigms and ecosystem are changing so fast, that many best practices have not yet been established.

What's even worse is that often designers and frontend developers don't understand each other very well!

Have you felt the pain of any of these points?

I want to make this easier for you by sharing this article about how Aarón Garcia (UI designer) and I worked together to build VueDose 2.0. We created a Design System using TailwindCSS to easily implement the design system and structuring in Vue.js components.

Then, you'll apply these concepts to Naruto Dose, the project we're building to better understand the things you learn.

Note: I'll focus more on the implementation side, but expect in the future an article where Aarón Garcia describes in depth the creative process of designing VueDose Design System.

# Designing a Design System

When you decide to create a software product with a top quality design, you should consider creating a Design System.

Why? Because it gives you the design structure, patterns and elements to make sure your design is consistent, harmonious and balanced throughout the product and people working on it. It's the language all designers and frontend devs are going to use to talk about design.

We structured the VueDose design system by separating the elements into 4 different layers: style guide, components, modules and pages.

In fact, this is similar to how Atomic Design structures elements.

Give a read to the amazing article "How to structure a Vue.js app using Atomic Design and TailwindCSS" by Alba Silvente to learn more about this.

# 1. Style Guide

The style guide contains the basic rules, constraints and elements that your design will be built upon.

In VueDose style guide we define:

  • Screen breakpoints (mobile, tablet, desktop...)
  • Colors
  • Typography
  • Spacing
  • Grid and Container
  • Icons
  • Shapes
  • Borders
  • Shadows
  • ...

Aarón designed it in Figma, and looks like this:

https://a.storyblok.com/f/83078/1797x1359/fa3e63ec33/02-style-guide.png

The question still remains, why is TailwindCSS a good fit for creating a style guide and how does it work?

Easy: TailwindCSS is style guide oriented. In fact, by default it gives you a standard and well-thought out style guide.

Being a utility based CSS framework, it gives you the flexibility to define your own styling rules and uses them as classes.

You can better understand it with this comparison:

If you don't follow a style guide, and you have to set a margin top, you probably just play with some sizes, like 12px, 15px, 18px... until it fits the design, supposing the design is not following a style guide.

But when you have a style guide, your elements are calculated coherently without guessing. For example, we can apply an 8-pt spacing scale in the VueDose style guide:

https://a.storyblok.com/f/83078/1451x860/15e704d339/02-sizing.png

The spacing is always multiples of 8, giving you a consistent, calibrated and aligned design. The end result is that you'll have an optimized vertical rythm and better reading experience.

Same for typography, colors, borders, icons... They're all calculated to bring balance to the design.

# 2. Components

Once you have a style guide defined, you use it to build components.

A component is considered an atomic element. This means that a component, in general, doesn't include other components.

Some examples of components are buttons, links, form elements, alerts, logos...

This is how we defined some components in the design system:

https://a.storyblok.com/f/83078/1394x1497/4a07723232/02-components.png

# 3. Modules

We consider a module a combination of components to create a "bigger component". For example, for the article cards we use a button, an avatar, a title and tags, which you can see in this image:

https://a.storyblok.com/f/83078/1644x1358/9c4037b510/02-modules.png

Examples of modules are your website header and footer, a card or a form.

# 4. Pages

Finally, we use a combination of modules and components that follow our style guide to create the pages.

This is an example of the Home page in VueDose:

https://a.storyblok.com/f/83078/1847x1335/2bda3c8b80/02-pages.png

# Creating your own design system

You probably realize that building a design system makes design much easier, since you start building the smaller elements that you later use for bigger ones.

That not only improves the gap between a designer and a developer, since they now have a common language measured and calculated to work on. But, it also makes your design more consistent.

Is this all that complex to implement?

Not at all. Lucky for us, today you have good libraries to make this easily happen.

TailwindCSS covers very well the style guide part, while Vue.js, by itself, is component oriented. The combination of both: extremely powerful.

Let's see how to apply this kind of design to NarutoDose.

# Style Guide

To create a style guide you start by defining the colors, spacing and typography. You can create your fully customized style guide as we did in VueDose.

In this case, let's start from the tailwind default utilities set which is very well thought out and has everything you need:

First of all, open the file tailwind.config.js in your Nuxt project.

Let's add a main color that we want to use in our application, based on the color Teal 500. For that, let's extend the default colors:

const { theme } = require('tailwindcss/defaultConfig')

module.exports = {
  theme: {
    extend: {
      colors: {
        main: theme.colors.teal['500'], // #38b2ac
      },
    },
  },
  // ...
}

You could have written main: '#38b2ac' directly, but you can reuse the default TailwindCSS theme like that to build yours.

I'm adding the main color under the extend key. That allows you to add your customizations while keeping TailwindCSS defaults, and apply that to all theme customizations. In case you want to fully override TailwindCSS colors, you have to write your settings just below the theme key without extend.

You will use the theme key to override or extend any configuration from TailwindCSS.

# Creating Components

First of all, I want to tell you how I organize components in the world of Vue.js where everything is a component.

I do that by categorizing them depending on their responsibilities. In particular, I organize it by creating these folders under components:

  • layout: components that are related to the main application layout, such as the navbar, main header, footer, etc.
  • ui: visual components that have no application logic and are usually reusable across the app. The obvious example are any components from a UI library like Vuetify.
  • app: they have application logic and they usually use ui components and are generally a bit more complex. They can either be reusable or not. Some examples could be: a login form, a user table, a filtered product list... or anything that has some business logic
  • page-sections: page components can sometimes get too big. In those cases, I start splitting a page component into page section components.

Combined with the special layout and pages directories, this structure is complete.

# Layout

First of all, let's define a simple layout with a navbar. Create the file components/layout/AppHeader with the following markup:

<template>
  <header class="w-full bg-main py-2">
    <div class="container mx-auto px-4">
      <span class="text-2xl text-white font-semibold">NarutoDose</span>
    </div>
  </header>
</template>

Notice we use bg-main. That's the custom color we added before, and we can use that for background, text and borders. The cool part is that just by changing its value in the tailwind configuration, we change how the whole app looks 😉.

Then add it to layouts/default.vue:

<template>
  <div>
    <AppHeader />
    <main>
      <Nuxt />
    </main>
  </div>
</template>

In case you find it difficult to understand TailwindCSS and its classes, I suggest you take a look at these beautiful docs and play around a bit. It's pretty easy once you have some practice.

# Design the ArticleCard component

You already created components/ArticleCard.vue. That's a good example of a UI component, so let's follow the structure proposed above and move it into components/ui/ArticleCard.vue.

Then add some tailwind magic to make it look nice:

<template>
  <nuxt-link
    class="block text-gray-800 rounded-lg shadow-lg p-6"
    :to="`/${slug}`"
  >
    <header class="text-2xl font-bold">{{ title }}</header>
    <p class="mt-4">{{ description }}</p>
    <footer class="flex items-center mt-6">
      <img
        class="w-20 rounded-full border-4 border-main"
        :src="author.image"
        :alt="author.name"
      />
      <div class="ml-6">
        <p class="text-xl font-bold">{{ author.name }}</p>
        <p class="text-sm mt-1">{{ date }}</p>
      </div>
    </footer>
  </nuxt-link>
</template>

<script>
export default {
  props: {
    title: String,
    slug: String,
    description: String,
    author: Object,
    date: String,
  },
}
</script>

Notice I've also added a date prop.

Now let's add some styling to the home page, located under pages/index.vue:

<template>
  <div class="mt-4">
    <section>
      <div class="container mx-auto px-4">
        <h1 class="text-4xl font-bold">Articles</h1>
        <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 mt-4">
          <ArticleCard
            v-for="article in articles"
            :key="article.title"
            :title="article.title"
            :description="article.description"
            :author="article.author"
            :date="article.date.toLocaleDateString()"
          />
        </div>
      </div>
    </section>
  </div>
</template>

I'm using a grid to show the articles in 1, 2 or 3 columns, depending on the window size.

For now, translate the date simply by using date.toLocaleDateString().

In order to see several article cards displayed, let's repeat the article object that we have on asyncData several times, and add the date property as well:

export default {
  asyncData() {
    const articles = [
      {
        title: 'How to make your articles Vuetiful',
        description:
          'This article guides you through all the steps to make an article shine with your favourite framework, Vue',
        author: {
          name: 'Naruto Uzumaki',
          image:
            'https://a.storyblok.com/f/83078/500x500/cb27fcd15a/naruto-avatar.jpg',
        },
        date: new Date(),
      },
      { ... }, // more articles repeated
      { ... },
   ]

    return { articles }
  },
}

That's it. If you followed the steps correctly, you should see an interface like this:

https://a.storyblok.com/f/83078/1355x873/c4290c8129/02-final-result.png

# Layout key points

There are some layout techniques and best practices I've applied that might have gone unnoticed. Let me emphasize them:

  • Use of container. You can see in AppHeader and the home page that I've used the classes container mx-auto px-4. A container is used to limit the width of the content, and we use it inside the AppHeader or the sections (instead of outside) because that way we can have a background color in full width (like AppHeader) while having the inner content width adjusted. You can center it and add a padding by default in tailwind config. Then you only need the container class, without mx-auto px-4.
  • Set vertical separation with margin top. For consistency, always use margin-top to separate elements, and no margin-bottom or padding-top. Margin is for separation, and padding is for inner spacing.
  • Margins and widths are set outside the elements. If you check ArticleCard, it doesn't have any width or margins because it's not its job to know how it's going to be displayed in different places. ArticleCard has its inner dimensions (padding, and separation between inner elements), but how it is displayed is decided in pages/index.vue using the grid classes.

# Recap

This is the end of the article, but not the end of the guide!

In this article I focused on showing you the way Aarón and I decided to architecture VueDose 2.0 design, explaining all the techniques and methodologies we used to ensure a consistent design for the best viewing and reading experience.

In particular, you've seen how to create a design system and to structure it in different layers.

You've also learned how to apply these concepts to NarutoDose, the pet project you're creating to integrate all of the lessons, and how TailwindCSS fits perfectly for this way of styling web apps.

Did you like it so far? Great, because the best is yet to come.

In the next article, you'll bring the articles to life! We'll set up the content structure in Storyblok and use its API to take the data from there.

Are you ready for it? 😏

Pssst: find this lesson's code on Github.

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

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

Unsure about how to organize your Vue.js components? Learn in this tutorial how to do it using Atomic Design methodology. Examples and demos included!

Alba Silvente

Alba Silvente

Jun 16, 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