| |

Vue.js 6 🟢 Attribute Binding with v-bind and Shorthand Syntax (:)

Vue templates are not just static HTML. The framework’s core promise is reactivity: when your data changes, the page updates. But displaying dynamic text with {{ }} only covers part of the story. What about attributes like src, href, disabled, or class? You cannot use mustache interpolation inside an attribute value—<img src="{{ imageUrl }}"> will not work as expected because Vue deliberately does not support that pattern. Attribute binding requires a different tool: the v-bind directive.

This chapter covers everything you need to know about binding HTML attributes to your component data. You will learn the full v-bind: syntax and its ubiquitous shorthand form, the colon (:). You will see how to bind to standard HTML attributes, boolean attributes, and the special cases of class and style where Vue provides powerful enhancements. You will also learn about binding multiple attributes at once, dynamic argument names, and how Vue 3 changed the merge behavior for combined bindings.

By the end of this chapter, you will understand why the colon shorthand is the preferred syntax in modern Vue codebases and how to use it safely in components, lists, and real-world forms.

Key point: The v-bind directive establishes a one-way, reactive connection from your component’s data to an HTML attribute. The shorthand : is not just syntactic sugar—it is the convention used in virtually all modern Vue templates because it keeps the binding intent visually prominent while reducing noise.


Why v-bind exists

The mustache limitation. Vue’s mustache syntax {{ }} is designed for text content, not attributes. When you write <a href="{{ url }}">, the browser sees a literal string value of {{ url }} before Vue ever processes it, and Vue intentionally does not parse mustache inside attribute positions . This is not a bug; it is a deliberate separation. Text interpolation belongs between tags, and attribute binding belongs inside tags with a directive. The v-bind directive fills this gap by telling Vue: “Treat the value of this attribute as a JavaScript expression, not a static string.”

The reactivity requirement. A static href="https://example.com" never changes. But the entire point of a Vue application is that data drives the UI. When you bind <img :src="imageUrl">, you create a live connection. Change imageUrl in your component, and Vue automatically patches the DOM’s src attribute . Without v-bind, you would need to manually query the element and set the attribute in a watcher or method—exactly the imperative DOM manipulation that Vue was designed to eliminate.

The readability trade-off. You could, in theory, achieve some dynamic attributes through computed string manipulation or inline expressions. But concatenating attribute strings like :src="'/images/' + fileName + '.png'" is error-prone and hard to scan . Vue’s binding system lets you keep the expression simple— :src="fullImagePath"—and move complexity into computed properties where it belongs. The template stays declarative; the logic stays in JavaScript.

The class and style enhancements. Binding class and style is so common that Vue provides special syntax beyond simple string binding. You can pass objects to toggle classes, arrays to apply multiple classes, and even mix object and array syntax . This would be impossible with plain attribute binding. The v-bind directive is the entry point for these enhancements.

The boolean attribute challenge. HTML has a category of attributes—disabled, checked, selected, readonly—that are “boolean.” Their presence means true; their absence means false. You cannot write disabled="{{ isDisabled }}" and get correct behavior. With :disabled="isDisabled", Vue handles the presence or removal of the attribute based on the truthiness of the value . This is a subtle but critical distinction that makes v-bind essential for form controls.

The component prop bridge. When you pass data to a child component via props, you use the same colon syntax: <UserCard :name="userName" :age="userAge" />. Under the hood, v-bind is how Vue communicates between components. Understanding attribute binding is a prerequisite for understanding component communication.


a. Full syntax and shorthand

The complete v-bind syntax places the directive name, a colon, and the attribute name before the equals sign. The shorthand simply replaces v-bind: with a single colon.

<!-- Full syntax -->
<img v-bind:src="imageUrl" v-bind:alt="imageAlt">

<!-- Shorthand syntax -->
<img :src="imageUrl" :alt="imageAlt">

Both forms are equivalent. The colon shorthand was introduced in Vue 1 and became the standard convention. The Vue team and community almost universally prefer the shorthand because attribute bindings are frequent enough that the extra v-bind: prefix adds significant visual noise .

The value on the right side of the equals sign is a JavaScript expression evaluated in the component’s scope. This can be a simple property reference, a computed property, a ternary, or even a method call.

<a :href="homeUrl">Home</a>
<a :href="isLoggedIn ? dashboardUrl : loginUrl">Account</a>

When the bound expression evaluates to null or undefined, Vue removes the attribute entirely from the rendered DOM. This behavior is useful for optional attributes.

<button :disabled="isSubmitting">Submit</button>
<!-- If isSubmitting is null or undefined, the disabled attribute is absent -->

b. Binding standard and boolean attributes

Most HTML attributes can be bound the same way: :attr="expression". The expression result is converted to a string and set as the attribute value.

<input :type="inputType" :placeholder="placeholderText" :maxlength="maxChars">

Boolean attributes deserve special attention. For disabled, checked, readonly, and similar attributes, Vue follows the HTML specification: if the bound value is truthy, the attribute is present; if falsy, the attribute is omitted.

<button :disabled="isLoading">Save</button>
<input type="checkbox" :checked="isSelected">

This is different from binding a string value. If you bind :disabled="'false'", the attribute will be present because the string "false" is truthy in JavaScript. The attribute will still render as disabled="false", but the button will be disabled because the browser only cares about the presence of the attribute, not its value. Always pass a boolean.

The class and style attributes are exceptions. They accept strings, objects, and arrays. This will be covered in detail in the next section.


c. Binding class and style

Vue enhances v-bind specifically for class and style because toggling them is so common. You can pass a string, an object, or an array.

Object syntax for class toggles classes based on truthiness.

<div :class="{ active: isActive, 'text-danger': hasError }"></div>

If isActive is true and hasError is false, the rendered class is "active". If both are true, it becomes "active text-danger". The object syntax coexists with a static class attribute .

<div class="static" :class="{ active: isActive }"></div>
<!-- Renders as: <div class="static active"> when isActive is true -->

Array syntax for class applies a list of classes.

<div :class="[activeClass, errorClass]"></div>

You can combine arrays and objects for conditional classes within an array.

<div :class="[{ active: isActive }, errorClass]"></div>

Style binding follows the same patterns. Object syntax uses camelCase or kebab-case property names.

<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

Array syntax allows multiple style objects to be merged.

<div :style="[baseStyles, overrideStyles]"></div>

This enhancement means you rarely need string concatenation for class or style manipulation.


Complete Example Session

<!-- ============================================ -->
<!-- PART 1: BASIC ATTRIBUTE BINDING -->
<!-- ============================================ -->
<template>
  <a :href="homeUrl">Home</a>
  <img :src="avatarUrl" :alt="userName">
</template>

<script setup>
const homeUrl = 'https://example.com'
const avatarUrl = '/images/avatar.png'
const userName = 'Alice'
</script>
<!-- ============================================ -->
<!-- PART 2: BOOLEAN ATTRIBUTES -->
<!-- ============================================ -->
<template>
  <button :disabled="isSubmitting">Submit</button>
  <input type="checkbox" :checked="isSelected">
</template>

<script setup>
import { ref } from 'vue'
const isSubmitting = ref(false)
const isSelected = ref(true)
</script>
<!-- ============================================ -->
<!-- PART 3: CLASS OBJECT SYNTAX -->
<!-- ============================================ -->
<template>
  <div :class="{ active: isActive, 'text-danger': hasError }">
    Status message
  </div>
</template>

<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
</script>
<!-- ============================================ -->
<!-- PART 4: CLASS ARRAY SYNTAX -->
<!-- ============================================ -->
<template>
  <div :class="[baseClass, { active: isActive }]">Content</div>
</template>

<script setup>
import { ref } from 'vue'
const baseClass = 'card'
const isActive = ref(true)
</script>
<!-- ============================================ -->
<!-- PART 5: STYLE BINDING -->
<!-- ============================================ -->
<template>
  <div :style="{ color: textColor, fontSize: size + 'px' }">
    Styled text
  </div>
</template>

<script setup>
import { ref } from 'vue'
const textColor = ref('#333')
const size = ref(16)
</script>
<!-- ============================================ -->
<!-- PART 6: COMPUTED CLASS OBJECT -->
<!-- ============================================ -->
<template>
  <div :class="statusClasses">Message</div>
</template>

<script setup>
import { computed, ref } from 'vue'
const isActive = ref(true)
const error = ref(null)
const statusClasses = computed(() => ({
  active: isActive.value && !error.value,
  'text-danger': error.value && error.value.type === 'fatal'
}))
</script>
<!-- ============================================ -->
<!-- PART 7: BINDING MULTIPLE ATTRIBUTES WITH v-bind -->
<!-- ============================================ -->
<template>
  <img v-bind="imageAttrs">
</template>

<script setup>
const imageAttrs = {
  src: '/images/photo.jpg',
  alt: 'A photo',
  width: 400,
  height: 300
}
</script>
<!-- ============================================ -->
<!-- PART 8: DYNAMIC ARGUMENT NAMES -->
<!-- ============================================ -->
<template>
  <a :[attrName]="urlValue">Dynamic link</a>
</template>

<script setup>
import { ref } from 'vue'
const attrName = ref('href')
const urlValue = ref('https://example.com')
</script>
<!-- ============================================ -->
<!-- PART 9: MERGE ORDER WITH v-bind OBJECT -->
<!-- ============================================ -->
<template>
  <div v-bind="{ id: 'from-object' }" id="individual"></div>
  <!-- Renders with id="individual" because individual comes after -->
</template>
<!-- ============================================ -->
<!-- PART 10: COMPONENT CLASS INHERITANCE -->
<!-- ============================================ -->
<template>
  <MyComponent :class="{ active: isActive }" />
  <!-- The active class is added to the component's root element -->
</template>

<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
const isActive = ref(true)
</script>

The ten parts covered the full range of attribute binding: basic attributes, boolean handling, class object and array syntax, style binding, computed class objects, binding entire objects of attributes, dynamic argument names, merge order behavior, and component class inheritance.


Quick Reference

Core Attribute Binding Syntax

SyntaxExampleNotes
Fullv-bind:href="url"Explicit, rarely used
Shorthand:href="url"Preferred convention
Dynamic arg:[attrName]="value"Attribute name from data
Object spreadv-bind="obj"Binds all key-value pairs
Null value:src="null"Attribute removed

Boolean Attributes

AttributeTruthy ValueFalsy Value
disabledAttribute presentAttribute absent
checkedAttribute presentAttribute absent
selectedAttribute presentAttribute absent
readonlyAttribute presentAttribute absent

Class Binding Patterns

PatternSyntaxResult
String:class="className"Single class name
Object:class="{ active: isActive }"Conditional toggle
Array:class="[a, b]"Multiple classes
Mixed:class="[a, { b: cond }]"Array with conditional
Computed:class="computedObj"Reactive object

Style Binding Patterns

PatternSyntaxNotes
Object:style="{ color: c }"camelCase preferred
Array:style="[base, override]"Merge multiple objects
Computed:style="styleObj"Reactive style object

Vue 3 Merge Behavior

Declaration OrderResultNotes
Object first, individual afterIndividual winsDefault in 2.x
Individual first, object afterObject winsVue 3 order-sensitive

Best Practices

✅ Do This:

<!-- Use shorthand consistently -->
<img :src="imageUrl" :alt="altText">                    <!-- ✅ -->
<a :href="link">Click</a>                                <!-- ✅ -->

<!-- Use boolean values for boolean attributes -->
<button :disabled="isLoading">Save</button>             <!-- ✅ -->

<!-- Move complex class logic to computed -->
<div :class="statusClasses">Status</div>                 <!-- ✅ -->

<!-- Use object syntax for conditional classes -->
<div :class="{ active: isActive }">Item</div>           <!-- ✅ -->

<!-- Bind objects for many attributes -->
<img v-bind="imageProps">                               <!-- ✅ -->

<!-- Use dynamic arguments for reusable components -->
<a :[linkAttr]="url">Link</a>                           <!-- ✅ -->

<!-- Order bindings intentionally in Vue 3 -->
<div v-bind="{ id: 'a' }" id="b">Text</div>              <!-- ✅ (id="b") -->

❌ Don’t Do This:

<!-- Don't use mustache in attributes -->
<img src="{{ imageUrl }}">                              <!-- ❌ -->

<!-- Don't pass strings for boolean attributes -->
<button :disabled="'false'">Save</button>               <!-- ❌ -->

<!-- Don't concatenate class strings -->
<div :class="'btn ' + (active ? 'active' : '')">        <!-- ❌ -->

<!-- Don't use v-bind when object won't change -->
<img v-bind="{ src: staticPath }">                      <!-- ❌ -->

<!-- Don't forget the colon for dynamic binding -->
<img src="imageUrl">                                    <!-- ❌ (static string) -->

<!-- Don't rely on Vue 2 merge order in Vue 3 -->
<div id="individual" v-bind="{ id: 'object' }">        <!-- ❌ (unclear intent) -->

Common Pitfalls

PitfallWhy It HappensFix
Mustache in attributeAttempting {{ }} inside tagUse :attr binding instead
Boolean as string:disabled="'false'" is truthyPass actual boolean: :disabled="false"
Missing colonsrc="url" renders literal stringAdd : prefix for dynamic value
Unreactive dataProperty not declared reactivelyUse ref/reactive or data()
Class object not updatingPassing non-reactive objectUse computed or reactive object
Dynamic arg undefined:[undefined]="x" fails silentlyEnsure arg evaluates to valid string
Merge order surpriseVue 3 order-sensitive mergingPlace v-bind before individual attrs

Real-World Examples

1. Dynamic Image Gallery

<img :src="`/images/${selectedImage}.jpg`" :alt="selectedImage">

2. Form Submit Button

<button :disabled="!isFormValid || isSubmitting" @click="submit">Submit</button>

3. Navigation Link with Active State

<RouterLink :to="path" :class="{ 'router-link-active': isCurrent }">
  {{ label }}
</RouterLink>

4. Dynamic Table Row Styling

<tr :class="{ 'table-warning': row.overdue, 'table-success': row.completed }">

5. Progress Bar with Dynamic Width

<div class="progress-bar" :style="{ width: progress + '%' }"></div>

6. Disabled Checkbox List

<input type="checkbox" :checked="item.selected" :disabled="item.locked">

7. Dynamic ARIA Attributes

<button :aria-label="buttonLabel" :aria-pressed="isPressed">

8. Card Component with Theme

<div :class="['card', `card--${theme}`]" :style="cardStyles">

9. Conditional Input Readonly

<input :readonly="isLocked" :placeholder="isLocked ? 'Locked' : 'Editable'">

10. Badge with Count

<span class="badge" :class="{ 'badge--zero': count === 0 }">{{ count }}</span>

Visual

The Binding Pipeline

┌─────────────────────────────────────────────────────────────┐
│  VUE ATTRIBUTE BINDING PIPELINE                             │
│                                                             │
│  Component Data          Template                DOM        │
│  ┌─────────────┐        ┌──────────────┐      ┌─────────┐  │
│  │             │        │              │      │         │  │
│  │ imageUrl    │───────▶│ :src="..."   │─────▶│ src=".."│  │
│  │ isActive    │───────▶│ :class="{...}"│─────▶│ class="."│  │
│  │ isDisabled  │───────▶│ :disabled=".."│─────▶│ disabled│  │
│  │             │        │              │      │         │  │
│  └─────────────┘        └──────────────┘      └─────────┘  │
│                                                             │
│  Data change triggers re-render                             │
│  Vue patches only the affected attribute                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Class Binding Decision Tree

┌─────────────────────────────────────────────────────────────┐
│  CLASS BINDING: WHICH SYNTAX TO USE?                        │
│                                                             │
│  Start                                                      │
│    │                                                        │
│    ├── Single dynamic class name?                           │
│    │     └── YES ──▶ :class="className"                     │
│    │                                                        │
│    ├── Toggle one or more classes conditionally?            │
│    │     └── YES ──▶ :class="{ active: isActive }"          │
│    │                                                        │
│    ├── Apply a list of classes?                             │
│    │     └── YES ──▶ :class="[a, b, c]"                     │
│    │                                                        │
│    ├── Mix static, dynamic, and conditional?                │
│    │     └── YES ──▶ :class="[base, { active: cond }]"      │
│    │                                                        │
│    └── Complex logic from multiple sources?                 │
│          └── YES ──▶ computed property returning object     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Boolean Attribute Behavior

┌─────────────────────────────────────────────────────────────┐
│  BOOLEAN ATTRIBUTES: TRUTHY vs FALSY                        │
│                                                             │
│  :disabled="true"      ──▶  <button disabled>               │
│  :disabled="false"     ──▶  <button>                        │
│  :disabled="0"         ──▶  <button>                        │
│  :disabled="1"         ──▶  <button disabled>               │
│  :disabled="''"        ──▶  <button>                        │
│  :disabled="'false'"   ──▶  <button disabled>  ⚠️ string!  │
│                                                             │
│  Rule: JavaScript truthiness determines presence            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Vue 3 Merge Order

┌─────────────────────────────────────────────────────────────┐
│  v-bind MERGE ORDER IN VUE 3                                │
│                                                             │
│  <div id="individual" v-bind="{ id: 'object' }">            │
│                                                             │
│  Individual first, object second                            │
│  ──▶  id="object"  (object wins)                            │
│                                                             │
│  ─────────────────────────────────────────────────────      │
│                                                             │
│  <div v-bind="{ id: 'object' }" id="individual">            │
│                                                             │
│  Object first, individual second                            │
│  ──▶  id="individual"  (individual wins)                    │
│                                                             │
│  Order matters in Vue 3. No more "individual always wins".  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
Directivev-bind establishes one-way reactive data binding
Shorthand: replaces v-bind: in modern templates
Value typeJavaScript expression evaluated in component scope
Null behaviorAttribute removed when value is null/undefined
Boolean attrsTruthy adds attribute, falsy omits it
Class syntaxString, object, array, or mixed
Style syntaxObject or array of objects
Object spreadv-bind="obj" binds all key-value pairs
Dynamic arg:[name]="value" with reactive name
Component propsSame : syntax passes props to children
Vue 3 changeMerge order of v-bind is order-sensitive

Key takeaways:

  • Shorthand is standard. The colon : syntax is the community convention for attribute binding. Use it everywhere except when teaching or documenting the full directive name.
  • Boolean attributes need booleans. Passing 'false' as a string will still enable a disabled button because strings are truthy. Always pass actual boolean values.
  • Class and style get enhancements. Vue provides object and array syntax specifically for class and style binding, eliminating string concatenation and enabling conditional logic inline.
  • Computed properties keep templates clean. When class or style logic involves multiple conditions, move it to a computed property that returns an object. The template becomes declarative again.
  • Null removes the attribute. This is useful for optional attributes: bind to null and Vue removes the attribute from the DOM entirely.
  • Object spread binds many attributes. When you have an object whose keys match attribute names, v-bind="obj" saves repetitive individual bindings.
  • Dynamic arguments enable reusability. :[attrName]="value" lets you build components that bind different attributes based on props or state.
  • Vue 3 changed merge order. If you mix v-bind="object" with individual attributes on the same element, the order determines precedence. Plan your bindings accordingly.
  • Components inherit class bindings. When you bind :class on a component with a single root, those classes merge onto the root element.

Remember: Attribute binding is the mechanism that makes Vue templates dynamic. Without it, you would be writing imperative DOM manipulation code. The colon shorthand is your primary tool for expressing these bindings, and understanding how it handles strings, booleans, objects, and null values is essential for building reactive interfaces. The special cases of class and style deserve extra attention because they appear in nearly every component, and Vue’s object and array syntax for these attributes eliminates the error-prone string concatenation that plagues other frameworks.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!