25 Vue Tips You Need to Know

Learning to be a better Vue developer isn’t always about the big concepts that take time and effort to master.

It’s also about the short tips and tricks that can make your life a whole lot easier — without a whole lot of work.

I’ve picked up a ton of useful tips over the years developing and writing about Vue. Some are clever, some I use almost every day, and some are more advanced — but they’re all useful.

All of these were first published in my weekly newsletter. Make sure to sign up if you want more great tips like these!

1. Restrict a prop to a list of types

Using the validator option in a prop definition you can restrict a prop to a specific set of values:

export default { name: 'Image', props: { src: { type: String, }, style: { type: String, validator: s => ['square', 'rounded'].includes(s) } }

This validator function takes in a prop, and returns either true or false — if the prop is valid or not.

I often use this when I need more options than a boolean will allow, but still want to restrict what can be set.

Button types or alert types (info, success, danger, warning) are some of the most common uses — at least in what I work on. Colours, too, are a really great use for this.

But there are many more!

2. Default Content and Extension Points

Slots in Vue can have default content, which allows you to make components that are much easier to use:

<button class="button" @click="$emit('click')"> <slot> <!-- Used if no slot is provided --> Click me </slot>

My favourite use for default slots though, is using them to create extension points.

Basically, you take any part of a component, wrap it in a slot, and now you can override that part of the component with whatever you want. By default it’ll still work the way it always has, but now you have more options:

<template> <button class="button" @click="$emit('click')"> <!-- Adding in the slot tag does nothing at first --> <!-- We can override this by providing content to the slot --> <slot> <div class="formatting"> {{ text }} </div> </slot> </button>

Now you can use this component in many different ways. The easy, default way, or your own, custom way:

<!-- Uses default functionality of the component -->
<ButtonWithExtensionPoint text="Formatted text" /> <!-- Use the extension point to create custom behaviour -->
<ButtonWithExtensionPoint> <div class="different-formatting"> Do something a little different here </div>

Here’s a Codesandbox you can dive into:


3. Use quotes to watch nested values

You may not have known this, but you can easily watch nested values directly, just by using quotes:

watch { '$route.query.id'() { // ... }

This is really useful for working with deeply nested objects!

4. Know when to use v-if (and when to avoid it)

Instead of using v-if, it’s sometimes more performant to use v-show instead:

<ComplicatedChart v-show="chartEnabled" />

When v-if is toggled on and off it will create and destroy the element completely. Instead, v-show will create the element and leave it there, hiding it by setting it’s style to display: none.

Doing this can be much more efficient if the component you’re toggling is expensive to render.

On the flip side, if you don’t need that expensive component immediately, use v-if so that it will skip rendering it and load the page just a bit faster.

5. Shorthand for single scoped slot (no template tag needed!)

Scoped slots are lots of fun, but in order to use them you have to use a lot of template tags, too.

Luckily, there’s a shorthand that let’s us get rid of it, but only if we’re using a single scoped slot.

Instead of writing this:

<DataTable> <template #header="tableAttributes"> <TableHeader v-bind="tableAttributes" /> </template>

We can write this:

<DataTable #header="tableAttributes"> <TableHeader v-bind="tableAttributes" />

Simple, straightforward, and marvelous.

(Ok, maybe not quite marvelous, but still pretty good)

All of these tips were first published in my weekly newsletter. Make sure to sign up if you want more great tips like these!

6. Conditionally Rendering Slots (and why you’d need to)

First I’ll show you how, then we’ll get into why you’d want to hide slots.

Every Vue component has a special $slots object with all of your slots in it. The default slot has the key default, and any named slots use their name as the key:

const $slots = { default: <default slot>, icon: <icon slot>, button: <button slot>,

But this $slots object only has the slots that are applied to the component, not every slot that is defined.

Take this component that defines several slots, including a couple named ones:

<!-- Slots.vue -->
<template> <div> <h2>Here are some slots</h2> <slot /> <slot name="second" /> <slot name="third" /> </div>

If we only apply one slot to the component, only that slot will show up in our $slots object:

<template> <Slots> <template #second> This will be applied to the second slot. </template> </Slots>

$slots = { second: <vnode> }

We can use this in our components to detect which slots have been applied to the component, for example, by hiding the wrapper element for the slot:

<template> <div> <h2>A wrapped slot</h2> <div v-if="$slots.default" class="styles"> <slot /> </div> </div>

Now the wrapper div that applies the styling will only be rendered if we actually fill that slot with something.

If we don’t use the v-if, we would end up with an empty and unnecessary div if we didn’t have a slot. Depending on what styling that div has, this could mess up our layout and make things look weird.

So why do we want to be able to conditionally render slots?

There are three main reasons to use a conditional slot:

  1. When using wrapper divs to add default styles
  2. The slot is empty
  3. If we’re combining default content with nested slots

For example, when we’re adding default styles, we’re adding a div around a slot:

<template> <div> <h2>This is a pretty great component, amirite?</h2> <div class="default-styling"> <slot > </div> <button @click="$emit('click')">Click me!</button> </div>

However, if no content is applied to that slot by the parent component, we’ll end up with an empty div rendered to the page:

<div> <h2>This is a pretty great component, amirite?</h2> <div class="default-styling"> <!-- No content in the slot, but this div is still rendered. Oops. --> </div> <button @click="$emit('click')">Click me!</button>

Adding that v-if on the wrapping div solves the problem though. No content applied to the slot? No problem:

<div> <h2>This is a pretty great component, amirite?</h2> <button @click="$emit('click')">Click me!</button>

Here’s a Codesandbox with a working demo if you want to take a look: https://codesandbox.io/s/reactive-slots-bth28?file=/src/components/HasSlot.vue

I wrote more tips on slots in this article: Tips to Supercharge Your Slots (Named, Scoped, and Dynamic)

7. How to watch a slot for changes

This tip comes from Austin Gil — check out his awesome blog post on this here.

Sometimes we need to know when the content inside of a slot has changed:

<!-- Too bad this event doesn't exist -->
<slot @change="update" />

Unfortunately, Vue has no built-in way for us to detect this.

However, my friend Austin figured out a very clean way of doing this using a mutation observer:

export default { mounted() { // Call `update` when something changes const observer = new MutationObserver(this.update); // Watch this component for changes observer.observe(this.$el, { childList: true, subtree: true }); }

You’ll also need to clean up the observer, but Austin covers that, and more, in his article.

8. Mixing local and global styles together

Normally, when working with styles we want them to be scoped to a single component:

<style scoped> .component { background: green; }

In a pinch though, you can also add a non-scoped style block to add in global styles if you need it:

<style> /* Applied globally */ .component p { margin-bottom: 16px; }
</style> <style scoped> /* Scoped to this specific component */ .component { background: green; }

Be careful though — global styles are dangerous and hard to track down. Sometimes, though, they’re the perfect escape hatch and are exactly what you need.

9. Overriding styles of a child component — the right way

Scoped CSS is fantastic for keeping things tidy, and not accidentally bleeding styles into other parts of your app.

But sometimes you need to override the styles of a child component, and break out of that scope.

Vue has a deep selector just for this:

<style scoped>
/* Override CSS of a child component while keeping styles scoped */
.my-component >>> .child-component { font-size: 24px;

Yes, a couple months ago I covered exactly why you shouldn’t do this, but overriding styles can be the best solution (we don’t believe in “best practices” here).

Note: If you’re using a CSS pre-processor like SCSS, you may need to use /deep/ instead.

10. Creating Magic with Context-Aware Components

Context-aware components are “magical” — they adapt to what’s going on around them automatically, handling edge cases, state sharing, and more.

There are 3 main types of context-aware components, but configuration is the one I find most interesting.

1. State Sharing

When you break up a large component into multiple smaller ones, they often still need to share state.

Instead of pushing that work on whoever’s consuming the components, you can make this happen “behind the scenes”.

You may break up a Dropdown component into Select and Option components to give you more flexibility. But to make it easier to use, the Select and Option components share the selected state with each other:

<!-- Used as a single component for simplicity -->
<Dropdown v-model="selected" :options="[]" /> <!-- Split up for more flexibility -->
<Select v-model="selected"> <Option value="mustard">Mustard</Option> <Option value="ketchup">Ketchup</Option> <div class="relish-wrapper"> <Option value="relish">Relish</Option> </div>

2. Configuration

Sometimes the behaviour of a component needs to be changed based on what’s going on in the rest of the application. This is often done to automagically handle edge cases that would otherwise be annoying to deal with.

A Popup or Tooltip should re-position itself so it doesn’t overflow out of the page. But if that component is inside of a modal, it should re-position itself so it doesn’t overflow out of the modal.

This can be done automagically if the Tooltip knows when it’s inside of a modal.

3. Styling

You already create context-aware CSS, applying different styles based on what’s happening in parent or sibling elements.

.statistic { color: black; font-size: 24px; font-weight: bold;
} /* Give some separation between stats that are right beside each other */
.statistic + .statistic { margin-left: 10px;

CSS variables let us push this even further, allowing us to set different values in different parts of a page.

Check out this thread on Twitter if you want to discuss this concept!

Exclusive tips and insights every week

Join 8135 other Vue devs and get exclusive tips and insights like these delivered straight to your inbox, every week.

You have great content in your emails. I seriously learn something from every one of them. — Titus Decali

Thanks for another beautiful tip 🙏 — Victor Onuoha

Loving these, and the spaced repetition — Mark Goldstein

Sign up here

11. How to make a variable created outside of Vue reactive (Vue 2 and 3)

If you get a variable from outside of Vue, it’s nice to be able to make it reactive.

That way you can use it in computed props, watchers, and everywhere else, and it works just like any other state in Vue.

If you’re using the options API, all you need is to put it in the data section of your component:

const externalVariable = getValue(); export default { data() { return { reactiveVariable: externalVariable, }; }

If you’re using the composition API with Vue 3, you can use ref or reactive directly:

import { ref } from 'vue'; // Can be done entirely outside of a Vue component
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable); // Access using .value

Using reactive instead:

import { reactive } from 'vue'; // Can be done entirely outside of a Vue component
const externalVariable = getValue();
// Reactive only works with objects and arrays
const anotherReactiveVariable = reactive(externalVariable); // Access directly

If you’re still on Vue 2 (as many of us are) you can use observable instead of reactive to achieve exactly the same result.

12. Destructuring in a v-for

Did you know that you can destructure in a v-for?

<li v-for="{ name, id } in users" :key="id"
> {{ name }}

It’s more widely known that you can grab the index out of the v-for by using a tuple like this:

<li v-for="(movie, index) in [ 'Lion King', 'Frozen', 'The Princess Bride'
]"> {{ index + 1 }} - {{ movie }}

When using an object you can also grab the key:

<li v-for="(value, key) in { name: 'Lion King', released: 2019, director: 'Jon Favreau',
}"> {{ key }}: {{ value }}

It’s also possible to combine these two methods, grabbing the key as well as the index of the property:

<li v-for="(value, key, index) in { name: 'Lion King', released: 2019, director: 'Jon Favreau',
}"> #{{ index + 1 }}. {{ key }}: {{ value }}

13. Looping Over a Range in Vue

The v-for directive allows us to loop over an Array, but it also let’s us loop over a range:

<template> <ul> <li v-for="n in 5">Item #{{ n }}</li> </ul>

This will render out:

  • Item #1
  • Item #2
  • Item #3
  • Item #4
  • Item #5

When we use v-for with a range, it will start at 1 and end on the number we specify.

14. Watch anything in your component

It took me a very long time to realize this, but anything in your component that is reactive can be watched:

export default { computed: { someComputedProperty() { // Update the computed prop }, }, watch: { someComputedProperty() { // Do something when the computed prop is updated } }

You can watch:

If you’re using the composition API, any value can be watched, as long as it’s a ref or reactive object.

15. Stealing Prop Types

Often I find that I’m copying prop types from a child component, just to use them in a parent component. But I’ve discovered that stealing those prop types is much better than just copying them.

For example, we have an Icon component being used in this component:

<template> <div> <h2>{{ heading }}</h2> <Icon :type="iconType" :size="iconSize" :colour="iconColour" /> </div>

To get this to work, we need to add the correct prop types, copying from the Icon component:

import Icon from './Icon';
export default { components: { Icon }, props: { iconType: { type: String, required: true, }, iconSize: { type: String, default: 'medium', validator: size => [ 'small', 'medium', 'large', 'x-large' ].includes(size), }, iconColour: { type: String, default: 'black', }, heading: { type: String, required: true, }, },

What a pain.

And when the prop types of the Icon component are updated, you can be sure that you’ll forget to come back to this component and update them. Over time bugs will be introduced as the prop types for this component start to drift away from the prop types in the Icon component.

So that’s why we’ll steal them instead:

import Icon from './Icon';
export default { components: { Icon }, props: { ...Icon.props, heading: { type: String, required: true, }, },

It doesn’t have to get any more complicated than that!

Except in our example, we have “icon” added to the beginning of each prop name. So we’ll have to do some extra work to get that to happen:

import Icon from './Icon'; const iconProps = {}; // Do some processing beforehand
Object.entries(Icon.props).forEach((key, val) => { iconProps[`icon${key.toUpperCase()}`] = val;
}); export default { components: { Icon }, props: { ...iconProps, heading: { type: String, required: true, }, },

Now, if the prop types in the Icon component are modified, our component will stay up-to-date.

But what if a prop type is added or removed from the Icon component? To cover those cases we can use v-bind and a computed prop to keep things dynamic.

All of these were first published in my weekly newsletter. Make sure to sign up if you want more great tips like these!

16. Detecting clicks outside of an element (or inside)

Once in awhile I need to detect whether a click happens inside or outside of a particular element el. This is the approach I typically use:

window.addEventListener('mousedown', e => { // Get the element that was clicked const clickedEl = e.target; // `el` is the element you're detecting clicks outside of if (el.contains(clickedEl)) { // Clicked inside of `el` } else { // Clicked outside of `el` }

17. Recursive slots

One time I decided to see if I could make a v-for component using only the template. Along the way I discovered how to use slots recursively, too.

This is what the component looks like:

<!-- VFor.vue -->
<template> <div> <!-- Render the first item --> {{ list[0] }} <!-- If we have more items, continue! But leave off the item we just rendered --> <v-for v-if="list.length > 1" :list="list.slice(1)" /> </div>

If you wanted to do this with scoped slots — and why wouldn’t you?! — it just takes a few tweaks:

<template> <div> <!-- Pass the item into the slot to be rendered --> <slot v-bind:item="list[0]"> <!-- Default --> {{ list[0] }} </slot> <v-for v-if="list.length > 1" :list="list.slice(1)" > <!-- Recursively pass down scoped slot --> <template v-slot="{ item }"> <slot v-bind:item="item" /> </template> </v-for> </div>

Here is how this component is used:

<template> <div> <!-- Regular list --> <v-for :list="list" /> <!-- List with bolded items --> <v-for :list="list"> <template v-slot="{ item }"> <strong>{{ item }}</strong> </template> </v-for> </div>

For a more detailed explanation of this example and nested slots, check out my blog post on it: How to Use Nested Slots in Vue (including scoped slots)

18. Component Metadata

Not every bit of info you add to a component is state. Sometimes you need to add some metadata that gives other components more information.

For example, if you’re building a bunch of different widgets for an analytics dashboard like Google Analytics:

Screenshot of Google Analytics dashboard

If you want the layout to know how many columns each widget should take up, you can add that directly on the component as metadata:

export default { name: 'LiveUsersWidget', // 👇 Just add it as an extra property columns: 3, props: { // ... }, data() { return { //... }; },

You’ll find this metadata as a property on the component:

import LiveUsersWidget from './LiveUsersWidget.vue';
const { columns } = LiveUsersWidget;

You can also access the metadata from within the component through the special $options property:

export default { name: 'LiveUsersWidget', columns: 3, created() { // 👇 `$options` contains all the metadata for a component console.log(`Using ${this.$options.metadata} columns`); },

Just keep in mind that this metadata is the same for each instance of the component, and is not reactive.

Other uses for this include (but are not limited to):

  • Keeping version numbers for individual components
  • Custom flags for build tools to treat components differently
  • Adding custom features to components beyond computed props, data, watchers, etc.
  • and many more I can’t think of!

See a live example here: https://codesandbox.io/s/vue-metadata-bew9j?file=/src/App.vue

19. Multi-file single-file components

Here’s a little known feature of SFC.

You can import files just like you would with a regular HTML file:

<!-- A "single" file component -->
<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>

If you need to share styles, docs, or anything else, this can come in really handy. Also perfect for that super long component file that’s wearing out your finger from all the scrolling…

Here’s a working demo of it in action: https://codesandbox.io/s/interesting-rosalind-9wwmr?file=/src/components/HelloWorld.vue

20. Reusable Components Aren’t What You Think

Reusable components don’t have to be big or complex things.

I often make small and short components reusable.

Because I’m not re-writing this code all over the place, updating it becomes much easier, and I can make sure that every OverflowMenu looks and works exactly the same — because they are the same!

<!-- OverflowMenu.vue -->
<template> <Menu> <!-- Add a custom button to trigger our Menu --> <template #button v-slot="bind"> <!-- Use bind to pass click handlers, a11y attributes, etc. --> <Button v-bind="bind"> <!-- Use our own "..." icon and no text for this button --> <template #icon> <svg src="./ellipsis.svg" /> </template> </Button> </template> </Menu>

Here we’re taking a Menu component, but adding a ‘…’ (ellipsis) icon to the button that triggers it open.

It almost seems like it’s not worth making a reusable component out of this, because it’s only a few lines. Can’t we just add the icon every time we want to use a Menu like this?

But this OverflowMenu will be used dozens of times, and now if we want to update the icon or it’s behaviour, we can do it very easily. And using it is much simpler too!

<template> <OverflowMenu :menu-items="items" @click="handleMenuClick" />

If you want to go even deeper on building highly reusable components, I have a course that teaches you a completely different way of thinking about your components.

21. Calling a Method from Outside of the Component

You can call a method from outside of a component by giving it a ref:

<!-- Parent.vue -->
<template> <ChildComponent ref="child" />

// Somewhere in Parent.vue

Let me explain this one a bit more.

Every now and then the “best practices” don’t work for what you’re doing, and you need an escape hatch like this.

Typically, we communicate between components using props and events. Props are sent down into child components, and events are emitted back up to parent components.

<template> <ChildComponent :tell-me-what-to-do="someInstructions" @something-happened="hereIWillHelpYouWithThat" />

Once in a while though, you may end up in a situation where you need your parent to trigger a method in the child component. This is where only passing props down doesn’t work as well.

You could pass a boolean down and have the child component watch it:

<!-- Parent.vue -->
<template> <ChildComponent :trigger="shouldCallMethod" />

// Child.vue
export default { props: ['trigger'], watch: { shouldCallMethod(newVal) { if (newVal) { // Call the method when the trigger is set to `true` this.method(); } } }

This works fine, but only on the first call. If you needed to trigger this multiple times you’d have to clean up and reset the state. The logic would then look like this:

  1. The Parent component passes true to trigger prop
  2. Watch is triggered, and the Child component calls the method
  3. The Child component emits an event to tell the Parent component that the method has been triggered successfully
  4. The Parent component resets trigger back to false, so we can do this all over again


Instead, if we set a ref on the child component we can call that method directly:

<!-- Parent.vue -->
<template> <ChildComponent ref="child" />

// Somewhere in Parent.vue

Yes, we’re breaking the “props down, events up” rule, and we’re breaking encapsulation, but it’s so much cleaner and easier to understand that it’s worth it!

Sometimes the “best” solution ends up being the worst solution.

22. Watching Arrays and Objects

The trickiest part of using a watcher is that sometimes it doesn’t seem to trigger properly.

Usually, this is because you’re trying to watch an Array or an Object, but didn’t set deep to true:

export default { name: 'ColourChange', props: { colours: { type: Array, required: true, }, }, watch: { // Use the object syntax instead of just a method colours: { // This will let Vue know to look inside the array deep: true, // We have to move our method to a handler field handler() console.log('The list of colours has changed!'); } } }

Using the reactivity API from Vue 3 would look like this:

watch( colours, () => { console.log('The list of colours has changed!'); }, { deep: true, }

Here are the docs for Vue 3 and Vue 2 if you want to read more on this.

23. Deep Linking with Vue Router

You can store (a bit of) state in the URL, allowing you to jump right into a specific state on the page.

For example, you can load a page with a date range filter already selected:


This is great for the parts of your app where users may share lots of links, for an app that is server rendered, or communicating more information between two separate apps than a regular link normally provides.

You can store filters, search values, whether a modal is open or closed, or where in a list we’ve scrolled to — perfect for infinite pagination.

Grabbing the query using vue-router works like this (this will work on most Vue frameworks like Nuxt and Vuepress too):

const dateRange = this.$route.query.dateRange;

To change it we use the RouterLink component and update the query:

<RouterLink :to="{ query: { dateRange: newDateRange }

Here’s a demo of this in action:


24. Another Use for the Template Tag

The template tag can be used anywhere inside of your template to organize code better.

I like to use it to simplify v-if logic, and sometimes v-for, too.

In this example, we have several elements that all use the same v-if condition:

<template> <div class="card"> <img src="imgPath" /> <h3> {{ title }} </h3> <h4 v-if="expanded"> {{ subheading }} </h4> <div v-if="expanded" class="card-content" > <slot /> </div> <SocialShare v-if="expanded" /> </div>

It’s a little clunky, and not obvious at first that a bunch of these elements are being shown and hidden together. On a bigger, more complicated component, this could be an even worse situation!

But we can fix that.

We can use the template tag to group these elements, and lift the v-if on to the template tag itself:

<template> <div class="card"> <img src="imgPath" /> <h3> {{ title }} </h3> <template v-if="expanded"> <h4> {{ subheading }} </h4> <div class="card-content"> <slot /> </div> <SocialShare /> </template> </div>

Now we have something that’s much easier to read. And it’s much easier to understand what’s going on at a glance.

25. A better way to handle errors (and warnings)

You can provide a custom handler for errors and warnings in Vue:

// Vue 3
const app = createApp(App);
app.config.errorHandler = (err) => { alert(err);
}; // Vue 2
Vue.config.errorHandler = (err) => { alert(err);

Bug tracking services like Bugsnag and Rollbar hook into these handlers to log errors, but you can also use them to handle errors more gracefully for a better UX.

For example, instead of the application just crashing if an error is unhandled, you can show a full page error screen and get the user to refresh or try something else.

In Vue 3 the error handler only works on template and watcher errors, but the Vue 2 error handler will catch almost everything. The warning handler in both versions only works in development.

I created a demo showing how this works. It uses Vue 3, but Vue 2 works nearly the same:

Error Handler Demo

Exclusive tips and insights every week

Join 8135 other Vue devs and get exclusive tips and insights like these delivered straight to your inbox, every week.

You have great content in your emails. I seriously learn something from every one of them. — Titus Decali

Thanks for another beautiful tip 🙏 — Victor Onuoha

Loving these, and the spaced repetition — Mark Goldstein

Sign up here

July 21, 2021


Ampuh! Ini rahasia mengembangkan aplikasi secara instan, tinggal download dan kembangkan. Gabung sekarang juga! Premium Membership [PRIVATE] https://premium.codeorayo.com

Leave a Reply

Your email address will not be published. Required fields are marked *