Template Syntax Rendering

In this chapter, you will learn to use Vue's template syntax to render JS data into HTML, making the blog homepage display a list of article cards.


Interpolation Expression {{ }}

The interpolation expression is the most basic data binding method in Vue.

It "inserts" the value of a JS variable into the HTML. The syntax is double curly braces wrapping the variable name.

Example

<script setup>
const blogTitle = 'EXAMPLE Frontend Notes'
const author = 'Xiao Ming'
</script>

<template>
  <h1>{{ blogTitle }}</h1>
  <p>Author: {{ author }}</p>
  <p>Current time: {{ new Date().toLocaleDateString() }}</p>
</template>

The page will render:

EXAMPLE 前端笔记
作者:小明
当前时间:2024/5/15

Inside the curly braces, you can put any JS expression: variables, operations, ternary expressions, function calls - all are fine, but you cannot put statements (such asif、for)。

The interpolation expression only replaces the text inside the tag, not the tag itself. For example,

{{ content }}
in
the tag itself is not affected.


Attribute Binding v-bind (shorthand :)

Interpolation expressions can only be used for tag content, not for HTML attributes.

To use JS variables in attributes, you need to use thev-binddirective.

Example

<script setup>
const coverUrl = '/images/vue-cover.png'
const linkUrl = '/post/1'
const isActive = true
</script>

<template>
  <!-- Full syntax: v-bind:attribute="variable" -->
  <img v-bind:src="coverUrl" v-bind:alt="Article cover">

  <!-- Shorthand: directly use :attribute="variable" -->
  <a :href="linkUrl">Read Full Article</a>

  <!-- Dynamic class: use object syntax -->
  <div :class="{ active: isActive, 'text-bold': true }">
This text has both the active and text-bold classes.
  </div>
</template>

The colon shorthandis the most common syntax; in actual development, you almost never usev-bind:the full name.


List Rendering v-for

The blog homepage needs to display multiple articles. A single article is an object, and multiple articles form an object array.

v-forYou can iterate over the array and generate corresponding HTML for each element.

Example

<script setup>
// Use a JS object array to simulate article data (will be changed to load from a JSON file later)
const articles = [
  {
    id: 1,
title: 'Complete Guide to Getting Started with Vue3',
summary: 'Learn Vue3 Composition API from scratch, covering core concepts such as ref, reactive, computed.',
    date: '2024-05-10',
    category: 'Vue',
    cover: '/images/vue.png'
  },
  {
    id: 2,
title: 'JavaScript Asynchronous Programming Explained',
summary: 'Understand Promise, async/await, event loop, and microtask queue in one article.',
    date: '2024-05-08',
    category: 'JavaScript',
    cover: '/images/js.png'
  },
  {
    id: 3,
title: 'CSS Grid Layout in Practice',
summary: 'Easily implement complex responsive layouts with CSS Grid, leaving behind the limitations of float and flexbox.',
    date: '2024-05-05',
    category: 'CSS',
    cover: '/images/css.png'
  }
]
</script>

<template>
  <div class="article-list">
    <!-- v-for="item in array" :key="unique identifier" -->
    <div v-for="article in articles" :key="article.id" class="card">
      <img :src="article.cover" :alt="article.title">
      <div class="card-body">
        <span class="category">{{ article.category }}</span>
        <h3>{{ article.title }}</h3>
        <p>{{ article.summary }}</p>
        <span class="date">{{ article.date }}</span>
      </div>
    </div>
  </div>
</template>

<style scoped>
.article-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 24px;
  margin-top: 20px;
}

.card {
  background: #fff;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  transition: transform 0.2s;
}

.card:hover {
  transform: translateY(-4px);
}

.card img {
  width: 100%;
  height: 180px;
  object-fit: cover;
}

.card-body {
  padding: 16px;
}

.category {
  display: inline-block;
  padding: 2px 10px;
  background: #E8EEF5;
  color: #427DB8;
  border-radius: 12px;
  font-size: 12px;
}

.card-body h3 {
  margin: 10px 0 8px;
  font-size: 18px;
}

.card-body p {
  color: #666;
  font-size: 14px;
  line-height: 1.6;
}

.date {
  display: block;
  margin-top: 10px;
  font-size: 12px;
  color: #999;
}
</style>

The Importance of the key Attribute

:keyIt is an essential companion to v-for, telling Vue the identity of each element.

When the order of the array changes (e.g., insertion, deletion, sorting), Vue uses the key to determine which DOM nodes need to be updated and which can be reused.

Value of keyConsequence
Unique id (recommended)Tracks each element correctly and updates efficiently
Array indexMay cause state confusion during insertion/deletion (e.g., input box contents get swapped)
No keyThe console will warn; Vue defaults to the "in-place update" strategy, which has poor performance.

Always give v-for a unique and stable key; do not use index as the key. This is one of the most important habits in Vue development.


Conditional Rendering v-if / v-else

When you need to display different content under different conditions, use thev-iffamily of directives.

Example

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

const articles = ref([]) // article list (empty array means no data for now)
const isLoading = ref(true) // loading state
// Simulate data loading: fill in data after 2 seconds
setTimeout(() => {
  articles.value = [
{ id: 1, title: 'Getting Started with Vue3', summary: '...' },
{ id: 2, title: 'React Comparison', summary: '...' }
  ]
  isLoading.value = false
}, 2000)
</script>

<template>
  <div>
    <!-- Loading -->
    <p v-if="isLoading">Loading, please wait...</p>

    <!-- Show the list when there is data -->
    <div v-else-if="articles.length > 0">
      <div v-for="article in articles" :key="article.id" class="card">
        <h3>{{ article.title }}</h3>
        <p>{{ article.summary }}</p>
      </div>
    </div>

    <!-- Show an empty state when there is no data -->
    <p v-else>No articles yet, please stay tuned.</p>
  </div>
</template>

This pattern covers the three most common UI states:Loading → Has data → No data。

v-if vs v-show

DirectiveToggle methodInitial render costToggle costUse cases
v-ifAdds/removes DOM nodesLow (does not render)High (rebuilds DOM)Scenarios where the condition rarely changes, such as permission control
v-showToggles the CSS display propertyHigh (always renders)Low (only changes styles)Scenarios with frequent toggling, such as tab switching

Easy to remember: use v-show for frequent toggling, use v-if for stable conditions.


Hands-on: Complete Blog Homepage

Combine the template syntax you learned earlier and write a complete blog homepage component.

Example

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

// Article data (currently hardcoded; later chapters will change it to load via fetch)
const articles = ref([
  {
    id: 1,
title: 'Complete Guide to Getting Started with Vue3',
summary: 'Learn Vue3 Composition API from scratch, covering core concepts such as ref, reactive, computed.',
    date: '2024-05-10',
    category: 'Vue',
    cover: '/images/vue.png'
  },
  {
    id: 2,
title: 'JavaScript Asynchronous Programming Explained',
summary: 'Understand Promise, async/await, event loop, and microtask queue in one article.',
    date: '2024-05-08',
    category: 'JavaScript',
    cover: '/images/js.png'
  },
  {
    id: 3,
title: 'CSS Grid Layout in Practice',
summary: 'Easily implement complex responsive layouts with CSS Grid.',
    date: '2024-05-05',
    category: 'CSS',
    cover: '/images/css.png'
  }
])
</script>

<template>
  <div class="home">
    <h2 class="section-title">Latest Articles</h2>

    <!-- Loading -->
    <p v-if="!articles.length" class="empty-tip">No articles yet, please stay tuned.</p>

    <!-- Article card list -->
    <div v-else class="article-grid">
      <div
       v-for="article in articles"
       :key="article.id"
       class="article-card"
     >
        <img :src="article.cover" :alt="article.title" class="card-cover">
        <div class="card-content">
          <span class="card-category">{{ article.category }}</span>
          <h3 class="card-title">{{ article.title }}</h3>
          <p class="card-summary">{{ article.summary }}</p>
          <span class="card-date">{{ article.date }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.home {
  max-width: 960px;
  margin: 0 auto;
  padding: 20px;
}

.section-title {
  font-size: 24px;
  margin-bottom: 20px;
  color: #333;
}

.article-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

.article-card {
  background: #fff;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  transition: transform 0.2s, box-shadow 0.2s;
  cursor: pointer;
}

.article-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.card-cover {
  width: 100%;
  height: 180px;
  object-fit: cover;
}

.card-content {
  padding: 16px;
}

.card-category {
  display: inline-block;
  padding: 2px 10px;
  background: #E8EEF5;
  color: #427DB8;
  border-radius: 12px;
  font-size: 12px;
  margin-bottom: 8px;
}

.card-title {
  font-size: 18px;
  margin-bottom: 8px;
  color: #222;
}

.card-summary {
  font-size: 14px;
  color: #666;
  line-height: 1.6;
  margin-bottom: 12px;
}

.card-date {
  font-size: 12px;
  color: #999;
}

.empty-tip {
  text-align: center;
  color: #999;
  padding: 60px 0;
  font-size: 16px;
}
</style>

Chapter Summary

In this chapter, you learned four core template syntax features:{{ }}interpolation to display data,v-bind(shorthand:) to bind attributes,v-forloops to render lists,v-if/v-elseconditionals to control visibility.

At this point, the blog homepage can already dynamically render article cards based on JS data.

Other Extensions