Skip to content

VueJS - Binding

Vue 3 - Attribute, Class, Style, and Form Bindings

Section titled “Vue 3 - Attribute, Class, Style, and Form Bindings”

Vue’s data binding system allows you to declaratively connect your application’s data to the DOM. This chapter covers how to bind HTML attributes, classes, inline styles, and form inputs using directives like v-bind and v-model in Vue 3.

You cannot use standard mustache syntax ({{ }}) inside HTML attributes. Instead, use the v-bind directive or its shorthand : to bind an attribute to a JavaScript expression or data property.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Attribute Binding</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app-attr">
<!-- Incorrect: Will render href="{{ googleLink }}" literally -->
<!-- <a href="{{ googleLink }}">Google (Incorrect)</a> -->
<!-- Correct: using v-bind -->
<a v-bind:href="googleLink" target="_blank">Google (v-bind)</a>
<br>
<!-- Correct: using shorthand : -->
<a :href="yahooLink" target="_blank">Yahoo (shorthand)</a>
<br>
<!-- Binding an element's ID -->
<p :id="paragraphId">This paragraph has a dynamic ID.</p>
<!-- Conditionally disabling a button -->
<button :disabled="isButtonDisabled">{{ isButtonDisabled ? 'Disabled' : 'Enabled' }}</button>
<button @click="toggleButton">Toggle Disable</button>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const googleLink = ref('https://www.google.com');
const yahooLink = ref('https://www.yahoo.com');
const paragraphId = ref('my-unique-paragraph');
const isButtonDisabled = ref(false);
function toggleButton() {
isButtonDisabled.value = !isButtonDisabled.value;
}
return {
googleLink,
yahooLink,
paragraphId,
isButtonDisabled,
toggleButton
};
}
}).mount('#app-attr');
</script>
</body>
</html>

v-bind:href or :href binds the anchor tag’s href attribute to the value of the googleLink or yahooLink reactive variable. Similarly, :id binds the paragraph’s ID, and :disabled dynamically binds the button’s disabled state.

Binding HTML Classes (v-bind:class or :class)

Section titled “Binding HTML Classes (v-bind:class or :class)”

You can dynamically toggle classes by binding :class to an object or an array.

Bind to an object where keys are class names and values are boolean expressions determining if the class should be applied.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Class Binding (Object)</title>
<script src="https://unpkg.com/vue@3"></script>
<style>
.active { background-color: lightgreen; font-weight: bold; }
.text-danger { color: red; }
.base-style { padding: 10px; border: 1px solid #ccc; margin-top: 10px; }
</style>
</head>
<body>
<div id="app-class-obj">
<div
class="base-style"
:class="{ active: isActive, 'text-danger': hasError }">
This div's classes change dynamically.
</div>
<button @click="isActive = !isActive">Toggle Active</button>
<button @click="hasError = !hasError">Toggle Error</button>
<!-- Can also bind to a computed property returning an object -->
<div class="base-style" :class="classObject">
Bound to computed object.
</div>
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const isActive = ref(true);
const hasError = ref(false);
// Computed property returning the class object
const classObject = computed(() => ({
active: isActive.value,
'text-danger': hasError.value
// You can add more static or dynamic classes here
}));
return { isActive, hasError, classObject };
}
}).mount('#app-class-obj');
</script>
</body>
</html>

The active class is applied only when isActive is true, and text-danger only when hasError is true. Note that kebab-cased class names like text-danger need quotes in the object key. You can mix static (base-style) and dynamic classes.

Bind to an array containing class names (strings) or objects (using the object syntax).

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Class Binding (Array)</title>
<script src="https://unpkg.com/vue@3"></script>
<style>
.active { background-color: lightblue; }
.important { font-weight: bold; border: 2px solid blue; }
.base { padding: 15px; margin-top: 10px; }
</style>
</head>
<body>
<div id="app-class-arr">
<div :class="['base', 'important', { active: isActive }]">
Mixing array and object syntax.
</div>
<button @click="isActive = !isActive">Toggle Active</button>
<!-- Binding to a computed property returning an array -->
<div :class="classArray">
Bound to computed array.
</div>
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const isActive = ref(false);
const activeClass = ref('active'); // Class name in a variable
const importantClass = ref('important');
const classArray = computed(() => [
'base',
importantClass.value, // Dynamically get class name
isActive.value ? activeClass.value : '' // Conditional class
]);
return { isActive, classArray };
}
}).mount('#app-class-arr');
</script>
</body>
</html>

This applies the base and important classes always, and the active class conditionally based on isActive. You can use ternary operators or include objects within the array.

When using :class on a custom component, the classes will be added to the component’s root element, merging with any existing classes on that root element.

Binding Inline Styles (v-bind:style or :style)

Section titled “Binding Inline Styles (v-bind:style or :style)”

Bind :style to an object or an array of objects.

The object syntax is recommended. CSS property names can use camelCase or kebab-case (use quotes for kebab-case).

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Style Binding (Object)</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app-style-obj">
<!-- Direct object binding -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px', 'margin-top': '10px' }">
Style bound directly.
</div>
<!-- Binding to a data object -->
<div :style="styleObject">
Style bound to data object.
</div>
<label>Color:</label> <input v-model="activeColor">
<label>Font Size (px):</label> <input type="range" v-model="fontSize" min="10" max="40">
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const activeColor = ref('blue');
const fontSize = ref(16);
// Computed property returning the style object
const styleObject = computed(() => ({
color: activeColor.value,
fontSize: `${fontSize.value}px` // Use template literal
// backgroundColor: 'lightyellow' // Add more styles
}));
return { activeColor, fontSize, styleObject };
}
}).mount('#app-style-obj');
</script>
</body>
</html>

The text color and font size are dynamically controlled by the activeColor and fontSize variables.

You can bind :style to an array of multiple style objects. This is useful for applying styles from several sources.

<div id="app-style-arr">
<div :style="[baseStyles, overrideStyles]">
Style bound to array of objects.
</div>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const baseStyles = ref({
fontWeight: 'bold',
padding: '10px'
});
const overrideStyles = ref({
color: 'green',
border: '1px solid green'
});
return { baseStyles, overrideStyles };
}
}).mount('#app-style-arr');
</script>

Styles from overrideStyles will merge with and potentially overwrite styles in baseStyles if they target the same properties.

Vue automatically adds vendor prefixes (like -webkit-) where needed for CSS properties bound via :style.

The v-model directive creates two-way data binding on form inputs (<input>, <textarea>, <select>). It automatically picks the correct way to update the element based on the input type.

Example: Text, Textarea, Checkbox, Radio, Select

Section titled “Example: Text, Textarea, Checkbox, Radio, Select”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 v-model Example</title>
<script src="https://unpkg.com/vue@3"></script>
<style> form > div { margin-bottom: 15px; } label { margin-right: 10px; } </style>
</head>
<body>
<div id="app-v-model">
<form @submit.prevent>
<!-- Text Input -->
<div>
<label for="name">Name:</label>
<input type="text" id="name" v-model="formData.name" placeholder="Enter name">
<p>Entered Name: {{ formData.name }}</p>
</div>
<!-- Textarea -->
<div>
<label for="message">Message:</label>
<textarea id="message" v-model="formData.message" placeholder="Enter message"></textarea>
<p style="white-space: pre-line;">Entered Message: {{ formData.message }}</p>
</div>
<!-- Single Checkbox (Boolean) -->
<div>
<input type="checkbox" id="agree" v-model="formData.agreed">
<label for="agree">Agree to terms</label>
<p>Agreed: {{ formData.agreed }}</p>
</div>
<!-- Multiple Checkboxes (Array) -->
<div>
<span>Interests:</span>
<input type="checkbox" id="tech" value="Technology" v-model="formData.interests">
<label for="tech">Technology</label>
<input type="checkbox" id="music" value="Music" v-model="formData.interests">
<label for="music">Music</label>
<input type="checkbox" id="sports" value="Sports" v-model="formData.interests">
<label for="sports">Sports</label>
<p>Selected Interests: {{ formData.interests }}</p>
</div>
<!-- Radio Buttons -->
<div>
<span>Favorite Color:</span>
<input type="radio" id="red" value="Red" v-model="formData.favoriteColor">
<label for="red">Red</label>
<input type="radio" id="green" value="Green" v-model="formData.favoriteColor">
<label for="green">Green</label>
<p>Selected Color: {{ formData.favoriteColor }}</p>
</div>
<!-- Select Dropdown -->
<div>
<label for="country">Country:</label>
<select id="country" v-model="formData.country">
<option disabled value="">Please select one</option>
<option>USA</option>
<option>Canada</option>
<option>Mexico</option>
</select>
<p>Selected Country: {{ formData.country }}</p>
</div>
<!-- Select Multiple (Array) -->
<div>
<label for="options">Options (Multiple):</label>
<select id="options" v-model="formData.selectedOptions" multiple>
<option value="A">Option A</option>
<option value="B">Option B</option>
<option value="C">Option C</option>
</select>
<p>Selected Options: {{ formData.selectedOptions }}</p>
</div>
</form>
</div>
<script>
const { createApp, reactive } = Vue;
createApp({
setup() {
const formData = reactive({
name: '',
message: '',
agreed: false,
interests: [], // Array for multiple checkboxes
favoriteColor: 'Red', // Initial radio selection
country: '', // Initial select value
selectedOptions: [] // Array for multiple select
});
return { formData };
}
}).mount('#app-v-model');
</script>
</body>
</html>
  • v-model syncs the form element’s value with the corresponding property in formData.
  • For text inputs and textareas, it uses the value property and input event.
  • For checkboxes, it uses the checked property and change event. A single checkbox binds to a boolean, while multiple checkboxes with the same v-model bind to an array of their value attributes.
  • For radio buttons, it uses the checked property and change event, binding to the selected radio’s value.
  • For select fields, it uses value and change. A single select binds to the selected option’s value. A multiple select binds to an array of selected option values.

v-model has built-in modifiers:

  • .lazy: Syncs after change events instead of input (e.g., update when focus leaves the input, not on every keystroke).
  • .number: Automatically typecasts the input value to a number. If the value cannot be parsed, the original value is returned.
  • .trim: Automatically trims whitespace from the user input.
<input v-model.lazy="message"> <!-- Updates when input loses focus -->
<input v-model.number="age" type="number"> <!-- Ensures age is a number -->
<input v-model.trim="username"> <!-- Removes leading/trailing whitespace -->

Data binding is a cornerstone of Vue, enabling seamless synchronization between your application state and the user interface.