Vue.js: How to maintain atomic design principles when using vendor components
Published: 2021-04-21 19:50:06

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](https://bradfrost.com/). 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
{% gist https://gist.github.com/timothyokooboh/866335b71532762a6ebdcbfce4fd4d98 %}

The focus of this post is not to deeply explain atomic design principles. To learn more, check out [chapter 2 of the book ](https://atomicdesign.bradfrost.com/chapter-2/). Also, in this article, [Alba Silvente](https://vuedose.tips/how-to-structure-a-vue-js-app-using-atomic-design-and-tailwindcss), 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](https://twitter.com/bencodezen), a Vue.js core team member in [this course](https://frontendmasters.com/courses/production-vue/).

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`
{% gist https://gist.github.com/timothyokooboh/44931a7f4ceb98092552d86429fd0634 %}   

#### 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
{% gist https://gist.github.com/timothyokooboh/38ba01ca25eee80b111f3d2ad0b00ced %}

#### BaseButton.vue
{% gist https://gist.github.com/timothyokooboh/bc56917241a4794fa9bd0adc6db2dc99 %}

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
{% gist https://gist.github.com/timothyokooboh/c764fbbcc8b12df0395c8b474db07212 %}

Then our form component will look like this:
#### AppForm.vue
{% gist https://gist.github.com/timothyokooboh/79b03dc1484ea3095ec38ba93364ea1e %}

#### 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

{% gist https://gist.github.com/timothyokooboh/82802e65eac9fb074efe71a63dd24608 %}

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*