Vue Cheat Sheet
By Syncfusion®, Inc. · Last updated
A quick reference for the whole Vue course: templates and directives, reactivity, rendering, events and forms, components, the Options API, composables, directives and plugins, routing, Pinia, HTTP, built-in components, transitions, rendering modes, testing, performance, errors and deployment.
What you will learn
- Find the exact Vue syntax for a task in seconds
- Jump to the lesson that explains each topic in full
How to Use This Cheat Sheet
- This page is a quick reference for the whole course, in course order.
- Each block shows the common patterns as short code you can copy.
- Each block names the lesson that explains it in full.
- Code samples follow the TypeScript / JavaScript picker in the top bar.
A Real-World Example
- A developer is adding a booking form to Meera's site.
- She forgot the exact
defineModel()syntax and the.numbermodifier. - She finds both here in seconds, instead of reading two lessons again.
Start an App
Introduction to Vue
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
npm create vue@latest # new project
npm install # packages
npm run dev # dev server with HMR
npm run build # production files in dist/
Templates and Directives
Templates and Directives
<template>
<p>{{ price * plates }}</p> <!-- one expression -->
<img :src="photo" :alt="dish" /> <!-- v-bind shorthand -->
<button :disabled="soldOut">Order</button> <!-- false removes it -->
<input v-bind="attrs" /> <!-- many attributes -->
<p v-html="trustedHtml"></p> <!-- only trusted HTML -->
<p v-once>{{ firstSpecial }}</p> <!-- render once -->
</template>
| Directive part | Example |
|---|---|
| Name | v-on |
| Argument | :submit |
| Dynamic argument | :[eventName] |
| Modifier | .prevent |
| Value | ="save" |
Reactivity
Reactive State, Computed Properties and Watchers, Advanced Reactivity
<script setup lang="ts">
import { ref, reactive, computed, watch, watchEffect, nextTick, toRefs } from "vue";
const count = ref<number>(0); // count.value in script
const order = reactive({ plates: 1 }); // no .value, do not destructure
const { plates } = toRefs(order); // safe destructure
const double = computed(() => count.value * 2);
watch(count, (now, before) => console.log(before, "→", now), { immediate: true });
watch(() => order.plates, (p) => console.log("plates", p));
watchEffect(() => console.log("count is", count.value));
async function add(): Promise<void> {
count.value++;
await nextTick(); // DOM is updated now
}
</script>
| Need | Use |
|---|---|
| A value from state | computed |
| A job when state changes | watch / watchEffect |
| Big data replaced as a whole | shallowRef + triggerRef |
| Read-only view | readonly() |
| A debounced ref | customRef() |
Dynamic and List Rendering
Dynamic Rendering, List Rendering
<template>
<p :class="{ active: isOn, 'sold-out': soldOut }" :style="{ fontSize: size + 'px' }">Dosa</p>
<p v-if="hour < 11">Breakfast</p>
<p v-else-if="hour < 16">Lunch</p>
<p v-else>Dinner</p>
<ul v-show="open">
<li v-for="(dish, i) in shown" :key="dish.id">{{ i + 1 }}. {{ dish.name }}</li>
</ul>
<span v-for="n in 3" :key="n">{{ n }}</span> <!-- 1, 2, 3 -->
</template>
- Filter and sort in a
computed, neverv-ifwithv-foron one element. filterandmapreturn new arrays: assign them.
Events and Forms
Events and Forms
<template>
<button @click="add(2, $event)">Add 2</button>
<input @keyup.enter="save" @keyup.esc="clear" />
<div @click="open"><button @click.stop="like">Like</button></div>
<form @submit.prevent="submit">
<input v-model.trim="name" :aria-invalid="!!error" :aria-describedby="errorId" />
<input v-model.number="guests" type="number" />
<input type="checkbox" v-model="extras" value="chutney" />
<select v-model="time"><option>19:00</option></select>
<p :id="errorId">{{ error }}</p>
<button :disabled="!!error">Book</button>
</form>
</template>
Components
Components and Single-File Components, Props and Events, Slots, Attributes and Provide/Inject
<script setup lang="ts">
const props = defineProps<{ name: string; price?: number }>();
const emit = defineEmits<{ add: [name: string] }>();
const qty = defineModel<number>({ default: 1 });
</script>
<template>
<div class="card">
<slot name="header" />
<slot :dish="props.name">{{ props.name }}</slot>
<button @click="emit('add', props.name)">Add</button>
</div>
</template>
<style scoped>
.card :deep(.price) { color: crimson; }
</style>
<template>
<DishCard name="Idli" :price="50" v-model="plates" @add="cart.push($event)">
<template #header><h3>Breakfast</h3></template>
<template #default="{ dish }">⭐ {{ dish }}</template>
</DishCard>
</template>
- Props down, events up. Never change a prop.
- Runtime props in JavaScript:
defineProps({ name: { type: String, required: true } }).
Attributes, Provide/Inject and Lifecycle
Slots, Attributes and Provide/Inject, Lifecycle and Template Refs
defineOptions({ inheritAttrs: false }); // then v-bind="$attrs"
provide(cartKey, cart); // ancestor
const cart = inject(cartKey, ref([])); // any descendant
onMounted(() => { /* DOM ready: start timers, listeners */ });
onUnmounted(() => { /* clean everything up */ });
const box = useTemplateRef<HTMLInputElement>("box"); // ref="box"
defineExpose({ reset }); // what a parent ref sees
Options API
Options API
export default defineComponent({
props: { price: { type: Number, required: true } },
emits: ["add"],
data() { return { plates: 1 }; },
computed: { total(): number { return this.price * this.plates; } },
watch: { plates(now: number) { console.log(now); } },
methods: { addPlate() { this.plates++; } },
mounted() { console.log("on the page"); },
});
Reuse: Composables, Directives and Plugins
Composables, Directives and Plugins
// composables/useCounter.ts
export function useCounter(start: MaybeRefOrGetter<number> = 0) {
const count = ref(toValue(start));
const add = () => count.value++;
return { count, add }; // refs: safe to destructure
}
// a directive (in <script setup>, vFocus = v-focus)
const vFocus: Directive<HTMLElement> = { mounted: (el) => el.focus() };
// a plugin
export const kitchenPlugin = {
install(app: App, options: { shop: string }) {
app.provide("shop", options.shop);
app.directive("focus", vFocus);
},
};
TypeScript
TypeScript with Vue
const props = defineProps<{ dish: Dish; plates?: number }>();
const emit = defineEmits<{ add: [name: string] }>();
const qty = defineModel<number>({ default: 1 });
const chosen = ref<Dish | null>(null);
const box = useTemplateRef<HTMLInputElement>("box");
export const themeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
// <script setup lang="ts" generic="T extends { id: number }">
// npx vue-tsc --noEmit (Vite does not check types)
Routing
Vue Router Basics, Navigation Guards and Advanced Routing
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", component: HomePage },
{ path: "/dish/:id", name: "dish", component: DishPage, props: true },
{ path: "/admin", component: () => import("./views/Admin.vue"), meta: { requiresAuth: true } },
{ path: "/:pathMatch(.*)*", component: NotFound },
],
});
router.beforeEach((to) => {
if (to.meta.requiresAuth && !loggedIn.value) return { path: "/login", query: { redirect: to.fullPath } };
});
<template>
<RouterLink :to="{ name: 'dish', params: { id: 2 } }">Dish 2</RouterLink>
<RouterView />
</template>
useRoute()readsparamsandquery.useRouter()doespush,replaceandgo.
Pinia
State Management with Pinia
export const useCartStore = defineStore("cart", () => {
const items = ref<string[]>([]);
const count = computed(() => items.value.length);
function add(dish: string): void { items.value.push(dish); }
return { items, count, add };
});
const cart = useCartStore();
const { count } = storeToRefs(cart); // state and getters
const { add } = cart; // actions directly
HTTP
HTTP Requests, Authentication and Real-Time Data
const res = await fetch(`${import.meta.env.VITE_API_URL}/dishes`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Pongal" }),
signal: controller.signal, // AbortController
});
if (!res.ok) throw new Error(`Request failed (${res.status})`);
const dish = await res.json();
// auth: send the token on every request, refresh once on 401
api.interceptors.request.use((c) => (c.headers.set("Authorization", `Bearer ${auth.token}`), c));
// live data: close on unmount
const socket = new WebSocket("wss://api.example.com/live");
onUnmounted(() => socket.close());
Advanced Components and Transitions
Dynamic and Built-In Components, Transitions and Animations, Render Functions and Component Patterns
<template>
<KeepAlive><component :is="current" /></KeepAlive>
<Teleport to="body"><div v-if="open" class="modal" role="dialog">…</div></Teleport>
<Transition name="fade" mode="out-in"><p :key="text">{{ text }}</p></Transition>
<TransitionGroup name="list" tag="ul"><li v-for="o in orders" :key="o.id">{{ o.dish }}</li></TransitionGroup>
</template>
const Chart = defineAsyncComponent(() => import("./Chart.vue"));
const render = () => h("button", { onClick: add }, "Add");
Rendering Modes, Testing and Production
How Vue Renders, Server and Static Rendering, Nuxt: Full-Stack Vue, Testing Vue Apps, Performance, Errors, Security and Deployment, Capstone: Build Meera's Kitchen
const html = await renderToString(createSSRApp(App)); // server
createSSRApp(App).mount("#app"); // hydrate
it("adds a dish", async () => { // Vitest + VTU
const wrapper = mount(DishCard, { props: { name: "Idli" } });
await wrapper.find("button").trigger("click");
expect(wrapper.emitted("add")).toEqual([["Idli"]]);
});
app.config.errorHandler = (err) => report(err); // global errors
onErrorCaptured(() => { failed.value = true; return false; });
| Mode | HTML made | Good for |
|---|---|---|
| CSR | In the browser | Apps behind a login |
| SSR | On the server, per request | Fresh, shareable pages |
| SSR + SWR/ISR | On the server, then cached | Menus, product pages |
| SSG | At build time | Docs, blogs, marketing |
| Hybrid | Per route (route rules) | Most real sites |
Nuxt
Nuxt: Full-Stack Vue
// app/pages/dish/[id].vue → /dish/:id
const { data: dish, status } = await useFetch(`/api/dishes/${useRoute().params.id}`);
useSeoMeta({ title: () => `${dish.value?.name} | Meera's Kitchen` });
definePageMeta({ layout: "default", middleware: "auth" });
// server/api/dishes/[id].get.ts
export default defineEventHandler((event) => findDish(Number(getRouterParam(event, "id"))));
// nuxt.config.ts
export default defineNuxtConfig({ routeRules: { "/": { prerender: true }, "/menu": { swr: 60 } } });
Stuck on a pattern? Each heading above names the lesson that explains it with runnable boxes and exercises.
TL;DR
- Start =
createApp(App).mount("#app"),npm create vue@latest. - State =
ref,reactive,computed,watch. - Templates =
{{ }},:attr,@event,v-if,v-for+:key,v-model. - Components =
defineProps,defineEmits,defineModel, slots. - Reuse = composables, directives, plugins.
- App-wide = Vue Router and Pinia.
- Production = tests, performance, error handling, safe deployment.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
No .value in the script | Refs work like plain variables | count.value++ | Nothing updates |
| Destructuring reactive or a store | Values stay linked | toRefs / storeToRefs | Frozen values |
Index as :key | Positions are names | Use an id | State jumps between rows |
| Changing a prop | Props are local | Emit an event or copy it | Warning and overwritten data |
v-html with user text | It is just formatting | {{ }} | XSS |
Secrets in VITE_ variables | .env is private | Keep them on the server | Anyone can read them |
Check Yourself
What to Remember
- State:
reffirst,computedfor values,watchfor jobs. - Templates:
:binds,@listens,v-modeldoes both,:keyon every list item. - Components: props down, events up, slots for content, composables for logic.
- Apps: Vue Router for pages, Pinia for shared state, a service module for HTTP.
- Production: test, measure, handle errors kindly, keep secrets on the server.