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)
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 adisable
property ortext
vsflat
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 likev-layout
orv-row
.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.