Starting Your First Vue Project
In this chapter, you will learn about the key concepts and benefits of Vue.js (Vue), how to set up the project architecture using the terminal (or command line), and how to create a simple Vue component with local data following the component fundamentals.
This chapter will cover the following topics:
- Understanding Vue as a framework
- Setting up a Vite-powered Vue application
- Exploring
data
properties as a local state - Writing components with
<
script setup>
- Understanding Vue directives
- Enabling two-way binding using
v-model
- Understanding data iteration with
v-for
- Exploring methods
- Understanding component lifecycle hooks
- Styling components
- Understanding CSS modules
By the end of this chapter, you will be able to describe the fundamentals of Vue lifecycle hooks and expressions and use various styling approaches and HTML syntax flavors to control the HTML template competently.
Technical requirements
The Node version has to be below v20 (preferable Yarn 1.22 and Node version above 16 and up to 19.x, and npm up to version 9.x.
The complete code for this chapter is available on GitHub at: https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01
Understanding Vue as a framework
Developers in the industry must resolve frontend development problems quickly with minimal impact on existing workflows or backend architecture. In many cases, developers tend to overlook the UI until the end of a project, which can happen because of a lack of resources, ever-evolving product requirements, or the existing attitude that the frontend is the easy bit.
However, companies such as Apple and Google have proven that thinking through the design of the frontend is key to a solid product or platform that will excite and engage users, leading to a higher return on investment and a more successful business.
If you know Vue, you may have also come across other frontend frameworks that, at face value, solve the same problems, such as Ember, Angular, or React. At a surface level, they attempt to make reactive frontend development more reliable and introduce patterns that make it easier. However, there are significant differences in how a Vue project might play out compared to an Angular or React project. Let’s investigate them.
Angular versus Vue
Angular is a Model-View-ViewModel (MVVM) framework built by Google and has built-in support for TypeScript. The Angular ecosystem includes Ahead-of-Time (AoT) rendering, a router, and a CLI tool. However, it fails to deliver a simplified system for global state management; developers would need to learn how to use Flux or adopt NgRx.
Vue takes Angular’s core robustness and provides a better development experience by removing the restriction of an enforced code style for developers. Vue also simplifies common Angular patterns, such as HTML directives, and eliminates a variety of Angular’s project structures, such as injectables, components, pipes, modules, and so on. From Vue 3.0 onward, it provides excellent support for TypeScript and typing without the drawbacks of Angular-enforced coding styles.
Vue is more flexible, developer-friendly, efficient, and straightforward to set up and learn to use than Angular in many cases.
Next, let’s look at how Vue and React differ.
React versus Vue
First released in 2013 and backed by Meta (previously known as Facebook), React rapidly gained popularity in the developer community. React introduces the JSX pattern to write HTML syntax directly with JavaScript. With JSX, React increases the amount that new developers are required to learn about both JavaScript and component-based architecture.
Both React and Vue share the same component-driven development approach, allowing developers to build applications in a modular way. Each component contains its functionalities and lifecycle. Vue takes these core concepts of modular coding and offers flexibility to developers in choosing which approach to use to write their components: JSX or the traditional style, in which HTML, CSS, and JavaScript are separated.
Vue uses the Single-File Component (SFC) approach to leverage this modular structure into a single file while keeping the separation readable and understandable for developers.
Advantages of using Vue for your project
Vue has a gentler learning curve and a vibrant ecosystem. This gentle learning curve helps reduce overhead and cost for any team onboarding developers to a new Vue project.
One key benefit of Vue is its approachability for both new and veteran developers:
- Out of the box, developers can use a well-optimized and performant framework on which to build scalable, dynamic frontend applications.
- The SFC format pattern offers a modular and flexible blueprint that provides an enjoyable experience to developers. SFCs allow Vue to be genuinely versatile. You can implement basic functionalities and incrementally adopt pieces of a static site into Vue rather than overhaul your entire website.
As powerful as Redux and NgRx, Vuex (and lately Pinia) proves to be an outstanding official global state management tool that is flexible enough to meet most development needs.
Thanks to its stable performance; well-defined tools such as Vue Router, Pinia, Vuex, and so on; and a supportive community, developers can save time and money by choosing Vue for their development stack.
The following section explores the essential Vue architecture before deep-diving into the SFC pattern and template syntax.
Working with Vue
To learn about the Vue architecture, we will start by importing the Vue package into our coding playground. One straightforward way is to import the Vue package through the official Content Distribution Network (CDN). We can do so by creating an index.html
file and adding a <script>
tag to load the Vue CDN within the <head>
section of the HTML template, as demonstrated in the following code block:
<!DOCTYPE html> <html> <head> <title>Vue.js project with CDN</title> <script src="https://unpkg.com/vue@3"></script> </head> </html>
The browser will also load the Vue package using the CDN defined in the script
tag when loading the page. Once completed, you can utilize the Vue functions and start writing Vue code.
But first, let’s look at the Vue instance.
Understanding the Vue instance
In general, each Vue application consists of only one root Vue instance, which can be created using the Vue.createApp
method:
const vm = Vue.createApp({ // options })
The Vue
class constructor accepts an options
object for the configurations and behavior of components. We call this approach Options API and we can use it for all corresponding Vue components. However, all of them are considered nested Vue instances, with their own options and properties.
Note
vm
is a term commonly used to refer to a View Model, which is an abstraction of the view that describes the state of the data in the model. Binding a Vue instance to vm
helps you to keep track of your Vue instance in a block of code.
For the Vue engine to render the application instance, in our index.html
file, we declare an <div>
element within the <body>
tag using a unique class name, ID, or data attribute as the main entry point for the application accordingly:
<body> <div id="vue-app"></div> <script> const vm = Vue.createApp({ //Options }) </script> </body>
To render the Vue application in the browser, we need to trigger vm.mount()
to mount the root component to the targeted HTML element using a unique selector. In this example, it is an id
with a value of vue-app
:
<body> <div id="vue-app"></div> <script> const vm = Vue.createApp({ //Options }) vm.mount('#vue-app') </script> </body>
Now, you bind the <div>
element with id="vue-app"
to the new Vue instance.
Next, let’s define text with a value of "Start using Vue.js today!"
and add it as a property of the return
value for the data
method in the application options:
const vm = Vue.createApp({ data() { return { text: 'Start using Vue.js today!' } } })
In the preceding code example, data
is a function that returns an Object instance containing the local state (or local variables) of a component. We will discuss this further in an upcoming section of this chapter.
To render the content of text
to the DOM, we use Vue template syntax, represented by double curly braces ({{}}
) wrapped around the reactive content. In this case, we use {{ text }}
, as shown in the following code:
<div id="vue-app">{{ text }}</div>
The Vue engine will replace the data property labeled text
and the curly brace placeholder with the Start using Vue.js
today!
string.
The output of the preceding code will be as follows:

Figure 1.1 – Displaying “Start using Vue.js today!” using a local data property
In the <head>
tag, we can also use the DOM API to construct a Vue application instance and bound it to our target element (with the ID selector as #vue-app
):
<head> <title>Vue.js CDN</title> <script src="https://unpkg.com/vue@3"></script> <script> document.addEventListener('DOMContentLoaded', function () { Vue.createApp({ data(){ return { text: "Start using Vue.js today!" } } }).mount('#vue-app') }) </script> </head> <body> <div id="vue-app">{{text}}</div> </body>
The output is the same for both approaches. However, we strongly recommend not using DOMContentLoaded
.
While working with a CDN is very portable, we recommend using package managers as the installation method for Vue. From Vue 3 and above, Vue projects use Vite (or Vite.js) to initialize and bundle the code. You can access it here: https://vuejs.org/guide/quick-start.html#creating-a-vue-application.
Using a bundling management tool is very helpful for managing other third-party libraries and building an optimized code package for production. In the next section, we will explore a package-controlled example.
Setting up a Vite-powered Vue application
A Vue project is structured similarly to a lot of modern node-based apps and contains the following:
- A
package.json
file - A
node_modules
folder in the root of your project - Various other configuration files are usually contained at the root level, such as
vite.config.js
and.eslintrc.js
, since they will generally have an effect across your whole project.
The following screenshot displays a default Vue app folder structure:

Figure 1.2 – Default Vue application folder structure
By default, there is an index.html
file at the root level that serves as a placeholder for loading the Vue application. You can modify this file to include header
and footer
scripts, such as Google Fonts or third-party JavaScript libraries that are not included as a part of your bundle.
The Vue project structure follows a pattern where you manage most of your source code within the /src
directory. You can subdivide your Vue files into various folders, for example, using a components
folder to store reusable Vue components. By default, Vite will create assets
and components
folders to code-split the default files. For beginners, it is good to follow this pattern until you get more comfortable:

Figure 1.3 – Default Vue application src folder structure
The public
folder is a special directory containing files that need to be transferred directly to the output location. The following screenshot displays how this folder will look:

Figure 1.4 – Default Vue application public folder
At this point, you should be somewhat familiar with how a Vue project structure looks. Next, we discuss Vue’s unique pattern – the SFC architecture.
Vue’s SFC architecture
Components are the building blocks of most modern frameworks. In general, splitting your code into component-specific chunks ensures code readability and facilitates the Don’t Repeat Yourself (DRY) principle. Vue’s SFC pattern follows this approach closely.
The SFC architecture centralizes the responsibility of both appearance and behavior into a single file, thus simplifying the architecture of your project. You now can refer to your HTML, CSS, and JavaScript logic without switching files. Your default .vue
file structure will be as follows:

Figure 1.5 – Default .vue file structure
A general good practice is to ensure your components
file doesn’t contain more than 500 lines of code. If you encounter this situation, it’s recommended to split them into smaller reusable components. For example, in the header of your application, you may have a logo element that is reused on other pages. You would create a component such as logo.vue
:
// logo.vue <template> <img src="myLogo.png" /> </template>
In header.vue
, you import the logo
component into the script
section and then include it as a nested component of the header
component. You can achieve this by declaring it as a property of the components
field:
// header.vue <script> import logo from 'components/logo.vue' export default { components: { logo } } </script>
In the template
section, you can use the logo as a normal HTML element, as shown here:
<template> <header> <a href="mywebsite.com"> <logo /> </a> </header> </template>
The output will be a header with the logo image rendered – and you can reuse the logo
component in any other component when needed.
Very soon, you will have lots of these semantically structured files, which use small chunks of a reusable syntax that your team can implement across various application areas.
In the next exercise, you will practice creating your first Vue component and displaying it in another component.
Exercise 1.01 – building your first component
We are going to build our first component, Exercise1.01
, inside of a Vue project and import it to use it in the App.vue
component using ES6 module syntax.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Front-End-Development-Projects-with-Vue.js/tree/v2-edition/Chapter01/Exercise1.01.
Note
Your app will hot-reload when you save new changes, so you can see them instantly.
To get started, execute the following steps:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.01
folder by using the following commands in order:> cd Chapter01/Exercise1.01/
> yarn
- Run the application using the following command:
yarn dev
- Go to
https://localhost:3000
. - Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Open the
src/App.vue
file, delete everything in that file, and save. - In your browser, everything should be a blank, clean state to start working from.
- The three primary components that make up a single-file component are the
<template>
,<script>
, and<style>
blocks. Add the following code blocks as our scaffolding for a Vue component:/** src/App.vue **/
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>
- Create another file in the
components
folder calledExercise1-01.vue
and repeat the same step for scaffolding the Vue component:// src/components/Exercise1-01.vue
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>
- Within our
Exercise1-01.vue
component, compose a set of<div>
tags, with an<h1>
element and a heading inside the<
template>
tags:<template>
<div>
<h1>My first component!</h1>
</div>
</template>
- Inside the
<style>
block, add some styling as follows:<style>
h1 {
font-family: 'Avenir', Helvetica, Arial,
sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- Import our component into
App.vue
by using the ES6import
method and defining the component inside thecomponents
object in the<script>
block. We can now reference this component inside the HTML by using its name in camelCase or kebab-case (both will work):<template>
<Exercise />
</template>
<script>
import Exercise from './components/Exercise1-01'
export default {
components: {
Exercise,
}
}
</script>
When you press Ctrl + S (or Cmd + S on macOS), https://localhost:3000
should reload and look amazing:

Figure 1.6 – localhost output for Exercise 1.01
In this exercise, we saw how to structure Vue components using template tags, and scaffold basic Vue components using Vetur. We also created a new Vue component and reuse it in App.vue
using ES6 syntax and property field components
.
In the next section, we will gain an understanding of how to define the local state data of a component using data
properties.
Exploring data properties as a local state
One of the most used terms and reactive elements used when constructing Vue components is data
properties. These manifest themselves within the data()
function of a Vue instance:
<script> export default { data() { return { color: 'red' } } } </script>
You can use the data()
function to create a local data object to essentially store any information you want to use within your Vue templates. This local object is bound to the component and we call it the local state data of the component. When any property of this local object is updated or changed, it will reactively update in the corresponding template.
Once we have defined our local data, we need to bind it to the template
section to display its values in the UI, which is called data interpolation.
Interpolation is the insertion of something of a different nature into something else. In the Vue context, this is where you would use mustache syntax (double curly braces) to define an area in which you can inject data into a component’s HTML template.
Consider the following example:
<template> <span> {{ color }}</span> </template > <script> export default { data() { return { color: 'red' } } } </script>
The data
property of red
is bound to Vue.js reactive data and will update during runtime, depending on state changes between the UI and its data.
At this point, we should look at how to define and bind local data in the most classical Vue way. With Vue 3.0, we enjoy a shorter and simpler approach to writing and importing components. Let’s explore it next.
Writing components with script setup
Starting from Vue 3.0, Vue introduces a new syntactic sugar setup
attribute for the <script>
tag. This attribute allows you to write code using Composition API (which we will discuss further in Chapter 5, The Composition API) in SFCs and shorten the amount of code needed for writing simple components.
The code block residing within the <script setup>
tag will then be compiled into a render()
function before being deployed to the browser, providing better runtime performance.
To start using this syntax, we take the following example code:
// header.vue <script> import logo from 'components/logo.vue' export default { components: { logo } } </script>
Then, we replace <script>
with <script setup>
, and remove all the code blocks of export default…
. The example code now becomes as follows:
// header.vue <script setup> import logo from 'components/logo.vue' </script>
In <template>
, we use logo
as usual:
<template> <header> <a href="mywebsite.com"> <logo /> </a> </header> </template>
To define and use local data, instead of using data()
, we can declare regular variables as local data and functions as local methods for that component directly. For example, to declare and render a local data property of color
, we use the following code:
<script setup> const color = 'red'; </script> <template> <div>{{color}}</div> </template>
The preceding code outputs the same result as the example in the previous section –red
.
As mentioned at the beginning of this section, <script setup>
is the most useful when you need to use Composition API within SFCs. Still, we can always take advantage of its simplicity for simple components.
Note
From this point onward, we will combine both approaches and use <script setup>
whenever possible.
In the following exercise, we will go into more detail about how to use interpolation and data.
Exercise 1.02 – interpolation with conditionals
When you want to output data into your template or make elements on a page reactive, interpolate data into the template by using curly braces. Vue can understand and replace that placeholder with data.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.02:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.02
folder by using the following commands in order:> cd Chapter01/Exercise1.02/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-02.vue
in thesrc/components
directory. - Inside the
Exercise1-02.vue
component, let’s add data within the<script setup>
tags by adding a function calleddata()
, and return a key calledtitle
with your heading string as the value:<script>
export default {
data() {
return {
title: 'My first component!',
}
},
}
</script>
- Reference
title
by replacing your<h1>
text with the interpolated value of{{
title }}
:<template>
<div>
<h1>{{ title }}</h1>
</div>
</template>
When you save this document, the data title will now appear inside your h1
tag.
- In Vue, interpolation will resolve any JavaScript that’s inside curly braces. For example, you can transform the text inside your curly braces using the
toUpperCase()
method:<template>
<div>
<h1>{{ title.toUpperCase() }}</h1>
</div>
</template>
- Go to https://localhost
:3000
. You should see an output like the following screenshot:

Figure 1.7 – Display of an uppercase title
- Interpolation can also handle conditional logic. Inside the data object, add a Boolean key-value pair,
isUppercase: false
:<template>
<div>
<h1>{{ isUppercase ? title.toUpperCase() : title }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
title: 'My first component!',
isUppercase: false,
}
},
}
</script>
The preceding code will generate the following output:

Figure 1.8 – Exercise 1.02 output after including the inline conditional statement
- Add this condition to the curly braces and when you save, you should see the title in sentence case. Play around with this value by changing
isUppercase
totrue
:<script>
export default {
data() {
return {
title: 'My first component!',
isUppercase: true,
}
},
}
</script>
The following screenshot displays the final output generated upon running the preceding code:

Figure 1.9 – Displaying the uppercase title
- Now, let’s replace
<script>
with<script setup>
and move all the local data declared within thedata()
function to its own variable names respectively, such astitle
andisUpperCase
, as shown here:<script setup>
const title ='My first component!';
const isUppercase = true;
</script>
- The output should remain the same as in Figure 1.9.
In this exercise, we were able to apply inline conditions within the interpolated tags ({{}}
) by using a Boolean variable. The feature allows us to modify what data to display without overly complicated situations, which can be helpful in certain use cases. We also learned how to write a more concise version of the component using <script setup>
in the end.
Since we are now familiar with using interpolation to bind local data, we will move on to our next topic – how to attach data and methods to HTML element events and attributes using Vue attributes.
Understanding Vue directives
All Vue-based directives start with a v-*
prefix as a Vue-specific attribute:
v-text
: Thev-text
directive has the same reactivity as with interpolation. Interpolation with{{ }}
is more performant than thev-text
directive. However, you may find yourself in situations where you have pre-rendered text from a server and want to override it once your Vue application has finished loading. For example, you can pre-define a static placeholder text while waiting for the Vue engine to eventually replace it with the dynamic value received fromv-text
, as shown in the following code block:<template>
<div v-text="msg">My placeholder</div>
</template>
<script setup>
const msg = "My message"
</script>
v-once
: When used, it indicates the starting point of static content. The Vue engine will render the component with this attribute and its children exactly once. It also ignores all data updates for this component or element after the initial render. This attribute is handy for scenarios with no reactivity needed for certain parts. You can combinev-once
withv-text
, interpolation, and any Vue directive.V-html
: Vue will parse the value passed to this directive and render your text data as a valid HTML code into the target element. We don’t recommend using this directive, especially on the client side, due to its performance impact and the potential security leak. Thescript
tag can be embedded and triggered using this directive.v-bind
: This directive is one of the most popular Vue features. You can use this directive to enable one-way binding for a data variable or an expression to an HTML attribute, as shown in the following example:<template>
<img v-bind:src="logo" />
</template>
<script setup>
const logo = '../assets/logo.png';
</script>
The preceding code demonstrates how to bind the logo
data variable to image’s src
. The img
component now takes the source value from the logo
variable and renders the image accordingly.
You can also use it to pass a local data variable as props to another component. A shorter way is using the :attr
syntax instead of v-bind:attr
. Take the preceding example, for instance. We can rewrite the template as follows:
<template> <img :src="logo" /> </template>
v-if
: This is a powerful directive you can use to conditionally control how elements render inside a component. This directive operates like theif…else
andif…else if…
conditions. It comes with supporting directives, such asv-else
, standing for theelse
case, andv-else-if
, standing for theelse if
case. For example, we want to render different text whencount
is2
,4
, and6
. The following code will demonstrate how to do so:<template>
<div v-if="count === 2">Two</div>
<div v-else-if="count === 4">Four</div>
<div v-else-if="count === 6">Six</div>
<div v-else>Others</div>
</template>
v-show
: You can also control the visible state of HTML elements by usingv-show
. Unlikev-if
, withv-show
, the Vue engine still mounts the element to the DOM tree but hides it using thedisplay: none
CSS style. You can still see the content of the hidden element visible in the DOM tree upon inspecting it, but it is not visible on the UI to end users. This directive does not work withv-else
orv-else-if
. Ifv-show
results in atrue
Boolean, it will leave the DOM element as is. If it resolves asfalse
, it will apply thedisplay: none
style to the element.v-for
: We use thev-for
directive to accomplish the goal of list rendering based on a data source. The data source is an iterative data collection, such as anarray
orobject
. We will dive deeper into different use cases for this directive in a separate section within this chapter.
We have gone over the most common directives in Vue. Let’s review and experiment with how to use these directives with the following exercise.
Exercise 1.03 – exploring basic directives (v-text, v-once, v-html, v-bind, v-if, v-show)
More complicated components will use multiple directives to achieve the desired outcome. In this exercise, we will construct a component that uses several directives to bind, manipulate, and output data to a template view.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.03.
Let’s start the exercise by performing the following steps:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.03
folder by using the following commands in order:> cd Chapter01/Exercise1.03/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-03.vue
in thesrc/components
directory. - Inside
Exercise1-03.vue
, compose the following code to display thetext
content:<template>
<div>
<h1>{{ text }}</h1>
</div>
</template>
<script setup>
const text = 'Directive text';
</script>
- Replace the
{{}}
interpolation with thev-text
attribute. The output should not change:<template>
<div>
<h1 v-text="text">Loading...</h1>
</div>
</template>
Figure 1.10 displays the output of the preceding code:

Figure 1.10 – Same output for v-text and the interpolation method
- Add the
v-once
directive to the same element. This will force this DOM element to only load thev-text
data once:<template>
<div>
<h1 v-once v-text="text">Loading...</h1>
</div>
</template>
- Underneath the
h1
element, include a newh2
element that uses thev-html
attribute. Add a new local data calledhtml
that contains a string with HTML formatting in it, as shown in the following code block:<template>
<div>
<h1 v-once v-text="text">Loading...</h1>
<h2 v-html="html" />
</div>
</template>
<script setup>
const text = 'Directive text';
const html = 'Stylise</br>HTML in<br/><b>your data</b>'
</script>
Running the preceding code will generate an output as follows:

Figure 1.11 – Rendering HTML elements from a string using v-html
- Add a new local
link
object that contains a bunch of information such as the URL, target, title, and tab index. Inside the template, add a new anchor HTML element and bind thelink
object to the HTML element using thev-bind
short syntax – for example,:href="link.url"
:<template>
<div>
<h1 v-once v-text="text">Loading...</h1>
<h2 v-html="html" />
<a
:href="link.url"
:target="link.target"
:tabindex="link.tabindex"
>{{ link.title }}</a>
</div>
</template>
<script setup>
const text = 'Directive text';
const html = 'Stylise</br>HTML in<br/><b>your data</b>'
const link = {
title: "Go to Google",
url: https://google.com,
tabindex: 1,
target: '_blank',
};
</script>
The following screenshot displays the output:

Figure 1.12 – Output on binding the reactive data from the Vue instance to any HTML attribute
- Apply
v-if="false"
to theh1
element,v-else-if="false"
toh2
, andv-else
to thea
tag like this:<template>
<div>
<h1 v-if="false" v-once v-text="text">Loading...
</h1>
<h2 v-html="html" v-else-if="false" />
<a
v-else
:href="link.url"
:target="link.target"
:tabindex="link.tabindex"
>{{ link.title }}</a>
</div>
</template>
You should only see the <a>
tag on the page since we have set the main conditional statements to false
.
The v-else
condition will display the following:

Figure 1.13 – false v-if statements hiding the whole HTML element from the DOM
- Change the template to use
v-show
instead of thev-if
statements, removev-else
from the<a>
element, and change the value ofv-show
inh1
totrue
:<template>
<div>
<h1 v-show="true" v-once v-text="text">Loading...
</h1>
<h2 v-html="html" v-show="false" />
<a
:href="link.url"
:target="link.target"
:tabindex="link.tabindex"
>{{ link.title }}</a>
</div>
</template>
The output of the preceding code will be as follows:

Figure 1.14 – Changing v-show to true will display the main directive text
When you open the Elements
tab of your browser Devtools, you should be able to observe that the h2
display state is set to none
as follows:

Figure 1.15 – h2 has “display: none” for the false condition
In this exercise, we learned about the core Vue directives to control, bind, show, and hide HTML template elements without requiring any JavaScript outside of adding new data objects to your local state.
In the next section, we will learn how to achieve two-way binding with the help of Vue’s v-model
.
Enabling two-way binding using v-model
Vue achieves two-way data binding by creating a dedicated directive that watches a data property within your Vue component. The v-model
directive triggers data updates when the target data property is modified on the UI. This directive is usually useful for HTML form elements that need to both display the data and modify it reactively – for example, input
, textarea
, radio buttons, and so on.
We can enable two-way binding by adding the v-model
directive to the target element and binding it to our desired data props:
<template> <input v-model="name" /> </template> <script> export default { data() { return { name: '' } } } </script>
In Figure 1.16, the output generated by running the preceding code will be as follows:

Figure 1.16 – Output for the v-model example
Note
Binding a huge amount of data using v-model
can affect the performance of your application. Consider your UI and split the data into different Vue components or views. Vue data in the local state is not immutable and can be redefined anywhere in the template.
In the next exercise, we are going to build a component using Vue’s two-way data binding and experiment with what it means to bind data in two ways.
Exercise 1.04 – experimenting with two-way binding using v-model
The context for this type of data model is usually forms or wherever you expect both input and output data. By the end of the exercise, we should be able to utilize the v-model
attribute in the context of a form.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.04.
Let’s start the exercise by performing the following steps:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise 1.04
folder by using the following commands in order:> cd Chapter01/Exercise 1.04/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in your VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-04.vue
in thesrc/components
directory. - Inside
Exercise1-04.vue
, start by composing an HTMLlabel
and bind aninput
element to thename
data prop usingv-model
inside thetemplate
area:<div class="form">
<label>
Name
<input type="text" v-model="name" />
</label>
</div>
- Complete the binding of the
text
input by returning a reactive data prop calledname
in the<
script>
tag:<script>
export default {
data() {
return {
name: '',
}
},
}
</script>
- Next, compose a
label
and selectable HTMLselect
tied to thelanguage
data prop usingv-model
inside of thetemplate
area:<div class="form">
<label>
Name
<input type="text" v-model="name" />
</label>
<label>
Preferred JavaScript style
<select name="language" v-model="language">
<option value="Javascript">JavaScript
</option>
<option value="TypeScript">TypeScript
</option>
<option value="CoffeeScript">CoffeeScript
</option>
<option value="Dart">Dart</option>
</select>
</label>
</div>
- Finish binding the
select
input by returning a reactive data prop calledlanguage
in the<
script>
tag:<script>
export default {
data() {
return {
name: '',
language: '',
}
},
}
</script>
- Below the
form
fields, output the name and language inside of an unordered list structure (<ul>
and<li>
) by using curly braces such as{{
name }}
:
Your code should look as follows:
<template> <section> <div class="form"> <label> Name <input type="text" v-model="name" /> </label> <label> Preferred JavaScript style <select name="language" v-model="language"> <option value="JavaScript">JavaScript </option> <option value="TypeScript">TypeScript </option> <option value="CoffeeScript">CoffeeScript </option> <option value="Dart">Dart</option> </select> </label> </div> <ul class="overview"> <li><strong>Overview</strong></li> <li>Name: {{ name }}</li> <li>Preference: {{ language }}</li> </ul> </section> </template>
- Add styling inside the
<style>
tag at the bottom of the component:<style>
.form {
display: flex;
justify-content: space-evenly;
max-width: 800px;
padding: 40px 20px;
border-radius: 10px;
margin: 0 auto;
background: #ececec;
}
.overview {
display: flex;
flex-direction: column;
justify-content: space-evenly;
max-width: 300px;
margin: 40px auto;
padding: 40px 20px;
border-radius: 10px;
border: 1px solid #ececec;
}
.overview > li {
list-style: none;
}
.overview > li + li {
margin-top: 20px;
}
</style>
- Go to
https://localhost:3000
. Your output should look as follows:

Figure 1.17 – Displaying the final form after the data is updated
When you update the data in the form, it should also update the overview area synchronously.
In this exercise, we used the v-model
directive to bind the name and JavaScript-style drop-down selection to our local state’s data. When you modify the data, it will reactively update the DOM elements to which we output its value.
Next, we will discuss our v-for
directive further and different approaches to handling iterative data collection in Vue.
Understanding data iteration with v-for
To loop over HTML elements in Vue, you use the v-for
loop directive directly on the target elements. When Vue renders the component, it will iterate the target to use and render the data being parsed into the directive, with the same concept as a normal JavaScript for
loop.
Basic iteration using v-for
The basic syntax of v-for
is as follows:
v-for="(item, index) in items" :key="index"
The preceding syntax example indicates that we are iterating through a list of items
. We have access to a single item
and its appearance index
in the list in each iteration. :key
is a required attribute, acting as the unique identifier of each iterating element rendered for the Vue engine to keep track.
When the key
or item
content changes, either programmatically or due to user interactions, the Vue engine triggers an update of the changed item on the UI. If you have multiple loops in one component, you should randomize the key
attribute with extra characters or context-related strings to avoid key
duplication conflicts.
There are various use cases for this direction. One straightforward use case is to perform anonymous loops, in which you can define a number, X, as a symbolic list, and the loop will iterate that X times. This can be handy in situations in which you strictly control the number of iterations you want or render some placeholder content.
In the following example, we see an anonymous loop in which the total iterations are 2
and we define key
with a loop-1
prefix:
<template> <div v-for="n in 2" :key="'loop-1-' + n"> {{ n }} </div> <template>
You can also use template literals (with ``
backticks) to compute strings without +
:
<template> <div v-for="n in 5" :key="`loop-2-${n}`"> {{ n }} </div> <template>
The output of the preceding code in both approaches should look as follows

Figure 1.18 – Output of anonymous loops example
Now that we have covered how to handle basic loops by using v-for
, we will utilize this function in the next exercise.
Exercise 1.05 – using v-for to iterate through an array of strings
In this exercise, we are going to create an anonymous loop using Vue’s v-for
directive. This will be familiar to those who have used for
or forEach
loops in JavaScript before.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.05.
Perform the following steps to complete the exercise:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.05
folder by using the following commands in order:> cd Chapter01/Exercise1.05/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-05.vue
in thesrc/components
directory. - Inside
Exercise1-05.vue
, we compose a new component with an<h1>
element to render the static title ofLooping through arrays
, and an<ul>
element containing an empty<
li>
tag:<template>
<h1>Looping through arrays</h1>
<ul>
<li></li>
</ul>
</template>
- In the
script
section, let’s add asetup
attribute to thescript
tag. Then, let’s declare an array ofinterests
containing some strings as follows:<script setup>
const interests = ['TV', 'Games', 'Sports']
</script>
- Now, let’s go back to the
template
section and add thev-for
directive on the<li>
tag to iterate throughinterests
. For each iteration, we get a combination of(item, index)
from the interests, in whichitem
outputs the string of the array, andindex
is the loop index. We map thekey
attribute toindex
, and display the value ofitem
as shown in the following code block:<template>
<h1>Looping through arrays</h1>
<ul>
<li v-for="(item, index) in interests"
:key="index">{{ item }}</li>
</ul>
</template>
- Go to
https://localhost:3000
. The following output is as follows:

Figure 1.19 – Output of iterating through an array of strings
In this exercise, we learned how to iterate through a specific array of strings, outputting the string value or index of an array. We also learned that the key attribute needs to be unique to avoid DOM conflicts and forces the DOM to re-render the component properly.
Next, let’s experiment with iterating a collection of objects.
Iterating through an array of objects
In most practical scenarios, we work with data as objects, especially when iterating through an array of objects. Vue makes it easy to control various data states through its directive syntax. Like iterating through an array of strings, the directive syntax remains the same:
v-for="(item, index) in items" :key="index"
The item
you receive is now an Object, with various properties. You can bind each property using what you have learned so far to display its value. For example, assume in item
, we will have id
, title
, description
, and another array, characteristics
, containing some strings. We can display the title
and description
information for each item
like so:
<template> <ul> <li v-for="(item, index) in items" :key="item.id"> <h2>{{ item.title }}</h2> <span>{{ item.description }}</span> </li> </ul> </template>
Note here we don’t use an index
as the key
; instead, we use id
as the unique identifier for key
. It is considered a more secure approach to use id
or any other unique identifier and we also don’t need to include index
in the syntax in this case since we don’t use it.
Since characteristics
is an array, we display its values by using a v-for
directive again for characteristics
. You don’t have to use the same name, item
, that the syntax example shows. Instead, you can give it a different name depending on how you want your variable to be.
In the following example, we use str
for each element in the item.characteristics
array:
<template> <ul> <li v-for="item in items" :key="item.id"> <h2>{{ item.title }}</h2> <span>{{ item.description }}</span> <ul> <li v-for="(str, index) in item.characteristics" :key="index"> <span>{{ str }}</span> </li> </ul> </li> </ul> </template>
And in the script
section, we define items
as follows:
<script setup> const items = [{ id: 1, title: "Item 1", description: "About item 1", characteristics: ["Summer", "Winter", "Spring", "Autumn"] }, { id: 2, title: 'Item 2", description: 'About item 2", characteristics: ["North", "West", "East", "South"] }] </script>
The preceding code will output as shown in Figure 1.20:

Figure 1.20 – Output of iterating through an array of object items
Understanding how to loop through collections of objects with v-for
is essential and useful for handling data, especially with external data. In the next exercise, you will combine v-for
and v-if
to display a list of objects conditionally.
Exercise 1.06 – using v-for to iterate through an array of objects and using their properties in v-if conditions
In this exercise, we will be controlling a Vue data array and iterating through the objects inside of it.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.06.
Let’s start the exercise by performing the following steps:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.06
folder by using the following commands in order:> cd Chapter01/Exercise1.06/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-06.vue
in thesrc/components
directory. - Inside
Exercise1-06.vue
, create an array of data objects,interests
, as local data. Each interest contains atitle
string and afavorites
array of strings:<script setup>
const interests = [
{
title: "TV",
favorites: ["Designated Survivor",
"Spongebob"],
},
{
title: "Games",
favorites: ["CS:GO"],
},
{
title: "Sports",
favorites: [],
},
];
</script>
- In
template
, we loop overinterests
and display thetitle
for eachitem
in theinterests
array:<template>
<div>
<h1>Looping through array of objects</h1>
<ul>
<li v-for="(item, n) in interests" :key="n">
{{ item.title }}
</li>
</ul>
</div>
</template>
- Go to
https://localhost:3000
and the output of the preceding code will be as follows:

Figure 1.21 – You should now see a list of titles in the browser
- Let’s create a second
v-for
loop to iterate through afavorites
list for eachitem
. Note that we use different names –fav
andm
– for our nested loop:<template>
<div>
<h1>Looping through array of objects</h1>
<ul>
<li v-for="(item, n) in interests" :key="n">
{{ item.title }}
<ol>
<li v-for="(fav, m) in item.favorites"
:key="m">
{{ fav }}</li>
</ol>
</li>
</ul>
</div>
</template>
Figure 1.22 displays an output where looping is performed through an array of objects:

Figure 1.22 – Nested ordered list detailing your favorites
- When inspecting the DOM elements (press Ctrl + F12 or open Developer Tools), you can see there are some empty elements as in Figure 1.23. This is because the Vue engine still renders the
<ol>
element even thoughfavorites
is an empty array:

Figure 1.23 – Displaying empty DOM elements in your virtual DOM
- Now, we need to hide that empty
<ol>
element after applying it. We will check whether thefavorites
array is empty (length > 0
) and then display the ordered list HTML element. Let’s add av-if
directive to<ol>
with theitem.favorites.length >
0
condition:<ol v-if="item.favorites.length > 0">
<li v-for="(fav, m) in item.favorites" :key="m">
{{ fav }}
</li>
</ol>
This won’t make a difference to the visuals of your page, but when you inspect the DOM tree in your browser, you’ll notice an HTML comment in dev mode that allows you to understand where a v-if
statement might be false
. When you build for production, these HTML comments won’t be visible in your DOM tree:

Figure 1.24 – Output displaying no HTML comment in production builds
In this exercise, we have iterated through complex arrays of objects, outputting the nested keys for these objects and controlling the view state of DOM elements based on length conditions.
Next, let’s experiment with iterating through a keyed collection (or Object).
Iterating through a keyed collection (Object)
We can generally use v-for
for looping through any iterative data collection type. Object in JavaScript is a key-value data collection, and we can iterate through its properties using v-for
.
The syntax example is like the previous syntax example for arrays of objects and strings, with a tiny difference. Here, we change the naming convention from (item, index)
to (value, key)
, in which key
is the object’s property, and value
is that key
property’s value. Vue also exposes one more parameter – index
– to indicate that property’s appearance index in the target object. Thus, the syntax now becomes the following:
v-for="(value, key, index) in obj"
Here, obj
is our target object to iterate.
For example, assume we have the following object named course
, which contains a title, a description, and the name of the lecturer(s):
<script setup> const course = { title: 'Frontend development with Vue', description: 'Learn the awesome of Vue', lecturer: 'Maya and Raymond' } </script>
In our template, we iterate through the course
’s properties and output their value in the <index>.< key > : <value>
format as shown in the following code block:
<template> <ul> <li v-for="(value, key, index) in course" :key="key"> {{index}}. {{key}}: {{value}} </li> </ul> </template>
The output will be as shown in Figure 1.25:

Figure 1.25 – Iterating and displaying values of the course’s properties
Looping through the properties of an object is also a joint development practice. It is the same concept as winding through any keyed collection type, such as a hash-map (mapping according to key), lookup dictionary (it is also an object), and so on. Since the syntax stays consistent between both array and object iteration, it helps reduce the need for refactoring or data conversion.
Next, you will practice how to write basic looping for Object properties.
Exercise 1.07 – using v-for to loop through the properties of Object
In this exercise, we will be controlling a Vue data object and iterating through the properties inside of it.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.07.
Let’s start the exercise by performing the following steps:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.07
folder by using the following commands in order:> cd Chapter01/Exercise1.07/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-07.vue
in thesrc/components
directory. - Inside
Exercise1-07.vue
, let’s composeinformation
for the local data within<script setup>
as follows:<script setup>
const information = {
title: "My list component information",
subtitle: "Vue JS basics",
items: ["Looping", "Data", "Methods"],
}
</script>
- In the
<template>
section, we will loop throughinformation
and display the values of its properties:<template>
<div>
<div v-for="(value, key) in information"
:key="key">
{{key}}: {{ value }}
</div>
</div>
</template>
- Go to
https://localhost:3000
and the output will be as follows:

Figure 1.26 – Output using v-for on the information object
- Note that Vue renders the value for items, which is an array of strings, the same as how we declared using JavaScript. To render it in a better format, we use the built-in JavaScript
toString()
function to export all the elements’ values into a string with comma separation automatically:<template>
<div>
<div v-for="(value, key) in information"
:key="key">
{{key}}: {{ value.toString() }}
</div>
</div>
</template>
- The final output will render the list as follows:

Figure 1.27 – Output using v-for and toString() on values
Understanding iterations (or loops) is key to not only working with Vue but also with JavaScript in general. Now that we have covered how to handle loops by using the v-for
directive and the importance of the key
property for proper reactivity enhancement, we will explore how to use, write, and trigger methods in a component.
Exploring methods
In Vue 2.0, Vue defines component methods inside the methods
object as part of a Vue instance. You compose each component method as a normal JavaScript function. The Vue method is scoped to your Vue component and can be run from anywhere inside the component it belongs to. It also has access to the this
instance, which indicates the instance of the component:
<script> export default { methods: { myMethod() { console.log('my first method'); } } } </script>
From Vue 3.0 onward, with <script setup>
, as with local data, you can define a method as a regular function and it will work the same way as with the traditional approach. Hence, we can rewrite the preceding code as follows:
<script setup> const myMethod = () => { console.log('my first method'); } </script>
You then can bind the methods to HTML events of an element as its event listeners in the template
section. When binding events to HTML elements in Vue, you would use the @
symbol. For example, v-on:click
is equivalent to @click
, as shown in the following code block:
<template> <button id="click-me" v-on:click="myMethod">Click me </button> <button id="click-me" @click="myMethod">Click me shorter</button> </template>
Clicking on both buttons triggers the same myMethod()
method and generates the same result.
Let’s build a component with some methods.
Exercise 1.08 – triggering methods
In this exercise, we are going to build a component that uses Vue’s methods API. Consider how similarly these Vue methods can be written to your own named functions in JavaScript, as they behave in a very similar way. By the end of the exercise, we should be able to use methods and trigger them from the HTML template.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.08
We will build a list of different elements. For each element, we bind an onClick
event with a component
method, and alert users about the index of the clicked element by performing the following:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.08
folder by using the following commands in order:> cd Chapter01/Exercise1.08/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-08.vue
in thesrc/components
directory. - Inside
Exercise1-08.vue
, within the<script setup>
section, let’s define a method,triggerAlert
, that receives an index and displays an alert informing users which index has been clicked:<script setup>
const triggerAlert = (index) => {
alert(`${index} has been clicked`)
}
</script>
- In the
template
section, set up an anonymousv-for
loop on an HTML list and add abutton
element inside the list element. Set the loop to iterate5
times, and display theindex
value as each button’s label:<template>
<div>
<h1>Triggering Vue Methods</h1>
<ul>
<li v-for="index in 5":key="index">
<button>Trigger {{index}}</button>
</li>
</ul>
</div>
</template>
- Add the
@click
directive, referencing thetriggerAlert
method, and pass the value ofindex
as an argument:<template>
<div>
<h1>Triggering Vue Methods</h1>
<ul>
<li v-for="index in 5" :key="index">
<button @click="triggerAlert(index)">Trigger
{{ index }}</a>
</li>
</ul>
</div>
</template>
- Add a margin between each button for readability:
<style>
button {
margin: 10px;
}
</style>
- Your page should feature a list of buttons that when clicked, trigger an alert with a message that contains the button number you clicked, as follows:

Figure 1.28 – Outputting a list of triggers
The following prompt is displayed when a trigger is clicked:

Figure 1.29 – Displaying a browser alert with the index number in it
Note
While you can add an event listener to any HTML element, we suggest applying them to native HTML interactive elements such as anchor tags, form input, or buttons to help with browser accessibility.
At this point, you can utilize the Vue methods API to define and trigger methods from the HTML template, and parse arguments into each method dynamically. In the next exercise, we will explore how to return data with Vue methods within a Vue component.
Exercise 1.09 – returning data using Vue methods
Often, in a web application, we want elements to appear on the page depending on whether a condition is met or not. For instance, if our product is not in stock, our page should display the fact that it is out of stock.
So, let’s figure out how we conditionally render these elements depending on whether our product is in stock or not.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Front-End-Development-Projects-with-Vue.js/tree/v2-edition/Chapter01/Exercise1.09.
We will build a list of different elements and demonstrate adding different quantities to a cart. Then, we will display the updated cart’s total value in a currency format by performing the following:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.09
folder by using the following commands in order:> cd Chapter01/Exercise1.09/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in your VS Code (by using the
code .
command within the project directory), or your preferred IDE. - Create a new Vue component file named
Exercise1-09.vue
in thesrc/components
directory. - Inside
Exercise1-09.vue
, within the<script>
section, we set up two data objects,totalItems
andtotalCost
, which will be updated when a user clicks on our shop’s buttons:<script>
export default {
data(){
return {
totalCost: 0,
totalItems: 0
}
}
}
</script>
- In the
template
section, we display the value oftotalItems
andtotalCost
accordingly:<template>
<div>
<h1>Returning Methods</h1>
<div>Cart({{ totalItems }}) {{ totalCost }} </div>
</div>
</template>
- Within the
script
section, let’s create anaddToCart
method, which will updatetotalCost
andtotalItems
for the current component based on the received number,n
, by usingthis.totalCost
andthis.totalItems
:<script>
export default {
data() {
/*…*/
},
methods: {
addToCart(n) {
this.totalItems = this.totalItems + 1
this.totalCost = this.totalCost + n
},
},
}
</script>
- Let’s iterate through a random amount to create buttons for adding a quantity to the cart. The quantity is the button’s index. Then, we bind the
addToCart
method to each button, with its index as the function’s input argument:<template>
<div>
<h1>Returning Methods</h1>
<div>Cart({{ totalItems }}) {{ totalCost }} </div>
<ul>
<li v-for="n in 5" :key="n">
<button @click="addToCart(n)">Add {{ n }}
</button>
</li>
</ul>
</div>
</template>
- Add a
10px
margin to thebutton
element for readability:<style>
button {
margin: 10px;
}
</style>
- Go to
https://localhost:3000
and the output is as follows:

Figure 1.30 – Pressing any of the buttons will demonstrate the cart logic
When you click on the buttons, the totalItems
counter should increment by 1
, but totalCost
will increment by the n
value, which should demonstrate a normal cart functionality. For example, when clicking Add 2
, then Add 5
, the output will be as follows:

Figure 1.31 – Output displaying Returning Methods after increments of 2 and 5
- Now, let’s format
totalCost
. Create a method calledformatCurrency
, which accepts one argument. We will return the same value after giving it two decimal points and a$
symbol:<script>
export default {
data() {
/*…*/
},
methods: {
addToCart(n) { /*…*/},
formatCurrency(val) {
return `$${val.toFixed(2)}`
},
},
}
</script>
- To use this method in the template, add it to the interpolated curly braces and pass the value that was there as an argument inside the method instead:
<template>
<div>
<h1>Returning Methods</h1>
<div>Cart({{ totalItems }}) {{
formatCurrency(totalCost) }}
</div>
<ul>
<li v-for="n in 5" :key="n">
<button @click="addToCart(n)">Add {{
formatCurrency(n) }}</button>
</li>
</ul>
</div>
</template>
The following screenshot displays the output of the preceding code:

Figure 1.32 – All the values now are in currency format while retaining the cart counter
In this exercise, we were able to utilize Vue’s methods API to parse arguments into methods, return modified values, and use methods to update the local data state in a life-like scenario.
In the next section, we will explore a significant part of a component – the lifecycle and available component hooks in Vue.
Understanding component lifecycle hooks
The Vue component lifecycle events happen during a component’s lifecycle, from creation to deletion. They allow us to add callbacks and side effects at each stage of the component’s life when necessary.
Vue executes the events in order, as follows:
setup
: This event runs before all other hooks, includingbeforeCreate
. It doesn’t have access to this instance since the instance has not yet been created at this point. It is mainly for using Composition API and is treated in the same way Vue treatsscript setup
. We will discuss this event more in Chapter 5, The Composition API.beforeCreate
: This runs when your component has been initialized.data
has not been made reactive and events are not set up in your DOM.created
: You will be able to access reactive data and events, but the templates and DOM are not mounted or rendered. This hook is generally good to use when requesting asynchronous data from a server since you will more than likely want this information as early as possible before the virtual DOM is mounted.beforeMount
: A very uncommon hook, as it runs directly before the first render of your component and is not called Server-Side Rendering.mounted
: Mounting hooks are among the most common hooks you will use since they allow you to access your DOM elements so that non-Vue libraries can be integrated.beforeUpdate
: This runs immediately after a change to your component occurs and before it has been re-rendered. It’s useful for acquiring the state of reactive data before it has been rendered.updated
: It runs immediately after thebeforeUpdate
hook and re-renders your component with new data changes.beforeUnMount
: This is fired directly before unmounting your component instance. The component will still be functional until theunmounted
hook is called, allowing you to stop event listeners and subscriptions to data to avoid memory leaks. Note this event is calledbeforeDestroy
in Vue 2.x.unmounted
: All the virtual DOM elements and event listeners have been cleaned up from your Vue instance. This hook allows you to communicate that to anyone or any element that needs to know this has been done. This event in Vue 2.x is calleddestroyed
.
Let’s do a small exercise to learn how and when to use Vue’s lifecycle hooks, and when they trigger.
Exercise 1.10 – using a Vue lifecycle to control data
In this exercise, we will be learning how and when to use Vue’s lifecycle hooks, and when they are triggered by using JavaScript alerts. By the end of the exercise, we will be able to understand and use multiple Vue lifecycle hooks.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.10.
We will build a list of different elements demonstrating adding different quantities to a cart. Then, we will display the updated cart’s total value in a currency format by performing the following:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.10
folder by using the following commands in order:> cd Chapter01/Exercise1.10/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-10.vue
in thesrc/components
directory. - Inside
Exercise1-10.vue
, we start by creating an array of data to iterate through in a list element, set the key ton
, and output the{{item}}
value inside of the<li>
element using curly braces:<template>
<div>
<h1>Vue Lifecycle hooks</h1>
<ul>
<li v-for="(item, n) in list" :key="n">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list: [
'Apex Legends',
'A Plague Tale: Innocence',
'ART SQOOL',
'Baba Is You',
'Devil May Cry 5',
'The Division 2',
'Hypnospace Outlaw',
'Katana ZERO',
],
}
}
}
</script>
- Add
beforeCreated()
andcreated()
as properties below thedata()
function. Set an alert or console log inside these hooks so that you can see when they are being triggered:<script>
export default {
data(){ /*…*/ },
beforeCreate() {
alert('beforeCreate: data is static, thats it')
},
created() {
alert('created: data and events ready, but no
DOM')
},
}
</script>
- When you refresh your browser, you should see both alerts before you see your list load on the page:

Figure 1.33 – Observing the beforeCreate() hook alert first
- The following screenshot displays the
created()
hook alert after thebeforeCreate()
hook:

Figure 1.34 – Observing the before() hook alert after the beforeCreate() hook
- Define
beforeMount()
andmounted()
in the same way as in step 6. Set an alert or console log inside of these hooks so that you can see when they are being triggered:<script>
export default {
data() { /*…*/ },
/*…*/
beforeMount() {
alert('beforeMount: $el not ready')
},
mounted() {
alert('mounted: DOM ready to use')
},
}
</script>
- When you refresh your browser, you should also see these alerts before you can see your list load on the page:

Figure 1.35 – Observing the beforeMount() hook alert after the create() hook
- The following screenshot displays the
mounted()
hook alert after thebeforeMount()
hook:

Figure 1.36 – Observing the mounted() hook alert after the beforeMount() hook
- Add a new
button
element inside your<li>
element that renders theitem
output. Use a@click
directive to bind this button to a method calleddeleteItem
and pass theitem
value as an argument:<template>
<div>
<h1>Vue Lifecycle hooks</h1>
<ul>
<li v-for="(item, n) in list" :key="n">
{{ item }}
<button @click="deleteItem(item)">Delete</button>
</li>
</ul>
</div>
</template>
- Add a method called
deleteItem
into amethods
object above your hooks but below thedata()
function. Inside this function, passvalue
as an argument and filter out items from thelist
array based on this value. Then, replace the existing list with the new list:<script>
export default {
data() { /*…*/ },
/*…*/
methods: {
deleteItem(value) {
this.list = this.list.filter(item => item !==
value)
},
},
}
</script>
- Add
beforeUpdate()
andupdated()
as functions same as in step 9 and set an alert or console log inside them:<script>
export default {
/*...*/
beforeUpdate() {
alert('beforeUpdate: we know an update is about to
happen, and have the data')
},
updated() {
alert('updated: virtual DOM will update after you
click OK')
},
}
</script>
When you delete a list item by clicking on the Delete button in your browser, you should see these alerts. For example, when deleting the first item in the list, beforeUpdated
will trigger:

Figure 1.37 – BeforeCreated is called first after clicking on any delete button
Then, updated
triggers, as shown in the following screenshot:

Figure 1.38 – updated is called when the Vue engine finishes updating the component before rendering to the DOM
- Continue adding
beforeUnmount()
andunmounted()
to the component options as function properties. Set an alert or console log inside these hooks so that you can see when they are being triggered:<script>
export default {
/*...*/
beforeUnmount() {
alert('beforeUnmount: about to blow up this
component')
},
unmounted() {
alert('unmounted: this component has been
destroyed')
},
}
</script>
- Add a new string to your
list
array – for example,testing
unmounted hooks
:<script>
export default {
data() {
return {
list: [
'Apex Legends',
'A Plague Tale: Innocence',
'ART SQOOL',
'Baba Is You',
'Devil May Cry 5',
'The Division 2',
'Hypnospace Outlaw',
'Katana ZERO',
'testing unmounted hooks',
],
}
},
- You should see the unmount alerts according to this order:
beforeUnmount
–beforeCreated
–created
–beforeMount
–unmounted
–mounted
. An example output screen displaying the alert forbeforeUnmount
is shown here:

Figure 1.39 – Alert displays when a component is about to be unmounted
Note
mounted
and created
lifecycle hooks will run every time a component is initialized. If this is not the desired effect, consider running the code you want to run once from the parent component or view, such as the App.vue
file.
In this exercise, we learned what Vue lifecycle hooks are, when they trigger, and in what order they trigger. This will be useful in combination with triggering methods and controlling data within your Vue components.
Next, we will discuss how we style our Vue components using the <
style>
section.
Styling components
When using Vue components, the Vite compiler allows you to use almost any frontend templating language style. The easiest way to enable these expressive library plugins in your Vue templates is to install them when you initialize your project, or by using npm install
(or yarn add
) for the package.
When using the style
tag inside of a Vue component, you can specify a language using the lang
attribute, provided that you have installed that specific language plugin.
For example, if you chose to install the Stylus preprocessor, first you need to install the stylus
package in your project as a dependency by performing the following command:
npm add -D stylus #OR yarn add -d stylus
Then, you can add the lang="stylus"
attribute to the style
tag to begin using Stylus:
<style lang="stylus"> ul color: #2c3e50; > h2 color: #22cc33; </style>
Another benefit of using Vue is scoping the style with the scoped
attribute. This is a useful way to create isolated and component-specific CSS stylings. It also overrides any other CSS global rules, according to the CSS rule of specificity.
It is not recommended to scope global styles. A common method for defining global styling is to separate these styles into another style sheet and import them into your App.vue
file.
Now, let’s practice importing SCSS, a pre-processor plugin for CSS, to use in your application, and write some scoped stylings with the following exercise.
Exercise 1.11 – importing SCSS into a scoped component
In this exercise, we will be utilizing the style
tag to add SCSS preprocessed styles to a component and importing external stylesheets.
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.11.
Let’s start by performing the following steps:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.11
folder by using the following commands in order:> cd Chapter01/Exercise1.11/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-11.vue
in thesrc/components
directory. - Inside
Exercise1-11.vue
, let’s write some HTML that can be styled using SCSS. Let’s keep practicing the interpolation method:<template>
<div>
<h1>{{ title }}</h1>
<h2>{{ subtitle }}</h2>
<ul>
<li>{{ items[0] }}</li>
<li>{{ items[1] }}</li>
<li>{{ items[2] }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
title: 'My list component!',
subtitle: 'Vue JS basics',
items: ['Item 1', 'Item 2', 'Item 3']
}
},
}
</script>
- Add the
sass
SCSS package as a project dependency:npm add -D sass
- Add the
lang
attribute to thestyle
tag and add thescss
value to enable SCSS syntax inside thestyle
block:<style lang="scss"></style>
- Create a folder inside the
src/
directory calledstyles
. Inside this new folder, create a file calledtypography.scss
:src/styles/typography.scss
- Inside
typography.scss
, add some styling for the template you composed in your component, such as defining color variables (green
,grey
, andblue
) to reuse in different areas of related CSS rules, and some CSS styles forh1
,h2
, and the list elements:/* typography.scss */
$color-green: #4fc08d;
$color-grey: #2c3e50;
$color-blue: #003366;
h1 {
margin-top: 60px;
text-align: center;
color: $color-grey;
+ h2 {
text-align: center;
color: $color-green;
}
}
ul {
display: block;
margin: 0 auto;
max-width: 400px;
padding: 30px;
border: 1px solid rgba(0,0,0,0.25);
> li {
color: $color-grey;
margin-bottom: 4px;
}
}
In SCSS, you can use standard CSS selectors to select elements in your component.
ul > li
will select every <li>
element inside of an <ul>
element for styling. Similarly, using the addition symbol (+
) means that the elements placed after the first element will be styled if they match the condition. For example, h1 + h2
will dictate that all h2
elements after h1
will be styled in a certain way, but h3
will not. You can understand this better through the following example:
In CSS, you would present this code as follows:
h1 + h2 { /* Add styling */ } ul > li { /* Add styling */ }
In SCSS, the same code can be represented as follows:
h1 { + h2 { // Add styling } } ul { > li { // Add styling } }
- In your component, import these styles by using the SCSS
@
import
method:<style lang="scss">
@import '../styles/typography.scss';
</style>
This will generate an output as follows:

Figure 1.40 – When you save and reload, your project should have the style imported
- Add the
scoped
attribute to your<style>
tag to only apply these styles to this component instance. Use the variable from the$color-blue
imported stylesheet:<style lang="scss" scoped>
@import '../styles/typography';
h1 {
font-size: 50px;
color: $color-blue; // Use variables from imported stylesheets
}
</style>
The output of the preceding code is as follows:

Figure 1.41 – The outcome of scoping styles
- Inspect the DOM and you will notice that at runtime, that scoping has applied
v-data-*
attributes to your DOM elements specifying these specific rules.
The Elements tab of your browser Devtools also shows the following after expanding the <head>
and <
style>
tags:

Figure 1.42 – How the virtual DOM uses data attributes to assign scoped styles
- Create a new style sheet called
global.scss
in thestyles
folder, containing only stylings for the mainbody
element:/* /src/styles/global.scss */
body {
font-family: 'Avenir', Helvetica, Arial,
sans-serif;
margin: 0;
}
- Import this stylesheet into your
App.vue
:<style lang="scss">
@import './styles/global.scss';
</style>
Our app should render the same as before; only the font family for all elements should change to Avenir and there should be no margin for the main body
, as follows:

Figure 1.43 – Properly scoped styles for Exercise 1.03
In this exercise, we interpolated data that originated from an array and learned about some basic SCSS syntax. Then, we styled our component using forms of scoped
SCSS, which can either exist inside the <style>
tag or be imported from another directory into our project.
In the next section, we are going to experiment with how to write dynamic CSS for a component using Vue 3 features.
Setting up state-driven dynamic CSS in Vue 3
Vue 3.x introduces a new CSS function, v-bind()
, to use within the style
section of a Vue SFC. We use this function to create a one-way link between local data and a CSS value.
Under the hood, the Vue engine uses CSS custom properties (or CSS variables) to compute the dynamic stylings received from v-bind()
. For each v-bind()
, it generates a hashed custom property (with the --
prefix) and adds it to the component’s root element. All the custom properties are added as inline static styles and will be updated whenever the linked local data’s value changes.
For example, let’s have a component that prints out a title
and contains a local data property, headingStyles
. The headingStyles
data object contains several fields such as marginTop
, textAlign
, and color
, indicating the relevant CSS properties:
<template> <h1>{{ title }}</h1> </template> <script> export default { data() { return { title: 'Binding with v-bind example', headingStyles: { marginTop: '10px', textAlign: 'center', : '#4fc08d', } } } } </script>
At this point, the output does not have a custom style and will be as follows:

Figure 1.44 – Displaying the title without using v-bind() and custom CSS
We now can bind headingStyles
to the CSS stylings of h1
in the <style>
section, by applying v-bind()
accordingly:
<style> h1 { margin-top: v-bind(headingStyles.marginTop); text-align: v-bind(headingStyles.textAlign); color: v-bind(headingStyles.color); } </style>
The output will now have custom CSS enabled:

Figure 1.45 – Output with v-bind() and custom CSS applied
If you open Developer Tools and inspect this h1
element in the Elements tab, you will see it has inline styles, as shown in Figure 1.47:

Figure 1.46 – Devtools inspection shows the inline styles with hashed custom properties generated
Since v-bind()
is a Vue 3.x feature, it also supports local variables defined using script setup
out of the box. You can re-write the code in the script setup
standards, and the outputs stay the same.
v-bind()
also support JavaScript expressions. To use JavaScript expressions, you need to wrap them in quotes. For example, we can take headingStyles
from the previous example and re-define marginTop
as a number only:
headingStyles: { marginTop: 10, textAlign: 'center', color: '#4fc08d', }
In the <style>
section, let’s compute margin-top
for the h1
selector with the addition of 5px
and add the px
suffix:
<style> h1 { margin-top: v-bind('`${headingStyles.marginTop + 5}px`'); text-align: v-bind(headingStyles.textAlign); color: v-bind(headingStyles.color); } </style>
The output now has a margin top of 15px
as shown in Figure 1.48:

Figure 1.47 – Generated custom property for margin-top is 15px
Using v-bind(
) is very beneficial for defining theming dynamically and programmatically. However, it provides only one-way binding from the local data to the styling, not vice versa. In the next section, we will explore the opposite binding direction using CSS modules.
Understanding CSS modules
A recent pattern that has become popular in the reactive framework world is CSS modules. Frontend development always faces the issue of conflicting CSS class names, ill-structured BEM code, and confusing CSS file structures. Vue components help to solve this by being modular and allowing you to compose CSS that will generate unique class names for the specific component at compile time.
Using CSS modules in Vue exports CSS styles from the style
section into JavaScript modules and uses those styles in the template and logic computing.
To enable this feature in Vue, you will need to add the module
attribute to the style
block, and reference as classes using the :class
and $style.<class name>
syntax, as shown here:
<template> <div :class="$style.container">CSS modules</div> </template> <style module> .container { width: 100px; margin: 0 auto; background: green; } </style>
Once you have enabled the CSS module, the Vue engine exposes the $style
object containing all the defined selectors as objects for use within the template
section, and this.$style
to use within the component’s JavaScript logic. In the preceding example, you are binding the CSS stylings defined for the.container
class selector to div
using $style.container
.
If you inspected the DOM tree, that class would be called something such as .container_ABC123
. If you were to create multiple components that had a semantic class name such as .container
but used CSS modules, you would never run into style conflicts again.
Now, let’s practice using CSS modules to style a Vue component.
Exercise 1.12 – styling Vue components using CSS modules
To access the code file for this exercise, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Exercise1.12.
Let’s start by performing the following steps:
- Use the application generated with
npm init vue@3
as a starting point, or within the root folder of the code repository, navigate into theChapter01/Exercise1.12
folder by using the following commands in order:> cd Chapter01/Exercise1.12/
> yarn
- Run the application using the following command:
yarn dev
- Open the exercise project in VS Code (by using the
code .
command within the project directory) or your preferred IDE. - Create a new Vue component file named
Exercise1-12.vue
in thesrc/components
directory. - Inside
Exercise1-12.vue
, compose the following code:<template>
<div>
<h1>{{ title }}</h1>
<h2>{{ subtitle }}</h2>
</div>
</template>
<script>
export default {
data() {
return {
title: 'CSS module component!',
subtitle: 'The fourth exercise',
}
},
}
</script>
- Add the
<style>
block and addmodule
as an attribute instead ofscoped
:<style module>
h1,
h2 {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
text-align: center;
}
.title {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
color: #2c3e50;
margin-top: 60px;
}
.subtitle {
color: #4fc08d;
font-style: italic;
}
</style>
- To use CSS modules in your template, you need to bind them to your HTML elements by using the
:class
syntax, which is the same as thev-bind:class
directive:<h1 :class="$style.title">{{ title }}</h1>
<h2 :class="$style.subtitle">{{ subtitle }}</h2>
When you save it, your project should look something like this:

Figure 1.48 – Output using CSS modules
- If you inspect the virtual DOM, you will see how it has applied unique class names to the bound elements:

Figure 1.49 – Generated CSS module class
In this exercise, we saw how to use CSS modules in your Vue components and how it works differently from CSS scoping.
In combination with file splitting and importing SCSS, using CSS modules is the preferred method for scoping component styling here. This safely ensures that individual component styles and business rules do not risk overriding each other and do not pollute global styling and variables with component-specific styling requirements.
Readability is important. The class name also hints at the component name as opposed to the v-data
attribute, which can be good when debugging large projects.
In the next section, you will apply what you have learned in this chapter to build a dynamic shopping list app by combining directives, loops, two-way data, and method declaration for a Vue component together, with scoped CSS styling.
Activity 1.01 – building a dynamic shopping list app using Vue
To access the code file for this activity, refer to https://github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter01/Activity1.01
This activity aims to leverage your knowledge thus far about the basic features of an SFC, such as expressions, loops, two-way binding, and event handling.
This application should let users create and delete individual list items and clear the total list in one click.
The following steps will help you complete the activity:
- Build an interactive form in one component using an input bound to
v-model
. - Add one input field to which you can add shopping list items. Allow users to add items by using the Enter key by binding a method to the
@
keyup.enter
event. - Users can expect to clear the list by deleting all the items or removing them one at a time. To facilitate this, you can use a
delete
method, which can pass the array position as an argument, or simply overwrite the whole shopping list data prop with an empty array,[]
.
The expected outcome is as follows:

Figure 1.50 – Expected output of Activity 1.01
Summary
In this chapter, you learned how to create and run a Vue project using the command prompt with Vite. You also learned how to create basic Vue components. Within these Vue components, you can scaffold templates that use Vue’s unique directives and HTML syntax sugar to loop over data or control DOM states with conditional statements. The key concepts of reactive data using data props and the v-model
binding were explored and demonstrated as useful through real-life examples that utilized Vue methods and lifecycles.e
In the next chapter, we will learn about more advanced reactive data concepts that will build upon this first chapter: using computed props and watchers and fetching asynchronous data from an external source.sssssse