Learn Vue - Directives & Custom Directives
Series/Learn Vue/Episode 7
Episode 7 of 24

Learn Vue - Directives & Custom Directives

This episode covers Vue's catalog of built-in directives: v-model for two-way binding, v-show, v-cloak, and v-pre, along with their modifiers, and ends with building custom directives that use lifecycle hooks for specific DOM needs.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Directives are the heart of Vue's template "magic" — special attributes prefixed with v- that tell Vue what to do with an element. In episode 4 you already met v-bind, v-if, v-for, and v-on. Episode 7 completes that picture with the directives used most often in everyday work — and most often misunderstood.

We'll focus on v-model for two-way binding, v-show, v-cloak, and v-pre, then close with custom directives. Understanding the built-in directives keeps you from rewriting what Vue already provides, while custom directives open the door to specific DOM solutions.

v-model: Two-Way Binding

Binding in Both Directions

v-model connects state with an input so the two stay in sync:

JSv-model pada input
<script setup>
import { ref } from "vue";
const nama = ref("");
</script>
 
<template>
  <input v-model="nama" placeholder="Nama" />
  <p>Halo, {{ nama }}</p>
</template>

When you type, nama updates; when nama changes in code, the input fills in. v-model="nama" is syntactic sugar for :value plus @input — two directions in a single directive.

v-model Modifiers

Modifiers adjust the binding behavior:

JSModifier v-model
<script setup>
import { ref } from "vue";
const angka = ref(0);
const teks = ref("");
</script>
 
<template>
  <input v-model.number="angka" type="number" />
  <input v-model.trim="teks" />
  <input v-model.lazy="teks" />
</template>
  • v-model.number converts the input into a number.
  • v-model.trim strips leading and trailing whitespace.
  • v-model.lazy syncs on the change event instead of every keystroke.

v-model on Components

Components can also use v-model through the modelValue contract and the update:modelValue event:

JSv-model pada komponen
<script setup>
const props = defineProps({ modelValue: String });
const emit = defineEmits(["update:modelValue"]);
</script>
 
<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

The parent uses it like a regular input: v-model="nama". This way any form component can support two-way binding.

v-show, v-cloak, and v-pre

v-show: Toggle Without Removal

v-show controls visibility through the CSS display property without removing the element from the DOM:

JSv-show
<script setup>
import { ref } from "vue";
const tampil = ref(true);
</script>
 
<template>
  <div v-show="tampil">Konten yang bisa disembunyikan</div>
  <button @click="tampil = !tampil">Toggle</button>
</template>

v-show="tampil" only changes the display style. Unlike v-if, the element stays rendered so its internal state isn't lost — good for frequent toggling.

v-cloak and v-pre

v-cloak hides templates that haven't been compiled yet so raw text doesn't flash on screen; v-pre skips compilation to display {{ }} syntax as-is.

Building Custom Directives

The Basic Structure of a Custom Directive

When a DOM need isn't covered by the built-in directives, you can create your own. Example: auto-focusing an element when it's mounted:

JSCustom directive vFocus
const vFocus = {
  mounted(el) {
    el.focus();
  },
};

Register it locally in a component or globally through app.directive:

JSMemakai custom directive
<script setup>
import { vFocus } from "../directives/focus";
</script>
 
<template>
  <input v-focus placeholder="Fokus otomatis" />
</template>

v-focus on the input triggers mounted(el) which calls el.focus() — the element gets focus as soon as it's rendered. With <script setup>, a directive imported as vFokus is automatically available as v-fokus.

Directive Lifecycle Hooks

Custom directives support lifecycle hooks just like components:

JSLifecycle hooks direktif
const vDemo = {
  mounted(el, binding) {
    console.log("dimount", binding.value);
  },
  updated(el, binding) {
    console.log("diperbarui", binding.value);
  },
  unmounted(el) {
    console.log("dilepas");
  },
};

binding.value carries the value passed when the directive is used, like v-demo="nilai". The updated hook runs whenever the element is updated, and unmounted runs when the element is removed from the DOM.

Practical Use Cases

Example: Text Highlighting

Custom directives are often used for DOM integration, such as highlighting an element based on a value:

JSDirective vHighlight
const vHighlight = {
  mounted(el, binding) {
    if (binding.value) {
      el.style.backgroundColor = binding.value;
    }
  },
};

v-highlight="'yellow'" sets the background color when the element is rendered. This pattern is useful for highlighting search keywords, field errors, or data status.

Summary

Episode 7 completed your directive catalog: v-model for two-way binding with modifiers like .number, .trim, and .lazy, v-show for CSS toggling, v-cloak and v-pre for special cases, and custom directives with lifecycle hooks for specific DOM needs.

Key takeaways:

  • v-model is syntactic sugar for two-way binding.
  • The .number, .trim, and .lazy modifiers adjust binding behavior.
  • v-show changes display; the element stays in the DOM.
  • v-cloak prevents template flicker; v-pre skips compilation.
  • Custom directives are imported as vNama in <script setup>.
  • Directive lifecycle: mounted, updated, unmounted.

In the next episode 8, we'll move into data fetching and asynchronous UI — fetching data with fetch and Axios, managing loading state and error handling, fetching patterns based on the Composition API, and integrating Vue Query (TanStack Query) for smart caching.

Learn Vue - Directives & Custom Directives | Learn Vue