What are attribute bindings in Vue?
Attribute bindings in Vue use the v-bind directive (or its : shorthand) to connect HTML attributes and component props to reactive JavaScript data.
Theory
TL;DR
v-bind:href="url"and:href="url"are the same;:is the standard shorthand- Unlike static
href="...", a bound attribute updates in the DOM automatically when the data changes - Use
:whenever the value comes fromdata,props, or a computed expression; hardcode literals otherwise - Binding
nullorundefinedremoves the attribute from the DOM entirely - Binding
:disabled="false"still disables a button because browsers treat any non-empty string as truthy for boolean attributes
Quick example
<template>
<!-- Static: always the same string -->
<a href="https://example.com">Static</a>
<!-- Bound: updates when userProfileUrl changes -->
<a :href="userProfileUrl">Profile</a>
<button @click="updateUrl">Change URL</button>
</template>
<script>
export default {
data() {
return { userProfileUrl: 'https://example.com/profile/1' };
},
methods: {
updateUrl() {
this.userProfileUrl = 'https://example.com/profile/2';
// href updates in the DOM automatically, no manual DOM call needed
}
}
};
</script>The bound link reflects the new URL the moment userProfileUrl changes. The static one never does.
Static vs. dynamic attributes
Static attributes like href="static.com" are baked into the DOM at render time. Vue ignores them after that. Bindings like :href="url" create a reactive connection: Vue's reactivity system (Proxy-based in Vue 3) tracks url, and when it changes, the scheduler queues a patch that updates only that attribute via the virtual DOM. No full re-render, just the attribute swap.
One common trap: writing <button disabled="isLoading"> instead of :disabled="isLoading". Without :, Vue treats "isLoading" as a plain string. The button is always disabled, regardless of the data.
When to use
- Value is a fixed string known at write time: hardcode it (
href="mailto:hi@example.com") - Value comes from
dataorprops::src="imageUrl" - Conditional logic:
:disabled="!isValid" - Inline style with logic:
:style="{ color: isError ? 'red' : 'green' }" - Passing data to a child component:
<MyButton :count="items.length"> - Spreading multiple attributes at once:
<div v-bind="{ id: docId, class: docClass }">
How Vue handles bindings internally
Vue's template compiler turns :href="url" into a render function call: h('a', { href: _ctx.url }). During mount and updates, the Proxy traps writes to url, queues a scheduler job, and runs patch to call setAttribute on the DOM node. Only that attribute changes. Everything else on the element stays untouched.
Common mistakes
Binding null or undefined without a fallback:
<!-- Wrong: avatarUrl=undefined → src="" → 404 -->
<img :src="avatarUrl" />
<!-- Fix: provide a fallback -->
<img :src="avatarUrl || '/default-avatar.png'" />When the value might be missing, add a fallback or guard with v-if="avatarUrl".
Boolean attributes with string "false":
<!-- Wrong: isDisabled=false → disabled="false" → button is still disabled -->
<button :disabled="isDisabled">Go</button>
<!-- Fix: use null to remove the attribute -->
<button :disabled="isDisabled || null">Go</button>Browsers treat any non-empty string as truthy for boolean attributes. Pass null to actually remove it.
Always-on class binding:
<!-- Wrong: "active" class is always present -->
<div :class="'active'">...</div>
<!-- Fix: object syntax for conditional classes -->
<div :class="{ active: isActive }">...</div>Object literal passed as a single attribute:
<!-- Wrong: can't bind an object literal as one attribute value -->
<div :data="{ id: user.id }">...</div>
<!-- Fix: use a specific data attribute -->
<div :data-id="user.id">...</div>Real-world usage
- Vuetify:
:color="themeColor"on buttons for dynamic theming - Quasar:
:dense="isMobile"on lists for responsive sizing - Nuxt with i18n:
:to="localePath('/cart')"for localized routing - Element Plus:
:model-value="searchQuery"on form inputs - Pinia:
:items="cartStore.items"in cart components
Follow-up questions
Q: What is the difference between :disabled="false" and omitting the disabled attribute?
A: :disabled="false" sets disabled="false" in the HTML. Browsers treat any non-empty string as truthy for boolean attributes, so the button stays disabled. Omitting the attribute or passing :disabled="null" removes it and enables the button.
Q: How does Vue handle undefined vs. null as a binding value?
A: Both remove the attribute in Vue 3. In Vue 2, null could render as an empty string. For optional attributes, the safe pattern is value || undefined.
Q: How do you bind multiple attributes at once?
A: Use v-bind with an object: <div v-bind="{ id: docId, class: docClass }">. Vue spreads all keys as individual attributes on the element.
Q: In Nuxt SSR, what causes hydration mismatches with attribute bindings?
A: The server renders with initial static values; the client hydrates with reactive state. If those differ at mount time, Vue logs a hydration mismatch. Fix: use useAsyncData for shared state, or wrap the element in v-if="mounted".
Examples
Basic: image with a dynamic source
<template>
<img :src="catImage" :alt="`Cat ${catId}`" />
<button @click="nextCat">Next</button>
</template>
<script>
export default {
data() {
return {
catId: 1,
catImage: 'https://example.com/cat1.jpg'
};
},
methods: {
nextCat() {
this.catId++;
this.catImage = `https://example.com/cat${this.catId}.jpg`;
// src and alt update together, no DOM manipulation needed
}
}
};
</script>Both :src and :alt react when catId changes. The browser reloads the image because it detects a new src value.
Intermediate: login form with a state-driven button
<template>
<form @submit.prevent="login">
<input
:value="email"
@input="email = $event.target.value"
placeholder="email@example.com"
required
/>
<button :disabled="isSubmitting || !email.includes('@')">
{{ isSubmitting ? 'Logging in...' : 'Login' }}
</button>
</form>
</template>
<script>
export default {
data() {
return { email: '', isSubmitting: false };
},
methods: {
async login() {
this.isSubmitting = true;
await new Promise(r => setTimeout(r, 1000)); // simulate API call
this.isSubmitting = false;
// button re-enables automatically after the request finishes
}
}
};
</script>The :disabled expression covers two cases at once: an invalid email format and a request in progress. No imperative DOM calls. This pattern shows up in almost every production Vue form I've worked with.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.