DEV Community

Cover image for Vue.js: How to maintain atomic design principles when using vendor components
timothyokooboh
timothyokooboh

Posted on

Vue.js: How to maintain atomic design principles when using vendor components

In this post, I explain how you can maintain atomic design principles when using UI component libraries such as Vuetify, Quasar, BootstrapVue, Font awesome, etc.

A brief overview of atomic design principles

Atomic design principles were introduced by Brad Frost. He recommends a way of coding up web user interfaces by first breaking them down into the smallest units called atoms. Then organize a group of atoms into molecules which can be reused in organisms. A group of organisms can be put together to form a template (without the real content). And when real content is placed inside templates, you get pages.

For example, let's say you want to create a form component.
By following the atomic design principles, you could have a component for inputs BaseInput.vue. A component for labels BaseLabel.vue. And a button component BaseButton.vue.

Each of these components would constitute your atoms. Then your form component could look like this.

AppForm.vue

The focus of this post is not to deeply explain atomic design principles. To learn more, check out chapter 2 of the book . Also, in this article, Alba Silvente, dives deeper into how you can apply atomic design principles in Vue.js.

Vendor component wrapper design pattern

The vendor component wrapper design pattern can be used to maintain atomic design principles when using component libraries like Vuetify.

It encourages wrapping vendor components (e.g Vuetify, BootstrapVue, Quasar, Font awesome) inside your own custom .vue components.

By the way, I learned about this design pattern from Ben Hong, a Vue.js core team member in this course.

Let's do a comparison of how our code could look like if we are to create a form component with Vuetify without applying this design pattern versus if we apply this design pattern.

Without applying vendor component wrapper design pattern

AppForm.vue

With vendor component wrapper design pattern

With vendor component wrapper design pattern, you will need to create your own .vue components that wrap the individual vuetify component.

BaseInput.vue

BaseButton.vue

For Vue 3.0, no need to add v-on="listeners"

By setting v-bind = "$attrs" and v-on="$listeners", you can pass all the props and attributes specified in the vendor component's API reference to your own .vue components, just as you would have done directly on the vendor components.

Note: If your template has a wrapping container element, e.g a div element, you will need to set inheritAttrs to false

BaseInput.vue

Then our form component will look like this:

AppForm.vue

Advantages of this pattern

(i) By wrapping vendor components inside your own .vue components, you can have finite control over vendor components API and still maintain atomic design principles just as you would have done if you were designing all your UI components with CSS, SCSS, or Tailwind CSS.

(ii) Your components can be flexible enough to switch between displaying vendor components or your own custom-made UI components.

In this example, I can switch between displaying a Vuetify button or display the button I designed myself with CSS

The isVuetify prop can be used to determine which button gets displayed.

You can even extend this methodology to switch between two vendor components e.g Font awesome icons or Material design icons.

That will not be possible if you do not wrap vendor components inside of your own .vue components

Top comments (2)

Collapse
 
portno profile image
Portno • Edited

Thanks for sharing!
I don't believe that it's so simple. How are you going to deal with API differences between libraries, eg vuetify has a disabled property and quasar has a disable property or text vs flat for buttons. With this design you are leaking all of these into the rest of your code and if you ever need to change the library you will have to refactor all of your codebase.
Moreover, are you getting any help from your IDE or editor, does VS code autocomplete for you when typing properties for your base components?
I think that if you wanted to abstract the API of the UI library you would have to skip v-bind="{...$attrs, ...$props}" and go all the way and introduce your own API with props and everything and handle all those differences in the implementations.
Finally, it's not just components. There's the layout system for each, quasar has special classes prefixed with q- and vuetify has probably something identical or special components like v-layout or v-row.

Collapse
 
momander profile image
Martin Omander

Great article!
Some of this feels like speculative development to me. "Let's spend extra engineering time now in case we want to switch a library later on" has always seemed like a weak argument to me. I think it's better to get to market sooner and only spend that extra engineering time of you actually do decide to switch libraries.
But I like how the technique described in this article can be used to reduce cognitive overhead for developers. Instead of using a low-level component with lots of settings, I can use a well-tested high-level component with one or two settings.