Single-File Components and Template Syntax

First lesson of the Vue.js course, free to read: about 17 minutes, 4 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Create a Vue project and write a single-file component with <script setup>
  • Use interpolation and the v-bind, v-on, v-if, v-show and v-for directives
  • Extract display logic into testable pure functions

Single-file components (SFC)Template syntaxDirectives v-if, v-for, v-bind, v-on

Creating a Vue project

Vue is a progressive JavaScript framework for building interfaces: you can add it to an existing page or build a full application. The recommended starting point is the official create-vue tool, based on Vite:

npm create vue@latest     # questions: TypeScript, then Router, Pinia, Vitest…
cd mon-projet
npm install
npm run dev               # Vite dev server with hot reload

The project uses Vue 3.5 and Vite 8 (Node.js 22.18 or later; the current LTS, Node 26, works). The first question, “Use TypeScript?”, is checked Yes by default: since this course is written in JavaScript, answer No. You can also specify everything on the command line: npm create vue@latest mon-projet -- --router --pinia --vitest directly creates a JavaScript project with these options (add --ts for TypeScript).

The project contains src/main.js, which creates the application (createApp(App).mount('#app')), and .vue files: single-file components (SFCs).

Anatomy of a component

<script setup>
import { ref } from 'vue'

const titre = 'Mes tâches'
const taches = ref([
  { id: 1, libelle: 'Acheter du pain', faite: false },
  { id: 2, libelle: 'Réviser Vue', faite: true },
])
function basculer(tache) {
  tache.faite = !tache.faite
}
</script>

<template>
  <h1>{{ titre }}</h1>
  <ul>
    <li v-for="tache in taches" :key="tache.id" :class="{ faite: tache.faite }">
      <label>
        <input type="checkbox" :checked="tache.faite" @change="basculer(tache)" />
        {{ tache.libelle }}
      </label>
    </li>
  </ul>
  <p v-if="taches.length === 0">No tasks.</p>
</template>

<style scoped>
.faite { text-decoration: line-through; }
</style>
  • <script setup> is the recommended syntax with the Composition API: any variable, function or component imported there is directly available in the template.
  • <template> describes the HTML, enriched with Vue’s syntax.
  • <style scoped> limits the styles to this component.

ref() makes a value reactive: when it changes, Vue updates the display. The next lesson covers reactivity in detail.

Interpolation and bindings

  • {{ expression }} displays a JavaScript expression (not a statement: no if or for).
  • v-bind:attribut or its shorthand :attribut binds an attribute to an expression: :disabled="panierVide", :href="url".
  • v-on:evenement or @evenement listens to an event: @click="ajouter", @submit.prevent="envoyer" (the .prevent modifier calls preventDefault()).
  • v-model creates a two-way binding with a field: <input v-model="recherche" />.

Conditional rendering and lists

v-if / v-else-if / v-else create or destroy the element; v-show keeps it in the DOM and hides it with display: none. Prefer v-show for an element toggled very often, v-if for content that is rarely displayed or expensive.

v-for repeats an element for each entry of a list. It requires a unique, stable :key, typically the item’s identifier. With the index as the key, Vue reuses the wrong elements when you sort or delete, and internal state (a field being typed in) ends up on the wrong row. Also avoid v-if and v-for on the same element: filter the list upstream instead.

Dynamic classes and styles

:class accepts an object (the class is applied if the value is truthy) or an array:

<button :class="['btn', `btn--${variante}`, { 'btn--desactive': desactive }]">

Extracting display logic

A template should stay readable. As soon as an expression becomes complex (filtering by a criterion, building classes, formatting a label), put it in a pure function: it receives data and returns a result, with no side effects. It can be tested without a browser and reused:

// src/logique/taches.js
export function filtrerTaches(taches, filtre) {
  if (filtre === 'actives') return taches.filter((t) => !t.faite)
  if (filtre === 'faites') return taches.filter((t) => t.faite)
  return taches
}

The component then uses it in the template or, better, in a computed property (computed), covered in the next lesson. This is what you will practice in this course’s code exercises: logic in plain JavaScript, components in hands-on tasks.

Takeaways

Component = script + template + style. : to bind, @ to listen, v-if/v-show to display, v-for with a stable :key to repeat, and display logic extracted into pure functions.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. CodeDifficulty 1JavaScript in the browser2 hints

    Filter a task list

    Logic used by the list component. Write:

    • filtrerTaches(taches, filtre) which returns all the tasks for "toutes", the incomplete tasks for "actives", the completed tasks for "faites". An unknown filter throws an error Filtre inconnu.
    • libelleRestantes(taches) which returns "Aucune tâche restante", "1 tâche restante" or "N tâches restantes".

    Never modify the array received.

    function filtrerTaches(taches, filtre) {
      // TODO
    }
    
    function libelleRestantes(taches) {
      // TODO
    }
  2. CodeDifficulty 2JavaScript in the browser2 hints

    Button classes

    The BoutonApp component computes its list of classes for :class. Write classesBouton(options) which receives an object { variante, taille, desactive } (all properties optional) and returns an array:

    • always "btn";
    • "btn--<variante>" with variante defaulting to "primaire"; only "primaire", "secondaire" and "danger" are accepted, otherwise an error Variante inconnue;
    • "btn--<taille>" with taille defaulting to "m";
    • "btn--desactive" last, only if desactive is truthy.
    function classesBouton(options = {}) {
      // TODO
    }
  3. TaskDifficulty 22 hints

    First component: the task list

    Create a project with npm create vue@latest (answer No to TypeScript and check no feature), then replace the content of App.vue with a task list component:

    1. Initial data (in a ref):
    id,libelle,faite
    1,Acheter du pain,false
    2,Réviser Vue,true
    3,Appeler le garage,false
    1. Display each task with a checkbox bound to faite, using v-for and :key="tache.id".
    2. Add three filter buttons (Toutes, Actives, Faites) that use your filtrerTaches function.
    3. Display below the list the text produced by libelleRestantes.
    • v-for uses :key with the task identifier
    • The filter buttons change the displayed list without modifying the data
    • Checking a task updates the counter immediately
    • The filter and label functions are imported from a separate .js file
  4. Multiple choiceDifficulty 11 hint

    v-if or v-show?

    A help panel is opened and closed very often by the user. Which directive is the most suitable, and why?

    • v-if, because it is faster on every toggle
    • v-show, because the element stays in the DOM and only its CSS visibility changes
    • v-for, to repeat the panel
    • No difference between the two

End-of-lesson quiz

  1. What does :disabled="panierVide" do?

    • It gives the attribute the string "panierVide"
    • It binds the disabled attribute to the value of the panierVide expression
    • It listens to the disabled event
  2. Why avoid the index as :key in a v-for?

    • It is forbidden by Vue
    • When the list is sorted or modified, Vue associates state with the wrong elements
    • It slows down the first render
  3. What is available in the template of a

    • Only exported variables
    • Variables, functions and imports declared at the top level of the script
    • Nothing without the data() method

The rest of the course

  1. Reactivity and the Composition API19 min
  2. Props, Events, v-model and Slots18 min
  3. Navigation with Vue Router18 min
  4. Shared State with Pinia and Testing19 min

Vue.js · Intermediate · 1 h 31 min