iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

v-bind

v-bind (or the : shorthand) sets an attribute, prop, or class/style from an expression. The fundamental directive — used on almost every Vue template.

:attribute, :class, :style, dynamic

EXAMPLE
<script setup>
import { ref, computed } from 'vue';

const url    = ref('https://example.com');
const image  = ref('/photo.jpg');
const alt    = ref('Profile photo');
const active = ref(true);
const color  = ref('#0ea5e9');
const size   = ref(48);
const meta   = ref({ id: 42, role: 'admin' });
</script>

<template>
    <!-- 1) Bind an attribute -->
    <a v-bind:href="url">Link</a>
    <a :href="url">Link</a>                  <!-- shorthand -->
    <img :src="image" :alt="alt" />

    <!-- 2) Boolean attributes -->
    <button :disabled="!active">Submit</button>
    <input :checked="agreed" type="checkbox" />
    <!-- Vue strips the attribute entirely if value is null/undefined/false -->

    <!-- 3) Bind as prop on a component -->
    <UserCard :user="currentUser" :compact="true" />
    <Modal :open="showModal" :title="'Confirm'" />

    <!-- 4) Class binding — multiple syntaxes -->

    <!-- a) Object syntax — class added when value truthy -->
    <div :class="{ active: isActive, disabled: !canEdit }"></div>

    <!-- b) Array syntax — combine classes -->
    <div :class="['card', 'card--featured', { highlighted: isHighlighted }]"></div>

    <!-- c) Computed for complex logic -->
    <div :class="alertClass">…</div>

    <!-- d) String -->
    <div :class="`alert alert-${level}`"></div>

    <!-- 5) Style binding -->

    <!-- Object — kebab OR camel -->
    <div :style="{ color: 'crimson', 'font-size': size + 'px', backgroundColor: color }"></div>

    <!-- Array — last wins on conflict -->
    <div :style="[baseStyle, error ? errorStyle : null]"></div>

    <!-- CSS custom properties — themeable components -->
    <div :style="{ '--accent': color }" class="themed">…</div>

    <!-- 6) Bind a whole object — multiple attrs at once -->
    <div v-bind="meta"></div>
    <!-- Equivalent to: <div :id="meta.id" :role="meta.role"></div> -->

    <!-- 7) Dynamic argument — attribute name from expression -->
    <button :[eventName]="handler">Click</button>
    <!-- if eventName = 'onclick', binds @click -->

    <!-- 8) Modifiers -->
    <button :title.prop="'tooltip'">Hover</button>    <!-- as DOM prop, not attribute -->
    <button :class.attr="'plain'">Plain</button>      <!-- force as attribute -->
    <!-- .camel is rarely needed -->

    <!-- 9) Conditional rendering vs binding -->
    <div :class="{ hidden: !visible }">Show me</div>     <!-- still in DOM, CSS hides -->
    <div v-if="visible">Show me</div>                     <!-- only in DOM when visible -->
    <div v-show="visible">Show me</div>                   <!-- in DOM; display:none if hidden -->

    <!-- 10) Pass slot data via :is -->
    <component :is="isList ? UnorderedList : OrderedList" :items="items" />

    <!-- 11) Refs are reactive — binding tracks changes -->
    <input :value="name" @@input="e => name = e.target.value" />
    <!-- Or use v-model for two-way -->

    <!-- 12) Style bindings for animations / theming -->
    <div
        class="progress-bar"
        :style="{ width: percent + '%', transition: 'width 300ms ease' }"
    ></div>

    <!-- 13) Pass through reactive computed values -->
    <button :class="computedClass" :disabled="computedDisabled">…</button>
</template>

<style scoped>
.themed { color: var(--accent); border: 1px solid var(--accent); }
</style>

<script setup>
// 14) Computed for complex class logic
import { computed } from 'vue';
const alertClass = computed(() => ({
    'alert':         true,
    'alert--error':  level.value === 'error',
    'alert--info':   level.value === 'info',
    'alert--active': isActive.value,
}));
</script>

<!-- === Real patterns === -->

<!-- 15) Dynamic button styles -->
<template>
    <button
        :class="[
            'px-4 py-2 rounded font-medium',
            variantClasses,
            { 'opacity-50 cursor-not-allowed': disabled },
        ]"
        :disabled="disabled"
        :aria-busy="loading"
    >
        {{ label }}
    </button>
</template>

<script setup>
const props = defineProps({
    variant: { type: String, default: 'primary' },
    disabled: Boolean,
    loading:  Boolean,
    label:    String,
});

const variantClasses = computed(() => ({
    'bg-sky-500 text-white hover:bg-sky-600':       props.variant === 'primary',
    'bg-rose-500 text-white hover:bg-rose-600':     props.variant === 'danger',
    'bg-slate-200 text-slate-900 hover:bg-slate-300': props.variant === 'secondary',
})[true] || '');
</script>

<!-- 16) Form field with error -->
<input
    type="email"
    v-model="email"
    :class="['input', { 'input--error': !!error }]"
    :aria-invalid="!!error"
    :aria-describedby="error ? 'email-error' : undefined"
/>
<p v-if="error" id="email-error" class="text-rose-600">{{ error }}</p>

<!-- 17) Image with fallback -->
<img
    :src="avatarUrl || '/default-avatar.svg'"
    :alt="`Profile photo for ${userName}`"
    loading="lazy"
/>

<!-- 18) Conditional rendering shortcut -->
<button
    :class="primary ? 'btn-primary' : 'btn-secondary'"
    :title="helpText || undefined"
>
    Save
</button>

<!-- 19) Reactive style — drag handle -->
<div
    :style="{
        transform: `translate(${pos.x}px, ${pos.y}px)`,
        cursor:    dragging ? 'grabbing' : 'grab',
    }"
    @@pointerdown="onDown"
    @@pointermove="onMove"
    @@pointerup="onUp"
>
    Drag me
</div>

<!-- 20) Common bugs -->
<!--   • Forgetting : prefix → attribute treats expression as literal string -->
<!--   • Binding to refs vs raw values — use .value in script, not in template -->
<!--   • :class with array containing falsy values — silently no-op (intentional, but confusing) -->
<!--   • Inline object/style new on every render — passes new ref to memoised children -->
<!--   • Boolean attributes (disabled, checked) — v-bind handles correctly; manual concat doesn't -->

<!-- 21) Best practices -->
<!--   ✅ Use : shorthand consistently -->
<!--   ✅ Computed for complex class logic -->
<!--   ✅ Object syntax for conditional classes (clearer intent) -->
<!--   ✅ CSS custom properties via :style for theming -->
<!--   ✅ Pass undefined to hide an optional attribute -->
<!--   ✅ Don't bind objects/arrays you generate inline if children are memoised -->

Why it matters

v-bind covers attributes, props, classes, styles, and even whole objects (v-bind="meta"). Use the : shorthand everywhere and reach for computed for complex class logic — templates stay readable.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<a v-bind:href="url">link</a>
<!-- shorthand -->
<a :href="url">link</a>
Try it Yourself »

Exercise

Shorthand to bind the href attribute.

<a "url">link</a>

Test yourself

Q1. v-bind binds a JavaScript expression to…
Q2. Shorthand for v-bind:href is…
Q3. v-bind with an object binds…

Discussion

Loading…