Suggest an editImprove this articleRefine the answer for “What are attribute bindings in Vue?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Attribute bindings** in Vue - `v-bind` (or `:` shorthand) connects HTML attributes and component props to reactive data, updating the DOM automatically on change. ```html <img :src="imageUrl" :alt="imageAlt" /> <button :disabled="isSubmitting || null">Submit</button> <div v-bind="{ id: itemId, class: itemClass }"></div> ``` **Key:** without `:`, Vue treats the value as a plain string, not a variable.Shown above the full answer for quick recall.Answer (EN)Image**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 from `data`, `props`, or a computed expression; hardcode literals otherwise - Binding `null` or `undefined` removes 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 ```html <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 `data` or `props`: `: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:** ```html <!-- 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"`:** ```html <!-- 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:** ```html <!-- 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:** ```html <!-- 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 ```html <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 ```html <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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.