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
| Topic | What it covers | |
|---|---|---|
| Reactivity and template syntax | Modifiers change a directive's behaviour: @submit.prevent stops the default action, @click.stop stops propagation | lesson |
| Components, props and events | A .vue file has three optional blocks: a script block with setup semantics, a template, and scoped styles. The script | lesson |
| Routing and state with the Composition API | Vue Router routes, guards and lazy loading, plus a Pinia store you can read from any component without prop drilling | lesson |
| Setting up a Vue project with Vite | Only index.html is a real entry point: Vite rewrites the module script it contains and leaves the rest of the markup to | lesson |
| Directives, lists and conditional rendering | v-if versus v-show, v-for with stable keys, binding classes and styles, and the raw-HTML directive that causes most Vue | lesson |
| Forms and user input | Bind every input type with v-model, use modifiers, implement v-model on your own component, and keep validation and | lesson |
| Composables and reusable logic | Write, name and share composables, decide when state should be per-component or shared, and test one without mounting | lesson |
| Slots, dynamic and async components | Named and scoped slots, switching components at runtime with keep-alive, and code-splitting with defineAsyncComponent | lesson |
| Pinia in depth and data fetching patterns | Setup versus options stores, getters, actions and plugins, then the loading, error and optimistic-update patterns | lesson |
| Performance, rendering and SSR basics | Where render cost actually comes from, the escape hatches that avoid deep reactivity, and what server rendering and | lesson |
| Testing Vue components | Vitest and Vue Test Utils for components, router and store mocking, testing a composable in isolation, and a small | lesson |
| Production builds, environments and deployment | Read the build output, split code by route, handle environment values and a base path, then host the bundle with the | lesson |
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 storeToRefsFull 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.envFull 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 hookFull 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?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
HTML CSS JavaScript TypeScript HTML DOM AJAX
Last refreshed 2026-09-27.