Vue.js Cheat Sheet
Complete Vue 3 quick reference with Composition API, directives, lifecycle hooks, reactivity, and more. 40 entries across 7 categories with copy-paste code snippets.
Search or browse by topic
Read the syntax and examples
Copy code into your Vue project
Text Interpolation {{ }}
Vue 3Display reactive data in the template using double curly braces.
<template>
<p>{{ message }}</p>
<p>{{ count + 1 }}</p>
<p>{{ ok ? 'YES' : 'NO' }}</p>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello Vue!')
const count = ref(0)
const ok = ref(true)
</script>v-bind / :
Vue 3Dynamically bind attributes or props to an expression.
<!-- Full syntax -->
<img v-bind:src="imageUrl" />
<!-- Shorthand -->
<img :src="imageUrl" />
<a :href="url" :class="{ active: isActive }">Link</a>
<!-- Bind multiple attrs with an object -->
<div v-bind="{ id: someId, class: someClass }"></div>v-on / @
Vue 3Attach event listeners to elements. Supports modifiers.
<!-- Full syntax -->
<button v-on:click="handleClick">Click</button>
<!-- Shorthand -->
<button @click="count++">Add 1</button>
<!-- Modifiers -->
<form @submit.prevent="onSubmit">...</form>
<input @keyup.enter="submit" />
<button @click.once="doOnce">Once</button>v-if / v-else-if / v-else
Vue 3Conditionally render elements. Elements are destroyed and re-created.
<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Default</div>
<!-- Use <template> for multiple elements -->
<template v-if="show">
<h1>Title</h1>
<p>Content</p>
</template>v-for with :key
Vue 3Render a list of items. Always provide a unique :key.
<!-- Array -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<!-- With index -->
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
<!-- Object -->
<div v-for="(value, key) in myObject" :key="key">
{{ key }}: {{ value }}
</div>v-model
Vue 3Two-way data binding on form inputs, textareas, and components.
<input v-model="text" />
<textarea v-model="message"></textarea>
<!-- Modifiers -->
<input v-model.trim="name" />
<input v-model.number="age" type="number" />
<input v-model.lazy="query" />
<!-- On components -->
<MyInput v-model="searchText" />
<MyInput v-model:title="pageTitle" />v-show
Vue 3Toggle visibility via CSS display. Element stays in DOM.
<!-- Toggles display: none -->
<p v-show="isVisible">Now you see me</p>
<!-- v-show vs v-if:
v-show: always rendered, toggles CSS
v-if: actually destroys/creates elements
Use v-show for frequent toggling
Use v-if for rare changes -->v-slot / #
Vue 3Declare slot content when using child components.
<!-- Default slot -->
<MyComponent>
<p>Slot content here</p>
</MyComponent>
<!-- Named slots -->
<MyLayout>
<template #header>
<h1>Page Title</h1>
</template>
<template #default>
<p>Main content</p>
</template>
<template #footer>
<p>Footer</p>
</template>
</MyLayout>ref()
Vue 3Create a reactive reference for primitive or object values. Access via .value in script.
import { ref } from 'vue'
const count = ref(0)
const name = ref('Vue')
// Access in script: use .value
count.value++
console.log(name.value)
// In template: auto-unwrapped (no .value needed)
// <p>{{ count }}</p>reactive()
Vue 3Create a reactive object. No .value needed but cannot reassign the whole object.
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: { name: 'Alice' },
items: []
})
// Direct property access
state.count++
state.user.name = 'Bob'
state.items.push('new item')computed()
Vue 3Create a cached computed value that updates when dependencies change.
import { ref, computed } from 'vue'
const items = ref([1, 2, 3, 4, 5])
// Read-only computed
const evenItems = computed(() =>
items.value.filter(n => n % 2 === 0)
)
// Writable computed
const fullName = computed({
get: () => firstName.value + ' ' + lastName.value,
set: (val) => {
[firstName.value, lastName.value] = val.split(' ')
}
})watch() / watchEffect()
Vue 3Run side effects when reactive data changes.
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const name = ref('Vue')
// Watch specific source
watch(count, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// Watch multiple sources
watch([count, name], ([newCount, newName]) => {
console.log(newCount, newName)
})
// Auto-track dependencies
watchEffect(() => {
console.log('Count is:', count.value)
})onMounted() / onUnmounted() / onUpdated()
Vue 3Lifecycle hooks for setup, cleanup, and DOM updates.
import {
onMounted,
onUnmounted,
onUpdated,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from 'vue'
onMounted(() => {
console.log('Component mounted')
window.addEventListener('resize', onResize)
})
onUnmounted(() => {
window.removeEventListener('resize', onResize)
})
onUpdated(() => {
console.log('DOM updated')
})defineProps() / defineEmits()
Vue 3Declare component props and emitted events in script setup.
<script setup>
// Props with types
const props = defineProps({
title: String,
count: { type: Number, default: 0 },
items: { type: Array, required: true }
})
// TypeScript props
const props = defineProps<{
title: string
count?: number
}>()
// Emits
const emit = defineEmits(['update', 'delete'])
emit('update', newValue)
// TypeScript emits
const emit = defineEmits<{
(e: 'update', value: string): void
(e: 'delete', id: number): void
}>()
</script>provide() / inject()
Vue 3Pass data down the component tree without prop drilling.
// Parent component
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)
provide('appName', 'My App')
// Child / grandchild component
import { inject } from 'vue'
const theme = inject('theme')
const appName = inject('appName', 'Default App')toRef() / toRefs()
Vue 3Create refs from reactive object properties while keeping reactivity.
import { reactive, toRef, toRefs } from 'vue'
const state = reactive({
name: 'Vue',
count: 0
})
// Single property ref
const nameRef = toRef(state, 'name')
// Destructure while keeping reactivity
const { name, count } = toRefs(state)
// Useful in composables
function useFeature(props) {
const title = toRef(props, 'title')
// title.value stays in sync with props.title
}SFC Structure
Vue 3Single File Component with script setup, template, and scoped styles.
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const count = ref(0)
</script>
<template>
<div class="wrapper">
<h1>{{ count }}</h1>
<button @click="count++">Add</button>
<ChildComponent :value="count" />
</div>
</template>
<style scoped>
.wrapper {
padding: 1rem;
}
</style>Props with Types
Vue 3Define typed props with defaults and validation.
<script setup>
// Runtime declaration
const props = defineProps({
status: {
type: String,
required: true,
validator: (v) => ['active', 'inactive'].includes(v)
},
items: {
type: Array,
default: () => []
}
})
// TypeScript with defaults
interface Props {
title: string
count?: number
items?: string[]
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => []
})
</script>Events / Emits
Vue 3Emit custom events from child to parent components.
<!-- Child component -->
<script setup>
const emit = defineEmits(['update', 'close'])
function save() {
emit('update', { id: 1, name: 'Updated' })
}
</script>
<template>
<button @click="save">Save</button>
<button @click="emit('close')">Close</button>
</template>
<!-- Parent component -->
<template>
<ChildComponent
@update="handleUpdate"
@close="showModal = false"
/>
</template>Slots (default / named / scoped)
Vue 3Content distribution with default, named, and scoped slots.
<!-- MyCard.vue -->
<template>
<div class="card">
<header><slot name="header" /></header>
<main><slot /></main>
<footer>
<slot name="footer" :year="2024" />
</footer>
</div>
</template>
<!-- Usage -->
<MyCard>
<template #header>
<h2>Card Title</h2>
</template>
<p>Default slot content</p>
<template #footer="{ year }">
<small>Copyright {{ year }}</small>
</template>
</MyCard>Dynamic Components
Vue 3Switch between components dynamically using :is.
<script setup>
import { ref, shallowRef } from 'vue'
import TabHome from './TabHome.vue'
import TabProfile from './TabProfile.vue'
import TabSettings from './TabSettings.vue'
const tabs = { home: TabHome, profile: TabProfile, settings: TabSettings }
const current = shallowRef('home')
</script>
<template>
<button v-for="(_, name) in tabs" :key="name"
@click="current = name">
{{ name }}
</button>
<component :is="tabs[current]" />
</template>Async Components
Vue 3Lazy-load components for code splitting.
import { defineAsyncComponent } from 'vue'
const AsyncModal = defineAsyncComponent(() =>
import('./components/Modal.vue')
)
// With loading and error states
const AsyncDashboard = defineAsyncComponent({
loader: () => import('./Dashboard.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200,
timeout: 3000
})ref vs reactive
Vue 3When to use ref() versus reactive() for state management.
import { ref, reactive } from 'vue'
// ref: works with any value type
const count = ref(0) // primitive
const user = ref({ name: 'A' }) // object (deeply reactive)
count.value++
user.value.name = 'B'
// reactive: objects only, no .value
const state = reactive({ count: 0, name: 'A' })
state.count++
// Rules of thumb:
// - ref for primitives (string, number, boolean)
// - ref or reactive for objects (ref is more flexible)
// - reactive cannot be reassigned: state = newObj (breaks)
// - ref can: user.value = newObj (works fine)shallowRef / shallowReactive
Vue 3Only track top-level reactivity for performance optimization.
import { shallowRef, shallowReactive, triggerRef } from 'vue'
// Only .value assignment is reactive
const state = shallowRef({ nested: { count: 0 } })
state.value.nested.count++ // NOT reactive
state.value = { nested: { count: 1 } } // reactive
// Force trigger after shallow mutation
triggerRef(state)
// Only top-level props are reactive
const obj = shallowReactive({
count: 0, // reactive
nested: { a: 1 } // NOT reactive
})readonly()
Vue 3Create a read-only proxy of a reactive object.
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// copy.count++ // Warning! Cannot mutate
original.count++ // This works, copy reflects it
// Useful for providing data to child components
// that should not be mutated
provide('config', readonly(appConfig))nextTick()
Vue 3Wait for the DOM to update after a state change.
import { ref, nextTick } from 'vue'
const message = ref('Hello')
async function updateAndRead() {
message.value = 'Updated'
// DOM hasn't updated yet
console.log(document.getElementById('msg').textContent)
// Still shows 'Hello'
await nextTick()
// DOM is now updated
console.log(document.getElementById('msg').textContent)
// Shows 'Updated'
}Reactivity Caveats
Vue 3Common pitfalls with Vue reactivity system.
import { reactive, ref, toRefs } from 'vue'
const state = reactive({ count: 0 })
// DON'T: destructure reactive (loses reactivity)
let { count } = state // count is now a plain number
// DO: use toRefs to keep reactivity
const { count } = toRefs(state) // count is a ref
// DON'T: reassign reactive object
let state = reactive({ a: 1 })
state = reactive({ a: 2 }) // original watchers lost
// DO: mutate properties instead
state.a = 2
// DON'T: replace ref array content like this
const list = ref([1, 2, 3])
// list.value = list.value.filter(...) // OK
// list.value.length = 0 // OK
// Use .push(), .splice() for mutationsrouter-link / router-view
Vue 3Declarative navigation and route outlet.
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link :to="{ name: 'user', params: { id: 1 }}">
User 1
</router-link>
</nav>
<!-- Route component renders here -->
<router-view />
<!-- Named views -->
<router-view name="sidebar" />
</template>useRouter() / useRoute()
Vue 3Access router instance and current route in Composition API.
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// Navigate programmatically
router.push('/dashboard')
router.push({ name: 'user', params: { id: 42 }})
router.replace('/login')
router.go(-1) // back
// Access current route info
console.log(route.path) // '/users/42'
console.log(route.params.id) // '42'
console.log(route.query.q) // from ?q=search
console.log(route.hash) // from #section
</script>Route Params / Query
Vue 3Define and access dynamic route segments and query strings.
// router/index.js
const routes = [
// Dynamic param
{ path: '/user/:id', name: 'user', component: User },
// Optional param
{ path: '/posts/:year?', component: Posts },
// Multiple params
{ path: '/org/:orgId/team/:teamId', component: Team },
// Catch-all
{ path: '/:pathMatch(.*)*', component: NotFound }
]
// In component
const route = useRoute()
route.params.id // from /user/42
route.query.page // from ?page=2Navigation Guards
Vue 3Control navigation with before/after hooks.
// Global guard (router/index.js)
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return { name: 'login' }
}
})
// Per-route guard
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
if (!isAdmin()) return false
}
}
// In-component guard
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges) {
return confirm('Discard changes?')
}
})Lazy Loading
Vue 3Code-split routes for faster initial load.
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
},
// Group chunks with webpackChunkName
{
path: '/settings',
component: () => import(
/* webpackChunkName: "settings" */
'./views/Settings.vue'
)
}
]defineStore
Vue 3Create a Pinia store with state, getters, and actions.
// stores/counter.js
import { defineStore } from 'pinia'
// Option syntax
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Counter' }),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
// Setup syntax (Composition API style)
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, doubleCount, increment }
})State / Getters / Actions
Vue 3Use store state, computed getters, and methods in components.
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// Read state and getters
store.count
store.doubleCount
// Call actions
store.increment()
// Direct mutation
store.count++
// Patch multiple values
store.$patch({
count: 10,
name: 'Updated'
})
// Reset to initial state
store.$reset()
// Subscribe to changes
store.$subscribe((mutation, state) => {
localStorage.setItem('counter', JSON.stringify(state))
})
</script>storeToRefs
Vue 3Destructure store properties while keeping reactivity.
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const store = useUserStore()
// DON'T: loses reactivity
// const { name, email } = store
// DO: use storeToRefs for state/getters
const { name, email, fullName } = storeToRefs(store)
// Actions can be destructured directly
const { updateProfile, logout } = store
</script>
<template>
<p>{{ name }}</p>
<button @click="logout">Logout</button>
</template>Store Composition
Vue 3Use one store inside another for shared state.
// stores/auth.js
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const isLoggedIn = computed(() => !!user.value)
return { user, isLoggedIn }
})
// stores/cart.js
export const useCartStore = defineStore('cart', () => {
const auth = useAuthStore()
const items = ref([])
const canCheckout = computed(() =>
auth.isLoggedIn && items.value.length > 0
)
async function checkout() {
if (!auth.isLoggedIn) throw new Error('Login required')
// process checkout...
}
return { items, canCheckout, checkout }
})Teleport
Vue 3Render content in a different part of the DOM tree.
<!-- Renders inside <body>, not in parent component -->
<Teleport to="body">
<div class="modal-overlay">
<div class="modal">
<h2>Modal Title</h2>
<p>This is rendered at the body level.</p>
</div>
</div>
</Teleport>
<!-- Conditional teleport -->
<Teleport to="#modals" :disabled="inline">
<div class="toast">Notification</div>
</Teleport>Transition / TransitionGroup
Vue 3Apply enter/leave animations to elements and lists.
<!-- Single element -->
<Transition name="fade">
<p v-if="show">Hello</p>
</Transition>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>
<!-- List transitions -->
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</TransitionGroup>KeepAlive
Vue 3Cache component instances when switching between dynamic components.
<!-- Cache all dynamic components -->
<KeepAlive>
<component :is="currentView" />
</KeepAlive>
<!-- Cache specific components -->
<KeepAlive include="SearchPanel,ResultsList">
<component :is="activePanel" />
</KeepAlive>
<!-- Limit cached instances -->
<KeepAlive :max="5">
<component :is="currentTab" />
</KeepAlive>
<!-- Lifecycle hooks for cached components -->
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => { /* component brought back */ })
onDeactivated(() => { /* component cached */ })
</script>Suspense
Vue 3Display fallback content while waiting for async components.
<Suspense>
<!-- Main content (async component) -->
<template #default>
<AsyncDashboard />
</template>
<!-- Loading fallback -->
<template #fallback>
<div class="loading">Loading dashboard...</div>
</template>
</Suspense>
<!-- Async component with top-level await -->
<script setup>
const data = await fetch('/api/data').then(r => r.json())
</script>
<template>
<div>{{ data.title }}</div>
</template>Get More Developer Resources
Join our newsletter for cheat sheets, code snippets, and developer tools delivered weekly.
Get this + weekly PM tools and templates.