Explain why Vue templates are basically HTML + data bindings + event bindings. Cover what v-bind and v-on compile to (VNode props + event listeners), how they connect reactivity to DOM updates, and why features like v-model build on them.
Why are v-bind and v-on fundamental to Vue’s template syntax?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
Vue templates are mostly static HTML. The two things that make templates dynamic are:
• v-bind (shorthand :) — state ➜ DOM (attributes/props/class/style)
• v-on (shorthand @) — DOM/component events ➜ code (which usually mutates state)
Those two directives are “fundamental” because they are the main bridges between your reactive state and the rendered output. Most higher-level conveniences (v-model, component prop/event APIs, many patterns) compile down to some combination of v-bind + v-on.
Directive | What you write in templates | What it means (conceptually) |
|---|---|---|
v-bind (:) |
| Evaluate expression during render; when reactive deps change, Vue patches only those bound props/attrs |
v-on (@) |
| Attach a listener; when event fires, run handler (often mutating reactive state) |
Together | UI updates flow: user event ➜ state change ➜ DOM patch | This is the main “reactive loop” in Vue apps |
<script setup>
import { computed, ref } from 'vue';
const count = ref(0);
const loading = ref(false);
const buttonClass = computed(() => ({
btn: true,
'is-loading': loading.value,
'is-positive': count.value > 0
}));
function inc() {
count.value++;
}
function toggleLoading() {
loading.value = !loading.value;
}
</script>
<template>
<!-- v-bind: state -> DOM -->
<button
:class="buttonClass"
:disabled="loading"
:aria-busy="loading"
@click="inc"
>
Count: {{ count }}
</button>
<!-- v-on: event -> code -> state change -->
<button @click="toggleLoading">
Toggle loading
</button>
</template>
What happens under the hood
Vue compiles templates into a render function that produces VNodes. v-bind becomes VNode props (including special handling for class/style and boolean props). v-on becomes event listener props like onClick.
During render, reactive reads are tracked. When reactive state changes, Vue schedules a re-render and patches only changed VNode props/listeners onto the real DOM.
// Pseudo-ish compiled shape (not exact Vue output)
export function render(_ctx) {
return h('button', {
class: _ctx.buttonClass,
disabled: _ctx.loading,
'aria-busy': _ctx.loading,
onClick: _ctx.inc
}, `Count: ${_ctx.count}`);
}
// Later, when count/loading changes:
// Vue re-runs render -> diffs VNodes -> patches only changed props on the same DOM node.
Feature | How it uses v-bind/v-on | Why it matters |
|---|---|---|
Props on components |
| Same binding mechanism, but targets component props instead of DOM attrs |
Component events |
| Same listener mechanism, but listens to emitted events (child ➜ parent) |
v-model | Compiles to a prop bind + an update listener | Explains why v-model is “syntax sugar” |
Object spread binding |
| Pass-through attributes/props in a single place (wrapper components) |
Event modifiers |
| Declarative control of default behavior and propagation |
Dynamic arguments |
| Attribute/event names can be data-driven (use carefully; readability) |
<!-- v-model is just v-bind + v-on (Vue 3, custom component) -->
<!-- Sugar -->
<UserInput v-model="name" />
<!-- Desugared -->
<UserInput
:modelValue="name"
@update:modelValue="name = $event"
/>
<!-- For native inputs (conceptually) -->
<input :value="name" @input="name = $event.target.value" />
Interview-ready takeawayv-bind and v-on are the core primitives because templates become dynamic only by (1) binding reactive values into VNode props and (2) wiring events back into your code. Everything else is either additional syntax around those primitives (like v-model) or structural directives that still rely on them to express dynamic attributes and interactivity.