skip to content

In a Vue 3 directive such as v-on:[eventName].once="handler", what are its parts, and what rules apply to a dynamic [argument]?

level: middleimportance: should knowfreq 38%

answer

  1. name, colon, dot
  2. square brackets evaluate
  3. string or null
  4. no spaces, lowercase in-DOM

basics

~10 s

A Vue 3 directive reads name:argument.modifiers="value"; a bracketed argument is a JavaScript expression that must evaluate to a string, or null to remove the binding, and cannot contain spaces or quotes.

solid answer

~40 s

Every directive follows `v-name:argument.modifier1.modifier2="value"`. In `v-on:[eventName].once="handler"` the **name** is `on`, the **argument** is dynamic (`[eventName]`), `.once` is a **modifier**, and `handler` is the **value** expression; `:`, `@` and `#` are shorthands for `v-bind:`, `v-on:` and `v-slot:`. A **dynamic argument** is evaluated like any template expression, and its result becomes the argument, so `eventName = 'focus'` makes it `v-on:focus`. The result must be a **string**; `null` explicitly removes the binding, and any other type triggers a warning. Because it lives in an attribute name, the bracket expression **cannot contain spaces or quotes** (the compiler reports a missing end bracket), so move complex logic into a computed. In in-DOM templates, browsers lowercase attribute names, so `[someAttr]` becomes `[someattr]`; SFC templates are not affected.

code

vue · 17 lines
vue
<script setup lang="ts">
import { computed, ref } from 'vue'

const tracking = ref(false)
const kind = ref('owner')
const dataAttr = computed(() => `data-${kind.value}`)
// null removes the listener entirely
const trackEvent = computed(() => (tracking.value ? 'mouseenter' : null))

function record() {
  console.log('hovered')
}
</script>

<template>
  <div :[dataAttr]="kind" @[trackEvent]="record">Card</div>
</template>

go deeper

for a junior

Read a directive as name, argument, modifiers and value, and know the :, @ and # shorthands.

for a middle

Explain that a bracketed argument is evaluated each render, must be a string or null, and cannot contain spaces or quotes.

for a senior

Use null arguments and computed names to build wrappers that bind events or attributes conditionally, and avoid the in-DOM casing trap.

for a principal

Prefer explicit props and object-form bindings over clever dynamic arguments in shared components, so templates stay readable.

## The anatomy of a directive A Vue directive is a special attribute starting with `v-`. Its full form has four parts: ```vue-html <form v-on:submit.prevent="onSubmit"> <!-- name : argument . modifier = value --> ``` | Part | Example | Role | |---|---|---| | **Name** | `on` in `v-on` | which directive runs | | **Argument** | `submit` | what it targets: an event, an attribute, a slot name | | **Modifiers** | `.prevent` | flags that change how it binds | | **Value** | `onSubmit` | a JavaScript expression evaluated against the component | Three built-in directives have shorthands that replace `v-name:`: `:` for `v-bind`, `@` for `v-on`, and `#` for `v-slot`. So `:href="url"`, `@click="go"` and `#header` are all name plus argument in compact form. Custom directives use the same anatomy, and receive the argument and modifiers in their binding object. ## Dynamic arguments Wrapping the argument in square brackets makes it an expression: ```vue-html <a :[attributeName]="url">link</a> <button @[eventName]="handle">go</button> ``` The expression is evaluated in the component's scope on every render, and its value becomes the argument. If `attributeName` is `'href'`, the first line behaves exactly like `:href="url"`; if `eventName` changes from `'click'` to `'focus'`, Vue removes the old listener and adds the new one. ## Value constraints - The expression should evaluate to a **string**. - **`null`** is a special, allowed value that explicitly **removes** the binding, useful for attaching a listener or attribute only in some states. - Any other non-string value triggers a warning. ## Syntax constraints The argument lives in an **attribute name**, and HTML attribute names cannot contain spaces or quotes. So: 1. `:['foo' + bar]="value"` is invalid. The compiler cannot find the closing bracket and reports that the end bracket for a dynamic directive argument was not found, noting that it cannot contain spaces. 2. Keep the bracket content to a simple identifier and compute the name elsewhere: `const attr = computed(() => 'data-' + kind.value)` then `:[attr]="value"`. 3. In **in-DOM templates** (written directly in an HTML page), the browser lowercases attribute names before Vue sees them, so `:[someAttr]` becomes `:[someattr]` and looks up the wrong property. Templates in SFCs, which Vue's compiler parses itself, are not affected. ## When to reach for a dynamic argument - A wrapper component that forwards a configurable event name. - Binding one of several ARIA or data attributes chosen by a prop. - Toggling a listener on and off by switching its name to `null`. For several attributes or listeners at once, the argument-less object forms (`v-bind="obj"`, `v-on="obj"`) are usually clearer than several dynamic arguments. ## Common mistakes - Writing string concatenation with spaces or quotes inside the brackets. - Returning a number or boolean from the argument expression. - Using camelCase dynamic arguments in an in-DOM template. - Confusing the argument (`:title`) with the value (`"title"`): the argument names the target, the value supplies the data.

  • How do you attach a Vue 3 listener only while a flag is true, using a dynamic argument?
    Bind the event name through a computed that returns the name when the flag is true and `null` otherwise, as in `@[trackEvent]="record"`. A `null` dynamic argument explicitly removes the binding, so the listener is added and removed as the flag changes.
  • Why does `:[someAttr]` work in a Vue 3 SFC but fail in a template written directly in an HTML page?
    In an in-DOM template the browser parses the HTML first and lowercases attribute names, so Vue receives `:[someattr]` and evaluates a different identifier. SFC templates are parsed by Vue's compiler, which preserves case.

saying these in an interview costs you the question

  • A dynamic argument can be any expression, including string concatenation with spaces.
  • Returning false from a dynamic argument removes the binding.
  • Dynamic arguments are evaluated only once, at mount.
  • In-DOM templates keep camelCase dynamic argument names.