Vue.js Cheat Sheet
Complete reference guide for Vue.js with interactive examples and live playground links
Click on any section to jump directly to it
Basic Syntax
Hello World
Basic Vue component structure
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue!'
}
}
}
</script>
<!-- Vue 3 Composition API -->
<script setup>
import { ref } from 'vue'
const message = ref('Hello, Vue 3!')
</script>
Directives
Common Vue directives
<template>
<!-- Conditional Rendering -->
<div v-if="show">Shown if show is true</div>
<div v-else-if="maybe">Shown if maybe is true</div>
<div v-else>Shown otherwise</div>
<!-- Display/hide element -->
<div v-show="isVisible">Toggles visibility</div>
<!-- List Rendering -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- Event Handling -->
<button v-on:click="handleClick">Click me</button>
<button @click="handleClick">Click me (shorthand)</button>
<!-- Two-way Binding -->
<input v-model="message" placeholder="Edit me">
<!-- Attribute Binding -->
<img v-bind:src="imageUrl" alt="Image">
<img :src="imageUrl" alt="Image (shorthand)">
<!-- Class Binding -->
<div :class="{ active: isActive, 'text-danger': hasError }">
Dynamic Classes
</div>
<!-- Style Binding -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
Dynamic Styles
</div>
</template>
Computed Properties & Watchers
Using computed properties and watchers
<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
count: 0
}
},
computed: {
// Computed property (getter only)
fullName() {
return this.firstName + ' ' + this.lastName
},
// Getter and setter
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
},
watch: {
// Simple watcher
count(newValue, oldValue) {
console.log(`Count changed from ${oldValue} to ${newValue}`)
},
// Deep watcher
user: {
handler(newValue) {
console.log('User object changed', newValue)
},
deep: true
},
// Immediate watcher (fires on creation)
searchQuery: {
handler(newValue) {
this.fetchResults(newValue)
},
immediate: true
}
}
}
</script>
<!-- Vue 3 Composition API -->
<script setup>
import { ref, computed, watch } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// Computed property
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
// Watch single ref
watch(firstName, (newValue, oldValue) => {
console.log(`First name changed from ${oldValue} to ${newValue}`)
})
// Watch multiple sources
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log('Name changed')
})
</script>
Lifecycle Hooks
Vue component lifecycle hooks
<script>
export default {
// Creation hooks
beforeCreate() {
// Instance has been initialized, before data observation
},
created() {
// Instance created, observed data, events, computed props established
// Can access 'this' but DOM not yet mounted
},
// Mounting hooks
beforeMount() {
// Called right before mounting
},
mounted() {
// DOM mounted, can access this.$el
},
// Update hooks
beforeUpdate() {
// Called when data changes, before DOM re-render
},
updated() {
// Called after re-render happens
},
// Destruction hooks
beforeUnmount() { // Vue 3 (beforeDestroy in Vue 2)
// Called right before instance is destroyed
},
unmounted() { // Vue 3 (destroyed in Vue 2)
// Component removed from DOM
// Good for cleanup
}
}
</script>
<!-- Vue 3 Composition API -->
<script setup>
import { onMounted, onUpdated, onUnmounted, onBeforeMount,
onBeforeUpdate, onBeforeUnmount } from 'vue'
onBeforeMount(() => {
console.log('Before mount')
})
onMounted(() => {
console.log('Component is mounted')
})
onBeforeUpdate(() => {
console.log('Before update')
})
onUpdated(() => {
console.log('Component updated')
})
onBeforeUnmount(() => {
console.log('Before unmount')
})
onUnmounted(() => {
console.log('Component unmounted')
})
</script>
Components
Component Registration
Registering and using Vue components
// Global component registration (main.js)
import { createApp } from 'vue'
import App from './App.vue'
import BaseButton from './components/BaseButton.vue'
const app = createApp(App)
app.component('BaseButton', BaseButton)
app.mount('#app')
// Local component registration
<script>
import BaseButton from './components/BaseButton.vue'
export default {
components: {
BaseButton
}
}
</script>
// Vue 3 with script setup
<script setup>
import BaseButton from './components/BaseButton.vue'
// No registration needed, imported components are automatically available
</script>
Props
Passing data to child components with props
// Child component (Button.vue)
<script>
export default {
props: {
// Basic type check
text: String,
// Multiple types
id: [String, Number],
// Required prop
title: {
type: String,
required: true
},
// With default value
color: {
type: String,
default: 'primary'
},
// Object/array default (factory function)
user: {
type: Object,
default: () => ({ name: 'Guest' })
},
// Custom validator
size: {
type: String,
validator(value) {
return ['small', 'medium', 'large'].includes(value)
}
}
}
}
</script>
// Vue 3 with script setup
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
text: String,
title: {
type: String,
required: true
},
color: {
type: String,
default: 'primary'
}
})
</script>
// Parent usage
<template>
<Button
text="Click me"
title="Submit"
color="success"
:user="{ name: 'John' }"
size="medium"
/>
</template>
Events & Emits
Parent-child component communication with events
// Child component (Button.vue)
<template>
<button @click="handleClick">{{ text }}</button>
</template>
<script>
export default {
props: {
text: String
},
methods: {
handleClick() {
// Emit event to parent
this.$emit('click-event')
// With data
this.$emit('change', { id: 1, value: 'new value' })
}
},
// Vue 3: Declare emitted events
emits: ['click-event', 'change']
}
</script>
// Vue 3 with script setup
<script setup>
import { defineEmits } from 'vue'
const emits = defineEmits(['click-event', 'change'])
function handleClick() {
emits('click-event')
emits('change', { id: 1, value: 'new value' })
}
</script>
// Parent usage
<template>
<Button
text="Click me"
@click-event="handleButtonClick"
@change="handleChange"
/>
</template>
<script>
export default {
methods: {
handleButtonClick() {
console.log('Button clicked')
},
handleChange(data) {
console.log('Changed:', data)
}
}
}
</script>
Slots
Content distribution with slots
// Child component (Card.vue)
<template>
<div class="card">
<!-- Default slot -->
<slot>Default content</slot>
<!-- Named slots -->
<header>
<slot name="header">Default header</slot>
</header>
<main>
<slot name="content">Default content</slot>
</main>
<footer>
<slot name="footer">Default footer</slot>
</footer>
<!-- Scoped slots (passing data to parent) -->
<slot name="user" :user="user" :isActive="isActive">
{{ user.name }}
</slot>
</div>
</template>
<script>
export default {
data() {
return {
user: { name: 'John', email: 'john@example.com' },
isActive: true
}
}
}
</script>
// Parent usage
<template>
<Card>
<!-- Default slot content -->
<p>Some content for default slot</p>
<!-- Named slots (Vue 2.6+/Vue 3) -->
<template #header>
<h2>Card Title</h2>
</template>
<template #content>
<p>This is the main content area</p>
</template>
<template #footer>
<button>Action</button>
</template>
<!-- Scoped slot (accessing data from child) -->
<template #user="{ user, isActive }">
<span :class="{ active: isActive }">{{ user.name }} ({{ user.email }})</span>
</template>
</Card>
</template>
Advanced Features
Composition API
Vue 3 Composition API usage
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<p>Double: {{ double }}</p>
<p>User: {{ user.name }}</p>
</div>
</template>
<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
export default {
setup() {
// Reactive state
const count = ref(0)
const user = reactive({ name: 'John', role: 'Admin' })
// Methods
function increment() {
count.value++
}
// Computed properties
const double = computed(() => count.value * 2)
// Watchers
watch(count, (newValue) => {
console.log('Count changed to', newValue)
})
// Lifecycle hooks
onMounted(() => {
console.log('Component mounted')
})
// Expose to template
return {
count,
user,
increment,
double
}
}
}
</script>
// Vue 3 script setup syntax (simplified)
<script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'
// State is automatically exposed to template
const count = ref(0)
const user = reactive({ name: 'John', role: 'Admin' })
// Methods
function increment() {
count.value++
}
// Computed properties
const double = computed(() => count.value * 2)
// Watchers
watch(count, (newValue) => {
console.log('Count changed to', newValue)
})
// Lifecycle hooks
onMounted(() => {
console.log('Component mounted')
})
</script>
Teleport
Teleport elements to specific DOM locations
<template>
<div>
<h1>My Vue App</h1>
<!-- Teleport content somewhere else in the DOM -->
<Teleport to="body">
<div class="modal" v-if="showModal">
<h2>Modal Title</h2>
<p>Modal content goes here</p>
<button @click="showModal = false">Close</button>
</div>
</Teleport>
<!-- Teleport to a specific CSS selector -->
<Teleport to="#modals">
<div class="notification" v-if="showNotification">
New message received!
</div>
</Teleport>
<button @click="showModal = true">Show Modal</button>
<button @click="showNotification = true">Show Notification</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
const showNotification = ref(false)
</script>
Suspense
Handling async components with Suspense
<template>
<Suspense>
<!-- Component with async setup -->
<template #default>
<AsyncComponent />
</template>
<!-- Loading state -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// Async component
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
// AsyncComponent.vue
<template>
<div>
<h1>Async Component</h1>
<p>{{ data.message }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
// Async setup
const data = await fetchData()
async function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ message: 'Data loaded!' })
}, 2000)
})
}
</script>
Custom Directives
Creating and using custom directives
// Global directive registration (main.js)
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// Simple directive
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// Directive with arguments & modifiers
app.directive('color', {
mounted(el, binding) {
// Access argument & value
const color = binding.arg || 'blue'
const value = binding.value || 'primary'
// Check for modifiers
if (binding.modifiers.bold) {
el.style.fontWeight = 'bold'
}
if (binding.modifiers.italic) {
el.style.fontStyle = 'italic'
}
// Set the color
el.style.color = `${color}-${value}`
}
})
app.mount('#app')
// Component-local directive
<script>
export default {
directives: {
highlight: {
mounted(el, binding) {
el.style.backgroundColor = binding.value || 'yellow'
}
}
}
}
</script>
// Usage
<template>
<!-- Auto-focus input -->
<input v-focus>
<!-- With argument & value -->
<p v-color:red="'dark'">Colored text</p>
<!-- With modifiers -->
<p v-color.bold.italic="'primary'">Styled text</p>
<!-- Local directive -->
<p v-highlight="'lightblue'">Highlighted text</p>
</template>
State Management
Vuex (Vue 2/3)
State management with Vuex
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0,
user: null,
loading: false
},
getters: {
doubleCount: (state) => state.count * 2,
isLoggedIn: (state) => !!state.user
},
mutations: {
INCREMENT(state, amount = 1) {
state.count += amount
},
SET_USER(state, user) {
state.user = user
},
SET_LOADING(state, status) {
state.loading = status
}
},
actions: {
// Simple action
incrementAsync({ commit }, amount) {
setTimeout(() => {
commit('INCREMENT', amount)
}, 1000)
},
// Action with async/await
async fetchUser({ commit }, userId) {
commit('SET_LOADING', true)
try {
const response = await fetch(`/api/users/${userId}`)
const user = await response.json()
commit('SET_USER', user)
} catch (error) {
console.error('Error fetching user:', error)
} finally {
commit('SET_LOADING', false)
}
}
},
// Namespaced modules
modules: {
cart: {
namespaced: true,
state: {
items: []
},
mutations: {
ADD_ITEM(state, item) {
state.items.push(item)
}
}
}
}
})
// Component usage
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// Map state properties
...mapState(['count', 'user', 'loading']),
...mapState('cart', ['items']),
// Map getters
...mapGetters(['doubleCount', 'isLoggedIn']),
// Direct access
tripleCount() {
return this.$store.state.count * 3
}
},
methods: {
// Map actions
...mapActions(['incrementAsync', 'fetchUser']),
...mapActions('cart', ['addToCart']),
// Direct commit
increment() {
this.$store.commit('INCREMENT')
},
// Direct dispatch
loadUser(id) {
this.$store.dispatch('fetchUser', id)
},
addItem(item) {
this.$store.commit('cart/ADD_ITEM', item)
}
}
}
</script>
// Composition API with Vuex
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// State & getters
const count = computed(() => store.state.count)
const doubleCount = computed(() => store.getters.doubleCount)
const cartItems = computed(() => store.state.cart.items)
// Actions & mutations
function increment() {
store.commit('INCREMENT')
}
function fetchUser(id) {
store.dispatch('fetchUser', id)
}
function addToCart(item) {
store.commit('cart/ADD_ITEM', item)
}
</script>
Pinia (Vue 3)
State management with Pinia (Vue 3)
// stores/counter.js
import { defineStore } from 'pinia'
// Define a store
export const useCounterStore = defineStore('counter', {
// State
state: () => ({
count: 0,
loading: false
}),
// Getters (like computed properties)
getters: {
doubleCount: (state) => state.count * 2,
// Getters that use other getters
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// Actions (like methods)
actions: {
increment() {
this.count++
},
async fetchCount() {
this.loading = true
try {
const response = await fetch('/api/count')
const data = await response.json()
this.count = data.count
} catch (error) {
console.error(error)
} finally {
this.loading = false
}
}
}
})
// Composition API setup
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Setup syntax (alternative style)
export const useUserStore = defineStore('user', () => {
// State
const user = ref(null)
const loading = ref(false)
// Getters
const isLoggedIn = computed(() => !!user.value)
const fullName = computed(() =>
user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
)
// Actions
async function login(credentials) {
loading.value = true
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
user.value = await response.json()
} finally {
loading.value = false
}
}
function logout() {
user.value = null
}
return {
user,
loading,
isLoggedIn,
fullName,
login,
logout
}
})
// Usage in components
<script setup>
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
const userStore = useUserStore()
// Destructure with storeToRefs to maintain reactivity
const { count, doubleCount } = storeToRefs(counterStore)
const { user, isLoggedIn, fullName } = storeToRefs(userStore)
// Actions can be destructured directly
const { increment, fetchCount } = counterStore
const { login, logout } = userStore
// Component methods
function handleLogin() {
login({ username: 'user', password: 'pass' })
}
</script>
Vue Router
Router Setup
Setting up Vue Router
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// Lazy-loaded component
component: () => import('@/views/About.vue')
},
{
// With params
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue')
},
{
// Nested routes
path: '/dashboard',
component: () => import('@/views/Dashboard.vue'),
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/views/dashboard/Overview.vue')
},
{
path: 'profile',
name: 'Profile',
component: () => import('@/views/dashboard/Profile.vue')
}
]
},
{
// Route with query params
path: '/search',
name: 'Search',
component: () => import('@/views/Search.vue')
},
{
// Named views
path: '/layout',
components: {
default: () => import('@/views/Default.vue'),
sidebar: () => import('@/components/Sidebar.vue'),
footer: () => import('@/components/Footer.vue')
}
},
{
// Catch-all 404 route
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
Navigation
Navigating with Vue Router
<template>
<!-- Router links -->
<nav>
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'About' }">About</router-link>
<router-link
:to="{ name: 'User', params: { id: userId } }">
User Profile
</router-link>
<router-link
:to="{ path: '/search', query: { q: searchQuery } }">
Search
</router-link>
</nav>
<!-- Render matched component -->
<router-view></router-view>
<!-- Named views -->
<router-view name="sidebar"></router-view>
<router-view name="footer"></router-view>
<!-- Navigation buttons -->
<button @click="goToHome">Home</button>
<button @click="goToUser">User</button>
<button @click="goBack">Back</button>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const userId = ref(123)
const searchQuery = ref('vue')
// Programmatic navigation
function goToHome() {
router.push('/')
}
function goToUser() {
router.push({
name: 'User',
params: { id: userId.value }
})
}
function goBack() {
router.back()
}
// Accessing route params & query
console.log(route.params.id)
console.log(route.query.q)
</script>
Route Guards
Route navigation guards
// Global guards
router.beforeEach((to, from, next) => {
// Check authentication
const isAuthenticated = localStorage.getItem('token')
if (to.meta.requiresAuth && !isAuthenticated) {
// Redirect to login
next({ name: 'Login', query: { redirect: to.fullPath } })
} else {
// Proceed
next()
}
})
router.afterEach((to, from) => {
// Update document title
document.title = to.meta.title || 'Vue App'
})
// Route with meta fields
const routes = [
{
path: '/admin',
name: 'Admin',
component: Admin,
meta: {
requiresAuth: true,
title: 'Admin Dashboard',
roles: ['admin']
}
}
]
// Per-route guard
{
path: '/profile',
name: 'Profile',
component: Profile,
beforeEnter: (to, from, next) => {
// Custom logic for this route
next()
}
}
// Component guards
export default {
name: 'UserProfile',
beforeRouteEnter(to, from, next) {
// Called before the component is created
// 'this' is not available here
fetchUserData(to.params.id).then(user => {
// Pass data to the component
next(vm => {
vm.setUser(user)
})
})
},
beforeRouteUpdate(to, from, next) {
// Called when route changes but component is reused
// e.g., /user/1 -> /user/2
// 'this' is available
this.loadUser(to.params.id)
next()
},
beforeRouteLeave(to, from, next) {
// Called when leaving the route
if (this.hasUnsavedChanges) {
const confirm = window.confirm('Discard changes?')
next(confirm)
} else {
next()
}
}
}
// Composition API route guards
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// Equivalent to beforeRouteUpdate
onBeforeRouteUpdate((to, from, next) => {
if (hasUnsavedChanges.value) {
const confirm = window.confirm('Discard changes?')
next(confirm)
} else {
next()
}
})
// Equivalent to beforeRouteLeave
onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) {
const confirm = window.confirm('Discard changes?')
next(confirm)
} else {
next()
}
})
</script>
Vue.js - Interactive Developer Reference
Hover over code blocks to copy or run in live playground