Hello! Welcome back.
In our last lesson, we explored the "magic" behind Vue's reactivity system. You learned how ref() and reactive() use JavaScript features like Getters/Setters and Proxies to track changes to your data and trigger UI updates. This gave you a solid grasp of the "why" behind state management.
Today, we'll focus on the "how." You'll learn to use that reactive state to bring your application to life. We'll cover Vue directives, which are special attributes you add to your HTML to create dynamic and interactive user interfaces. These are the fundamental building blocks for making things appear, disappear, loop, and react to user input.
By the end of this lesson, you will be able to use Vue's core directives for conditional rendering (v-if), list rendering (v-for), attribute binding (v-bind), and event handling (v-on). Mastering these will give you the complete toolset needed for our next task: building the to-do list mini-project.
What are Directives?
Before we dive in, let's clarify what a directive is. In Vue, directives are special attributes prefixed with v-. Their job is to apply reactive, side-effect-driven behavior to the DOM when the value of their expression changes. Think of them as instructions that tell Vue how to connect your data to your HTML.
For a quick but thorough introduction to the concept, have a look at the following reading.
Vue Basics: What Are Directives in Vue?
This article from Telerik provides a clear definition of what directives are and their basic anatomy. It will help you frame the concepts we're about to cover.
Read the first two sections, 'What Are Directives in Vue?' and 'The Anatomy of a Directive'. Focus on understanding the v- prefix and the general structure: v-directiveName:argument.modifier="expression".
The Core Directives in Action
The best way to learn these directives is to see them work together. Let's start with a basic App.vue file. Our goal will be to build a very simple interactive list, which will be the foundation for your to-do app project.
Clear your src/App.vue and replace it with this starting code:
<script setup>
import { ref } from 'vue'
// Our reactive data
const tasks = ref([
{ id: 1, text: 'Learn HTML', done: true },
{ id: 2, text: 'Learn CSS', done: true },
{ id: 3, text: 'Learn JavaScript', done: false }
])
</script>
<template>
<div class="app">
<h1>My Tasks</h1>
<ul>
<!-- We will render our list here -->
</ul>
</div>
</template>
<style scoped>
.app {
max-width: 400px;
margin: 2rem auto;
font-family: sans-serif;
}
.done {
text-decoration: line-through;
}
</style>
Right now, this just shows a heading. Let's make it dynamic.
1. Rendering Lists with v-for
To display our list of tasks, we need to loop through the tasks array and create an <li> element for each one. This is the job of the v-for directive.
The syntax is v-for="item in items", where items is your array and item is an alias for the element at each iteration.
Update your <template> section:
<ul>
<li v-for="task in tasks">
{{ task.text }}
</li>
</ul>
If you run your app now, you'll see the list of tasks! However, your browser console will show a warning: [Vue warn]: v-for is missing a key..
Why the :key is essential:
Vue uses the key attribute to track each element's identity. This allows it to reuse and reorder existing elements efficiently, rather than re-rendering the entire list every time it changes. The key must be a unique primitive value (a string or number). In our case, task.id is perfect.
The key is a special attribute that needs to be bound to a dynamic value, so we use v-bind:key, or its shorthand, just a colon (:).
Let's fix our code:
<ul>
<li v-for="task in tasks" :key="task.id">
{{ task.text }}
</li>
</ul>
The warning should now be gone.
2. Handling User Input with v-on
Static lists are boring. Let's add a button to mark a task as complete. We need to listen for a user's click and then call a function. This is what v-on is for. Its shorthand is the @ symbol.
First, let's add a function to our <script> to toggle a task's done status.
// In <script setup>
function toggleTask(taskToToggle) {
taskToToggle.done = !taskToToggle.done
}
Now, let's add a button in our v-for loop and use @click to call this function.
<li v-for="task in tasks" :key="task.id">
{{ task.text }}
<button @click="toggleTask(task)">
Toggle
</button>
</li>
Now when you click the button, nothing visually happens yet, but the done property in our reactive tasks array is being toggled. We can now use that state.
3. Binding Attributes with v-bind
We want to apply a visual style when a task is done. We can dynamically bind the class attribute using v-bind:class, or its shorthand :class.
Vue provides a convenient object syntax for :class: you provide an object where the keys are class names and the values are boolean expressions. The class will be applied only if the expression is true.
Let's modify our <li> to use this. We already have a .done style defined.
<li v-for="task in tasks" :key="task.id" :class="{ done: task.done }">
<span>{{ task.text }}</span>
<!-- The button is still here, I've just wrapped the text in a span for styling -->
</li>
Note: I removed the button temporarily to focus on the class binding. You can add it back.
Now, when you click the "Toggle" button (if you added it back), you should see the line-through style appear and disappear! This is because:
@clickcallstoggleTask().toggleTask()changes the reactivetask.doneproperty.- Vue detects the change and re-evaluates the
:classbinding. - The
doneclass is added or removed from the DOM.
This video demonstrates these concepts of list rendering, event handling, and attribute binding in the context of a similar task application.
Modern Vue.js Crash Course | with TypeScript + script setup + Composition API
Watch this section from the 'Modern Vue.js Crash Course' to see v-for, @ events, and : attribute binding used together to build a dynamic list.
Watch from 19:23 to 20:22 to see v-for and :key being implemented. Then, continue watching from 26:48 to 27:55 to see how conditional class binding (:class) is used to style completed tasks.
4. Adding Items with v-model
To add new tasks, we need an input field. We want the value of this input to be synchronized with a variable in our script. This is called two-way data binding, and it's handled by the v-model directive.
First, let's add a new ref to hold the input's value and a function to add the new task.
// In <script setup>
const newTaskText = ref('')
function addTask() {
if (newTaskText.value.trim() === '') return // Don't add empty tasks
tasks.value.push({
id: Date.now(), // Simple unique ID
text: newTaskText.value,
done: false
})
newTaskText.value = '' // Clear the input
}
Now, let's add a form to our template. We'll use v-model on the input and @submit on the form.
The .prevent modifier on @submit.prevent is very important—it calls event.preventDefault() for us, stopping the browser from doing a full-page reload on form submission, which is the default behavior.
<form @submit.prevent="addTask">
<input
v-model="newTaskText"
placeholder="Add a new task"
/>
<button type="submit">Add Task</button>
</form>
Place this form right after your <h1> tag. Now you can type in the input and add new tasks to your list!
v-model is actually syntactic sugar. Under the hood, it's a combination of v-bind and v-on:
- It binds the input's
valueattribute to your data (:value="newTaskText"). - It listens for the
inputevent and updates your data (@input="event => newTaskText = event.target.value").
Understanding this helps you see that v-model isn't magic, it's just a convenient shortcut for a very common pattern.
5. Conditional Rendering with v-if
What should we display if the task list is empty? We can use the v-if directive to conditionally render an element. If the expression passed to v-if is truthy, the element is rendered. If it's falsy, the element is completely removed from the DOM.
You can create an "if-else" block using v-else on a subsequent element.
Let's modify our template to show a message when tasks.length is 0.
<ul v-if="tasks.length > 0">
<li v-for="task in tasks" :key="task.id" :class="{ done: task.done }">
<!-- ... our li content ... -->
</li>
</ul>
<p v-else>
No tasks yet! Add one above.
</p>
Now, if you remove all the tasks from the initial tasks array in your script and run the app, you'll see the "No tasks yet!" message. As soon as you add a task, the message will disappear and the <ul> will be rendered.
For a final review of all these concepts in a single, well-structured document, refer to the official Vue documentation.
The official Vue docs provide the definitive reference for each built-in directive. This is an excellent resource to bookmark for future reference.
Skim through the sections for v-if, v-else, v-for, v-on, and v-bind. You don't need to memorize everything, but pay attention to the different shorthands (@ for v-on, : for v-bind) and the list of modifiers for v-on.
Conclusion
You've done great work today! You've learned the four most essential Vue directives that form the backbone of almost every component you'll ever build.
Key Takeaways:
v-for: Renders a list of elements by iterating over an array or object. Always use a unique:key.v-on(or@): Listens for DOM events (like@click) to trigger methods in your script. Modifiers like.preventadd extra behavior.v-bind(or:): Dynamically binds HTML attributes to your component's data. Incredibly useful for dynamic classes (:class), styles, and other attributes.v-if/v-else: Conditionally renders elements by adding or removing them from the DOM based on a data property.v-model: Provides convenient two-way data binding for form inputs, simplifying state synchronization.
With these tools, you are now fully equipped to tackle your first Vue mini-project. In the next lesson, you will build a complete to-do list application, applying everything you've learned about reactive state and directives to create a functional and interactive UI.