Vue.js is a simplified yet robust alternative to popular front-end frameworks like React or Angular. It's nice to learn this one first, as it has a smaller learning curve, meaning it's easier to learn.

Before, you could use vue-cli (29.5k ⭐), but now it's in maintenance mode, so they are recommending us to use vite (50.6k ⭐).

There are two ways of doing the same thing in Vue.

  • Composition API: use <script setup>
  • Options API: use <script>

The official create-vue project is using vite to generate a project

$ npm create vue@3 --yes
$ cd project_name
$ npm install
$ npm run dev

Where to learn?

πŸ“¦ Vue Single-File Components (.vue) πŸ“¦

The main component is App. It will usually load a view stored in src/views. To make views easier to manage, and to recycle parts of the code, we are extracting them inside a file in src/components.

➑️ Ex: a component for the pagination. Another for one product...

A Single-File Components is a .vue file, split into 3 tags: script, template, and style, so everything related to the component is encapsulated in one place.

// JavaScript
// ex: import a component
import HelloWorld from './components/HelloWorld.vue';
const text = "Hello, World";

  <!-- HTML CODE -->
  <!-- ex: use another component -->
  <HelloWorld msg="Hello, World!" />
  <HelloWorld :msg="text" />

<style scoped>
    /* CSS */

➑️ Note: you must use :msg if you want to use a variable. For instance, msg="myVariable" will pass a text instead of a variable.

Passing arguments to a component

A component can receive parameters. They are declared inside props.

export default {
  props: {
    msg: String,
    other: { // more complex properties
      type: String,
      required: false,
      default: ""

If we remove every check, we could shorten the code to:

export default {
  props: ['msg', 'other']

Inside the template, you can use it with {{ property_name }}

  <p>{{ msg }}</p>

πŸ“– Options API πŸ“–οΈ

The Options API is usually wordier, but it looks more declarative from my point of view, so it's easier to understand how Vue.js works.

// import a component, see components:
import HelloWorld from '../components/HelloWorld.vue'

export default {
  // you can use these in "template"
  components: { HelloWorld },
  data() {
    // declare references here. These are variables
    // that can be used in the HTML block. If they are
    // modified, then, the HTML element is updated too.
    // (bidirectional data-biding)
    return {
      count: 0
  watch: {
    // whenever count changes, this function will run
    // you can use paths (ex: 'xxx.yyy.zzz'(newValue))
    count(newCount, oldCount) {
  // avoid making calculations/complex stuff in template
  // do it in computed instead
  // the difference with methods is that the result
  // is cached until the data is modified
  computed: {
    square() {
        return this.count * this.count
  methods: {
    // declare methods that can be used in the HTML
    increment() {
  mounted() {
    // execute code with document.querySelector/... here,
    // like stuff that needs the component to be inside the
    // DOM to work.
  created() {},
  // async created() {},

Use a data/... inside a template

Example of using the variable count

  <div class="home">
    <!-- example of using count (data) -->
    <button @click="count++">Count is: {{ count }}</button>
    <!-- increment (method), and square (computed) -->
    <button @click="increment">Square is: {{ square }}</button>

Takeaway: inside vue properties (see v-bind), or braces ({{ here }}), you can use data, methods, computed, or JavaScript code, although you should rely on methods/computed in such cases.


  • v-bind: uni-directional data binding. When the value is updated, the bound attributes/... are updated, but editing the input field won't update the value.
<input v-bind:value="count">
<input :value="count"> <!-- same, shortcut -->
<input :[attributeName]="url"> <!-- custom attribute -->
<input :id="`input-${count}`"> <!-- complex value -->
<!-- add class based on a data -->
<div :class="{ 'active': isActive }"></div>
<div :class="['classA', 'classB']"></div>
  • v-model: bidirectional data-binding. Now, if the value is modified by the element, then the data is modified too.
<input v-model="count">
<input v-model.lazy="count"> <!-- after changes -->
<input v-model.trim="count"> <!-- trim -->
  • v-if: add/remove from the DOM the element
<p v-if="count===0">Zero</p>
<p v-else-if="count===1">One</p>
<p v-else>Greater than one</p>
  • v-show: always add in the DOM, but toggle visibility. When you toggle visibility a lot, it will be less costly than using v-if.
<p v-show="count===0">Zero</p>
  • v-on/@: on event
<button @click="count++">Count is: {{ count }}</button>
<button v-on:click="count++">Count is: {{ count }}</button>
<img src=# @error="count--">
<!-- .prevent is a modified to call e.preventDefault() -->
<form @submit.prevent="onSubmit">...</form>
<input @input="e => count =">
  • v-for
<!-- 0 then 1 -->
<li v-for="item in [1,2]">
  {{ item }}
<!-- "Element 0: 1" then "Element 1: 2" -->
<li v-for="(item, index) in [1, 2]">
    Element {{ index }}: {{ item }}
<!-- Other uses -->
<li v-for="({x, y}, index) in [{x: 1, y: 2}]"></li>
<li v-for="(value, key) in myObject"></li>
<li v-for="n in 10"></li>
v-for to replicate a component
  v-for="(item, index) in items"

In "MyComponent", you could use code like that.

// good practice: define a class for item
import Product from "@/classes/Product";

export default {
  props: {
    item: Product
    // if you don't want to create a class
    // 'item: Object'

πŸ“• Composition API πŸ“•

The code below is the same as declaring count inside data.

<script setup>
import { ref } from 'vue'
const count = ref(0)

πŸ›£οΈ Routing πŸ›£οΈ

See Router. See also Data Fetching.

<script setup>
import { RouterLink, RouterView } from 'vue-router'

      <RouterLink to="/">Home</RouterLink>
      <RouterLink to="/about">About</RouterLink>

  <RouterView />

➑️ Note that RouterView is where the loaded page will be displayed.

Edit routes

To edit the routes, edit router/index.js.

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
      path: '/',
      name: 'home',
      component: HomeView
      path: '/about',
      name: 'about',
      // lazy-loaded when the route is visited
      component: () => import('../views/AboutView.vue')

Move to another page manually

You can move to another page with

this.$router.push({ name:'about' })

Query, and params

You can get query parameters (?xxx=yyy) with.


To use params, you must declare them inside the path of your route.

+ path: '/users/:id',

Then, you can get them back using



You can do something before loading the view inside beforeRouteEnter. For instance, you can change the title of the page.

Load something from an API, set the title dynamically
export default {
  data() {
    return {
      xxx: null,
  // ...
  beforeRouteEnter(to, from, next) {
    const id =; // see Params
    fetch("XXX" + id)
      .then((res) => res.json())
      .then((json) => {
        next((vm) => { // call 'next' when done
          // set the variable 'data/xxx'
 = json;
          // set the title
          window.document.title = "XXX | " + id;

πŸ₯‚ Using Bootstrap in Vue πŸ₯‚

First, install bootstrap

$ npm install bootstrap

Remove everything inside main.css. You may keep the import.

@import './base.css';

- [...]

Inside main.js, import bootstrap's css.

import "./assets/main.css";
+import "bootstrap/dist/css/bootstrap.css";

const app = createApp(App);

Done! πŸ₯‚

🐏 Notes 🐏

  • nextTick in Methods
  • writable computed
  • deep watchers
  • template refs
  • Preprocessors (ts, scss)
  • @/main matches src/main.js (shortcut for src)
  • import "./assets/main.css";


<!-- in a template -->
<slot name="xxx"></slot>

<!-- when calling the template -->
    <template #xxx>
  • Vue.js - change the page title (article)
  • <span v-html="tags"></span> to avoid escaping HTML
// created
window.document.title = "xxx"