DEV Community

Cover image for Create wrapper components for Vuetify components
Giannis Koutsaftakis
Giannis Koutsaftakis

Posted on

Create wrapper components for Vuetify components

Vuetify is one of the most popular high-level UI component frameworks for Vue.js. It's based on Google Material Design and provides a wide range of high-quality pre-made components so that you can start building functional, accessible, and aesthetically pleasing web apps right away.

Although the built-in styling of Vuetify is great, your project might require different styling or you just want to customize Vuetify components to your liking.

Vue wrapper components to the rescue!

Wrapper components

A wrapper component is a custom component that "wraps" (hence the name) a native element or another component in order to add some custom functionality, styles, or anything else really.

Wrapper components are a great way to keep our codebase DRY by encapsulating functionality and/or styling that would otherwise have to be repeated.

Some of the advantages of wrapper components include:

  • Coherence between different parts of the application.
  • Simplified development by avoiding copy/paste.
  • Reduced bundle size.
  • Decouple the application from third-party components so that it's easier to switch from one component implementation to another.


In this case, we are going to build a wrapper component for the Vuetify v-text-field component as an example, putting a label on top of the input and setting some default styles as well.

In the picture below:

  • On the left, there's the default Vuetify text field.
  • On the right, we see the custom text field that we are going to create using a wrapper component. Alt Text

Now that we know how our final result looks like, let's get to the code.

Our wrapper component

    <label>{{ label }}</label>
    <v-text-field v-bind="{ ...$attrs, ...commonAttrs }" v-on="$listeners">
      <template v-for="(_, scopedSlotName) in $scopedSlots" #[scopedSlotName]="slotData">
        <slot :name="scopedSlotName" v-bind="slotData" />
      <template v-for="(_, slotName) in $slots" #[slotName]>
        <slot :name="slotName" />

export default {
  inheritAttrs: false,
  props: {
    label: {
      type: String,
      default: ''
  computed: {
    commonAttrs() {
      return {
        label: '',
        persistentHint: true,
        outlined: true,
        dense: true,
        hideDetails: false,
        class: {
          'mt-1': this.$props.label
Enter fullscreen mode Exit fullscreen mode

Breakdown of the most important parts.

  • Disable attribute inheritance
inheritAttrs: false
Enter fullscreen mode Exit fullscreen mode

Setting inheritAttrs to false enables us to forward all attributes to v-text-field using $attrs.

  • Bind all parent-scope attribute bindings to the Vuetify component.
v-bind="{ ...$attrs, ...commonAttrs }"
Enter fullscreen mode Exit fullscreen mode

In this case, we are also merging $attrs with our own attributes.

  • Forward all event listeners on the component to the Vuetify component.
Enter fullscreen mode Exit fullscreen mode

All event listeners such as e.g @click, @input etc will propagate to the Vuetify component.

  • Pass down slots to the Vuetify component.
<template v-for="(_, scopedSlotName) in $scopedSlots" #[scopedSlotName]="slotData">
  <slot :name="scopedSlotName" v-bind="slotData" />
<template v-for="(_, slotName) in $slots" #[slotName]>
  <slot :name="slotName" />
Enter fullscreen mode Exit fullscreen mode

Vuetify components provide slots for customization, we want them passed on from the wrapper component to the Vuetify component as well.

That's all there is to it!
You can now use the "component wrapper" technique to extend Vuetify components and also to build apps that are modular and more organized overall.

You can view the code used, in this CodeSandbox example.

Top comments (5)

zsgomoridev profile image
Zsolt Gomori

Thanks, awesome write-up! Exactly what I was looking for.

cawa93 profile image
Alex Kozack

Where defined slotData?

guivern profile image
Guillermo Verón

Very good explanation! I use this technique a lot in my projects.

daudibrahim profile image

Your solution works, and your explanation helps to understand the underlying intricacies. Thanks for sharing very helpful indeed.

michaelgitart profile image

Thanks, it was very useful!