Vue.js cheat sheet

A scannable Vue.js reference: 30 short snippets across 12 topics, each linking back to the lesson it came from.

At a glance

TopicWhat it covers
Reactivity and template syntaxModifiers change a directive's behaviour: @submit.prevent stops the default action, @click.stop stops propagationlesson
Components, props and eventsA .vue file has three optional blocks: a script block with setup semantics, a template, and scoped styles. The scriptlesson
Routing and state with the Composition APIVue Router routes, guards and lazy loading, plus a Pinia store you can read from any component without prop drillinglesson
Setting up a Vue project with ViteOnly index.html is a real entry point: Vite rewrites the module script it contains and leaves the rest of the markup tolesson
Directives, lists and conditional renderingv-if versus v-show, v-for with stable keys, binding classes and styles, and the raw-HTML directive that causes most Vuelesson
Forms and user inputBind every input type with v-model, use modifiers, implement v-model on your own component, and keep validation andlesson
Composables and reusable logicWrite, name and share composables, decide when state should be per-component or shared, and test one without mountinglesson
Slots, dynamic and async componentsNamed and scoped slots, switching components at runtime with keep-alive, and code-splitting with defineAsyncComponentlesson
Pinia in depth and data fetching patternsSetup versus options stores, getters, actions and plugins, then the loading, error and optimistic-update patternslesson
Performance, rendering and SSR basicsWhere render cost actually comes from, the escape hatches that avoid deep reactivity, and what server rendering andlesson
Testing Vue componentsVitest and Vue Test Utils for components, router and store mocking, testing a composable in isolation, and a smalllesson
Production builds, environments and deploymentRead the build output, split code by route, handle environment values and a base path, then host the bundle with thelesson

Quick snippets

Reactivity and template syntax

Declaring reactive state

import { ref, reactive, computed } from 'vue'

const count = ref(0)            // primitive: read and write with .value
const user = reactive({ name: 'Ada', tags: [] })   // object: properties are tracked directly

const double = computed(() => count.value * 2)     // cached derived value

count.value += 1
user.name = 'Grace'             // no .value for reactive objects

watch versus computed

import { ref, computed, watch, watchEffect } from 'vue'

const query = ref('')
const results = ref([])

const trimmed = computed(() => query.value.trim())   // derive

watch(trimmed, async (value, oldValue) => {          // side effect
  if (!value) { results.value = []; return }
  results.value = await search(value)
})

watch versus computed

watch(query, async (value, _old, onCleanup) => {
  const controller = new AbortController()
  onCleanup(() => controller.abort())
  results.value = await search(value, { signal: controller.signal })
})

Full lesson: Reactivity and template syntax →

Components, props and events

Props down, events up

<Button
  label="Save"
  :busy="saving"
  @submit="onSave"
  @cancel="saving = false"
/>

<ChildForm v-model:email="email" />   <!-- v-model on a custom component -->

Lifecycle and composables

import { onMounted, onUnmounted, useTemplateRef } from 'vue'

const canvas = useTemplateRef('canvas')      // matches ref="canvas" in the template

onMounted(() => {
  draw(canvas.value)
  window.addEventListener('resize', onResize)
})

onUnmounted(() => window.removeEventListener('resize', onResize))

Lifecycle and composables

// useCounter.js — shared logic, no component wrapper
import { ref, onUnmounted } from 'vue'

export function useCounter(interval = 1000) {
  const n = ref(0)
  const id = setInterval(() => n.value++, interval)
  onUnmounted(() => clearInterval(id))     // works because it runs in a component scope
  return { n }
}

Full lesson: Components, props and events →

Routing and state with the Composition API

Routes and navigation

<RouterLink :to="{ name: 'guide', params: { slug: 'intro' } }">Intro</RouterLink>
<RouterView />   <!-- the matched component renders here -->

Shared state with Pinia

import { storeToRefs } from 'pinia'
import { useCart } from '@/stores/cart'

const cart = useCart()
cart.add(product)                          // actions are just methods

const { items, total } = storeToRefs(cart) // refs that stay connected
// reading cart.total in a template is reactive without storeToRefs

Full lesson: Routing and state with the Composition API →

Setting up a Vue project with Vite

Scaffold and run

# interactive: choose TypeScript, Router, Pinia, ESLint, Vitest
npm create vue@latest my-app

cd my-app
npm install
npm run dev        # Vite dev server with HMR
npm run build      # production bundle into dist/
npm run preview    # serve the built bundle locally to check it

Aliases, proxy and environment

# .env        committed, applies to every environment
VITE_APP_TITLE=Guides

# .env.local  gitignored, per-machine overrides
VITE_API_BASE=/api

# only VITE_ prefixed variables reach client code; read with import.meta.env

Full lesson: Setting up a Vue project with Vite →

Directives, lists and conditional rendering

v-if, v-else and v-show

<p v-if="status === 'loading'">Loading...</p>
<p v-else-if="status === 'error'">Something went wrong.</p>
<p v-else>{{ data.title }}</p>

<!-- v-show always renders the element and toggles display instead -->
<div v-show="open" class="panel">Hidden but present in the DOM</div>

<!-- v-if creates and destroys, so the child's local state is reset -->
<UserForm v-if="editing" :user="user" />

Lists and keys

import { ref, computed } from 'vue'

const items = ref([
  { id: 'a1', title: 'Install Vite', done: false },
  { id: 'a2', title: 'Write a component', done: true }
])

const visible = computed(() => items.value.filter(i => !i.done))

Lists and keys

<ul>
  <li v-for="(item, index) in visible" :key="item.id">
    {{ index + 1 }}. {{ item.title }}
  </li>
</ul>

<!-- an object iterates as (value, key) -->
<dl>
  <template v-for="(value, key) in settings" :key="key">
    <dt>{{ key }}</dt><dd>{{ value }}</dd>
  </template>
</dl>

Full lesson: Directives, lists and conditional rendering →

Forms and user input

Modifiers and a custom v-model

<input v-model.trim="form.name" />
<input v-model.number="form.qty" type="number" min="1" />
<input v-model.lazy="form.search" />         <!-- syncs on change, not per keystroke -->

<StarRating v-model="form.rating" />          <!-- default model -->
<PriceInput v-model:amount="form.amount" v-model:currency="form.currency" />

Modifiers and a custom v-model

// StarRating.vue — script block
const rating = defineModel({ type: Number, default: 0 })

function set(value) {
  rating.value = value      // writes back to the parent through the emitted update
}

Full lesson: Forms and user input →

Composables and reusable logic

Writing a composable

import { useLocalStorage } from '@/composables/useLocalStorage'

const { value: draft, reset } = useLocalStorage('draft', { title: '', body: '' })

Sharing state between composables

// composables/useCheckout.js — composes two others instead of inheriting
import { ref } from 'vue'
import { useCart } from './useCart'
import { useUser } from './useUser'

export function useCheckout() {
  const cart = useCart()
  const user = useUser()
  const step = ref(1)
  return { cart, user, step }
}

Testing and the Options API comparison

// the same feature in the Options API
export default {
  data() { return { n: 0 } },
  computed: { double() { return this.n * 2 } },
  mounted() { this.timer = setInterval(() => this.n++, 1000) },
  unmounted() { clearInterval(this.timer) }
}
// used twice in one component it collides: one data key, one mounted hook

Full lesson: Composables and reusable logic →

Slots, dynamic and async components

Dynamic components and keep-alive

import { shallowRef } from 'vue'
import ProfileTab from './ProfileTab.vue'
import BillingTab from './BillingTab.vue'

const tabs = { profile: ProfileTab, billing: BillingTab }
const currentTab = shallowRef(ProfileTab)   // component definitions are not reactive data

Dynamic components and keep-alive

<component :is="tabs[current]" :user="user" />

<!-- cache the heavy tab so scroll position and local state survive switching -->
<KeepAlive :max="3" :include="['ProfileTab', 'BillingTab']">
  <component :is="tabs[current]" :user="user" />
</KeepAlive>

Async components and Suspense

import { defineAsyncComponent } from 'vue'

const HeavyChart = defineAsyncComponent({
  loader: () => import('./HeavyChart.vue'),
  loadingComponent: Spinner,
  errorComponent: LoadFailed,
  delay: 200,        // avoids a spinner flash when the chunk is already cached
  timeout: 10000
})

Full lesson: Slots, dynamic and async components →

Pinia in depth and data fetching patterns

Two store shapes

// stores/guides.js — the same store in options style
export const useGuides = defineStore('guides', {
  state: () => ({ items: [], filter: 'all' }),
  getters: {
    visible: state => state.filter === 'all'
      ? state.items
      : state.items.filter(g => g.status === state.filter)
  },
  actions: {
    setFilter(value) { this.filter = value }
  }
})

Full lesson: Pinia in depth and data fetching patterns →

Performance, rendering and SSR basics

Where the cost comes from

import { ref, computed, shallowRef } from 'vue'

// every property of a reactive object is wrapped in a proxy: fine for a small form,
// expensive for a large table you replace wholesale
const rows = shallowRef([])             // only the .value assignment is tracked

const sorted = computed(() => [...rows.value].sort(byDate))

// a plain function in the template re-runs on every render
const cheap = computed(() => sorted.value.filter(r => r.visible))

Escape hatches and long lists

import { shallowRef, triggerRef, markRaw } from 'vue'

const chart = shallowRef(null)
chart.value = createChart()      // replaces the whole value, so one re-render
triggerRef(chart)                // force an update after an in-place mutation

const handlers = markRaw({ onSave, onDelete })   // opted out of reactivity for good

const thirdParty = shallowRef(new Editor())      // a class instance you must not proxy

Escape hatches and long lists

<!-- skip this subtree unless one of the listed values changed -->
<div v-memo="[user.id, user.updatedAt]">
  <ExpensiveRow :user="user" />
</div>

<!-- render only the window the user can see -->
<VirtualList :items="rows" :item-height="48" v-slot="{ item }">
  <Row :row="item" />
</VirtualList>

Full lesson: Performance, rendering and SSR basics →

Testing Vue components

Composables and end-to-end tests

// tests/helpers.js — give a composable a component scope
import { defineComponent, h } from 'vue'
import { mount } from '@vue/test-utils'

export function withSetup(composable) {
  let result
  const wrapper = mount(defineComponent({
    setup() { result = composable(); return () => h('div') }
  }))
  return { result, unmount: () => wrapper.unmount() }
}

Composables and end-to-end tests

// e2e/checkout.spec.js — Playwright against a preview build
import { test, expect } from '@playwright/test'

test('a visitor can add an item and see the total', async ({ page }) => {
  await page.goto('/')
  await page.getByRole('button', { name: 'Add to cart' }).click()
  await page.getByRole('link', { name: 'Cart' }).click()
  await expect(page.getByText('Total')).toContainText('20')
})

Full lesson: Testing Vue components →

Production builds, environments and deployment

What the build produces

npm run build
# dist/index.html
# dist/assets/index-a1b2c3.js        entry chunk
# dist/assets/CartView-d4e5f6.js     route chunk from () => import()
# dist/assets/index-7g8h9i.css

npm run preview              # verify the real build before it ships
npx vite-bundle-visualizer   # see what is inside each chunk

Environment values and the base path

// src/config.js — one place that reads environment values
export const config = {
  apiBase: import.meta.env.VITE_API_BASE ?? '/api',
  title: import.meta.env.VITE_APP_TITLE ?? 'Guides',
  isProd: import.meta.env.PROD
}

Environment values and the base path

// vite.config.js — serving from a subdirectory
export default defineConfig({
  base: '/app/',      // asset URLs in index.html become /app/assets/...
  build: { sourcemap: false }
})

Full lesson: Production builds, environments and deployment →

FAQ

Is this Vue.js cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 12 lessons of the Vue.js course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Vue.js course — it carries the worked explanations, the edge cases and the exercises behind every line here.

HTML CSS JavaScript TypeScript HTML DOM AJAX

Last refreshed 2026-09-27.