You'll set up a Vue 3 project with Vite, write your first single-file component, and understand how Vue's template syntax and two-way binding work. By the end you'll have a working app running locally.
Vue 2 is in maintenance mode — no new features, just security fixes until end of 2023 (already passed). Vue 3 with the Composition API is the current standard. If you're starting fresh, start with Vue 3. Everything in this course is Vue 3.
The Composition API replaced the Options API as the recommended way to write components. It's more flexible, easier to test, and makes code reuse straightforward. We'll use <script setup> syntax throughout — it's the cleanest way to write Vue components today.
Vite is the official build tool for Vue 3. It starts in under a second and hot-reloads instantly. Don't use Vue CLI for new projects — it's legacy.
# Create a new Vue 3 project
npm create vite@latest my-app -- --template vue
cd my-app
npm install
npm run dev
Open http://localhost:5173. You'll see the Vite + Vue welcome page. Now open the project in your editor and look at the file structure:
my-app/
├── src/
│ ├── App.vue ← root component
│ ├── main.js ← app entry point
│ ├── components/ ← your components go here
│ └── assets/ ← images, css
├── index.html
└── vite.config.js
A Vue single-file component (SFC) has three sections: <script setup> for logic, <template> for HTML, and <style> for CSS. They all live in one .vue file.
<script setup>
import { ref } from 'vue'
// ref() creates a reactive variable
const name = ref('World')
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<div class="greeting">
<h1>Hello, {{ name }}!</h1>
<!-- v-model creates two-way binding -->
<input v-model="name" placeholder="Enter your name" />
<p>Count: {{ count }}</p>
<button @click="increment">Add One</button>
</div>
</template>
<style scoped>
.greeting {
padding: 24px;
font-family: sans-serif;
}
button {
margin-top: 8px;
padding: 8px 16px;
cursor: pointer;
}
</style>
<script setup>, you use name.value. Inside the template, Vue automatically unwraps it — so you just write {{ name }}. This trips everyone up once. Now you know.Vue's template is HTML with special directives (attributes starting with v-). These are the ones you'll use every day:
<script setup>
import { ref } from 'vue'
const show = ref(true)
const items = ref(['Apples', 'Bananas', 'Cherries'])
const selectedColor = ref('blue')
</script>
<template>
<!-- v-if / v-else: conditional rendering -->
<p v-if="show">Visible</p>
<p v-else>Hidden</p>
<!-- v-for: list rendering -->
<ul>
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
<!-- :attr (v-bind shorthand): dynamic attribute -->
<div :class="selectedColor">Colored box</div>
<!-- @event (v-on shorthand): event listener -->
<button @click="show = !show">Toggle</button>
<!-- v-model: two-way binding on input -->
<select v-model="selectedColor">
<option value="blue">Blue</option>
<option value="red">Red</option>
</select>
</template>
Import a component and use it as an HTML tag. With <script setup>, imported components are automatically available in the template — no registration step needed.
<script setup>
import Greeting from './components/Greeting.vue'
</script>
<template>
<main>
<Greeting />
</main>
</template>
Pass data to child components with props, and communicate back up with emits:
<script setup>
// defineProps: declare what data the parent can pass in
const props = defineProps({
username: String,
score: {
type: Number,
default: 0
}
})
// defineEmits: declare events this component can fire
const emit = defineEmits(['delete'])
</script>
<template>
<div class="card">
<h3>{{ username }}</h3>
<p>Score: {{ score }}</p>
<button @click="emit('delete', username)">Remove</button>
</div>
</template>
<script setup>
import UserCard from './components/UserCard.vue'
import { ref } from 'vue'
const users = ref([
{ username: 'alice', score: 42 },
{ username: 'bob', score: 17 }
])
function removeUser(name) {
users.value = users.value.filter(u => u.username !== name)
}
</script>
<template>
<UserCard
v-for="user in users"
:key="user.username"
:username="user.username"
:score="user.score"
@delete="removeUser"
/>
</template>
npm create vite@latest.TaskList.vue component with a ref array of tasks (strings).v-for, with a delete button next to each one.v-model to toggle completed state. Cross out completed tasks with CSS.App.vue.<script setup> is the modern way to write components. Use it everywhere.ref() creates reactive variables. Access .value in scripts, not in templates.v-if, v-for, v-model, :attr (v-bind), @event (v-on).