DEV Community

Cover image for Build your own Vue.js UI library based on Vuetify and publish it to NPM
Chris Y.
Chris Y.

Posted on

Build your own Vue.js UI library based on Vuetify and publish it to NPM

Customize and enhance Vuetify components

Vue.js is a simple yet powerful front-end JavaScript framework that is designed exquisitely. In my opinion, Vue.js is the first choice for the UI of middle to large-sized projects. Over the years, there are a wide range of UI component libraries for Vue.js, and the most popular one seems to have been Vuetify. Often based on our own business and project requirements, we need to customize an open-source UI library component, and this article shows how to build our custom UI components based on Vuetify.

Create a Vue + Vuetify project

First Let’s create a new Vue.js project with Vuetify. Following the Vuetify guide, it is very easy:

We first create a new project named custom-ui using Vue CLI:

vue create custom-ui
Enter fullscreen mode Exit fullscreen mode

then in the project root, run:

vue add vuetify
Enter fullscreen mode Exit fullscreen mode

And it’s all ready to go. After cleaning up App.vue and delete HelloWorld.vue, our project structure looks like this:

Image description

It currently just renders a blank page.

Now let’s use a Vuetify button component <v-btn> and see how it works:

Image description

Here we used:

  • a prop named color to specify a primary material design color (by default it is grey color),
  • a prop named rounded to make the button have rounded edges (by default it is not rounded),
  • an event listener for the click event,
  • an event listener for the mouseover event,
  • the text "Custom Button" for the button.

Once clicking the button or mousing over it, the event handler functions are triggered and the text is output in the console:

Image description

It works as expected.

Suppose now we need to use this button (with primary color, rounded edges, default text, and two mouse events) in multiple places in our project and later in other projects as well, instead of copying and pasting code, ideally it is better to encapsulate what we have done into a new component. Later we can publish our custom UI library onto npm, and whichever project needs to use them can just npm install the package.

Let’s go ahead and create this custom component first.

We will create a new directory named MyLib, and within it, we create an index.js file and a MyButton component:

Image description

Like many open-source libraries, the purpose of the index.js file is to import all the components and export them out in one big object.

We move the <v-btn> from App to MyButton, and import MyButton from MyLib, the project now looks like this:

Image description

The two event handler functions are still in the parent App component, and the events are bound on the child component MyButton, which is the common practice in a component-based framework. Within MyButton, we still have the "default" value for color, rounded, and button text. For a component, we want it to be able to accept props passed from the parent, and props can have the default values that we want, so let’s add three props for MyButton:

Image description

The props color and rounded are bound onto the <v-btn>’s color and rounded, and text is placed in a slot. Refresh the page, the button still looks the same as before with the default values.

Now we can pass different values for these three props in the parent App component as well:

Image description

Image description

The color, edges, and text of the button are all changed. For the text, you can also pass a slot to MyButton, and it will override the default one.

If you take a look at <v-btn>'s API document, it has many other props. Let’s try some on MyButton:

Image description

We add outlined onto our MyButton component according to <v-btn> API. If you refresh the page, the button actually does not become the outlined style. It is not working, why? Because our own component works like a wrapper component for <v-btn> — we can customize some styling, add some default values, etc., which is similar to a concept popular in React.js — Higher Order Component. What we can do now is to use a powerful feature in Vue.js —$attrs.

$attrs

The document briefs $attrs as below:

Image description

Basically what it means is, in a parent component such as App, when we bind some attributes onto a child component such as MyButton, if these attributes are not declared in the child component as props, then Vue.js will add those attributes onto the root element of the child component.
Let’s see it in action.

In App, we have already bound a new attribute outlined on MyButton, now in MyButton, we will add v-bind="$attrs" onto the <v-btn> in MyButton component:

Image description

Refresh the page, and it works:

Image description

What happened is, because of v-bind="$attrs", now any non-declared attributes passed from App to MyButton (such as outlined) are added onto the root element of MyButton, which is <v-btn>, and outlined is a prop defined in <v-btn>, so it works on it and the outlined button is rendered.

In a nutshell:

  • $attrs is an object on a VueComponent instance that collects all the non-declared attributes bound onto this component.

  • In the $attrs object, the key name is the attribute name, they value is the attribute value.

Correspondingly:

  • $props is an object on a VueComponent instance that collects all the declared properties bound onto this component.

Image description

Let’s output $attrs and $props in MyButton to see them more clearly:

Image description

Both $attrs and $props are important properties on the VueComponent instance. Also, you can iterate $attrs or extract certain attributes out, depending on your needs. If you want to apply v-bind="$attrs" onto other elements in the <template> instead of the root one, you can set the inheritAttrs option to false in the component’s configs object. Please refer to the document about inheritAttrs.

Image description

So far we can apply all <v-btn>'s props on our MyButton, which is nice thanks to $attrs.

Let’s take a look at the two event listeners now. If you click on or mouse over the button, the two event handler functions currently are not triggered.
Similarly, there is another mighty VueComponent instance property called $listeners that can help.

$listeners

According to the document:

Image description

What it means is, in the parent component App, all the events (DOM native or custom) bound onto the child component MyButton will be collected into
the child component’s $listeners object. Let’s output it in the console to take a look:

Image description

Image description

The $listeners object contains the two events click and mouseover that the parent App bound onto it.
Now we can add a new line of code v-on="$listeners" on <v-btn> in MyButton:

Image description

Refresh the page and click on the button again:

Image description

The event callback functions are working now, awesome.

In a nutshell:

  • $listeners is an object on a VueComponent instance that collects all the events + event handler functions bound onto this component.

  • Like $attrs, a component can pass its $listeners to a descendant component.

In MyButton, by adding v-on="$listeners" on its child component <v-btn>, it passes its $listeners to <v-btn>,
and when the event (the key filed in the $listeners object) is triggered, the corresponding callback function defined in the parent will be executed.

Except for v-on="$listeners", you can also execute an event callback function directly in the child component using this.$listeners.eventname(), or you can destructure the $listeners object and even add new key:value pair into it to suit your specific needs.

Publish to NPM

Now our MyButton component is ready to go. We can import it into other places in the project, or we can enter the MyLib directory, run npm init to generate a package.json file, add the necessary metadata for our custom MyLib UI library, and run npm publish to push it onto npm. Other projects just need to install the MyLib package and can use MyButton and other custom components.

For more articles about $attrs and $listeners, please check out:

Vue.js Components Communication Patterns (without Vuex) - Part 4

Vue.js Components Communication Patterns (without Vuex) - Part 5

Discussion (0)