Built for developers, by XinhND

v2.1.0

Ready

Vue.js Cheat Sheet

Complete reference guide for Vue.js with interactive examples and live playground links

Basic Syntax

4 items

Hello World
Directives
Computed Properties & Watchers
+1 more items

Components

4 items

Component Registration
Props
Events & Emits
+1 more items

Advanced Features

4 items

Composition API
Teleport
Suspense
+1 more items

State Management

2 items

Vuex (Vue 2/3)
Pinia (Vue 3)

Vue Router

3 items

Router Setup
Navigation
Route Guards

Click on any section to jump directly to it

Basic Syntax

Hello World

Basic Vue component structure

Vue.js
<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

Vue.js
<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

Vue.js
<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

Vue.js
<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

Vue.js
// 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

Vue.js
// 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

Vue.js
// 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

Vue.js
// 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

Vue.js
<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

Vue.js
<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

Vue.js
<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

Vue.js
// 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

Vue.js
// 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)

Vue.js
// 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

Vue.js
// 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

Vue.js
<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

Vue.js
// 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