The short version
- Vue is a JavaScript framework for building user interfaces. You can start with just HTML, CSS, and JavaScript.
- You can learn Vue for free. The core of the path is our free Vue.js Fundamentals course.
- A first component takes an afternoon. A real application takes a few weeks of steady practice.
- Learn the Composition API. It is how modern Vue 3 is written.
What is Vue.js?
Vue is a framework for building the part of an app a user sees and interacts with.
Reactivity finally clicked for me 🎉
Tap the heart. The count changes in the post and in your code at once.
This is the one idea the rest of Vue builds on: you keep the state, Vue keeps the view in sync with it. You describe what the screen should look like for a given state, and each time that state changes Vue updates the page for you, so you rarely touch the DOM by hand. Once you can think in that loop, state in and view out, every feature further down the page is just another way to express it.
It runs anywhere, from a small widget dropped into an existing page to a large single-page app, and with Nuxt it powers full server-rendered sites.
GitLab, Nintendo, and Adobe all run Vue in production, so the skills you build here carry into real work. People reach for Vue because it is approachable on day one and still holds up when a project grows.
What you need before you start
You do not need much. If you are comfortable with the basics of JavaScript, variables, functions, arrays, and objects, plus a little HTML and CSS, you have enough to begin. No other framework first, and no TypeScript.
- JavaScript basics
- A little HTML
- A little CSS
Your Vue learning path
Learn the pieces in the order they build on each other. Follow it from the top for a clear route from zero to a real app, or jump straight to the topic you came for.
StartThe one idea
State in, view out, the single mental model the rest of Vue is built on.
Part 1Your first components
Everything you need to build one interactive component from scratch.
Part 2Build with components
Split a UI into pieces, pass data down with props, send events up.
Part 3Into a real application
The pieces a real app needs: shared logic, routing, state, and data.
Set up Vue
Three ways to start, from zero-install to a full project. Getting started with Vue is genuinely this short, the rest is learning what to put inside your components.
Run it in the browser
Open the Vue Playground and paste any example from this page. Nothing to set up, and it is the fastest way to try an idea.
Open the Vue PlaygroundDrop Vue into a page
Add one script tag and Vue works inside plain HTML, handy for adding interactivity to a page you already have.
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
Start a real project
When you are building something you will keep, scaffold a full setup with Vite in one command.
npm create vue@latestThat is the whole setup. From here it is all about what goes inside a component, starting with the features you reach for on day one.
Every feature is the same idea, in a different shape
You change the state, Vue keeps the view in sync. Try out each one.
Two-way binding
Tie an input to state. Type, and the value stays in sync both ways at once, no listeners to wire up.
Derived values
Compute a value from state. Change the quantity, the total recalculates itself.
Conditional rendering
Render an element only when a condition is true. Flip the switch.
List rendering
Repeat an element for each item in an array. Add a couple of tasks and watch the list grow.
- Learn Vue
- Ship it
You just watched each one stay in sync on its own. That is not magic, it is a single mechanism underneath, and it is worth seeing how it works.
How reactivity works
Reactivity is the machinery behind that loop. Change a value, and Vue updates exactly the parts of the page that depended on it, and nothing else.
import { ref } from 'vue' // a ref is one reactive value, a box Vue watches const count = ref(0) // read and change it with .value count.value++ // 1, the view updates itself
A ref is one reactive value, a box Vue keeps an eye on. You read and change it through .value in your script. While your template renders, Vue quietly notes every ref it read, and the moment one of them changes it re-runs just that piece of the view.
That is the whole trick behind the demos above. The binding, the list, the running total, each one is a ref being tracked as the page renders, then triggering an update when it changes. You never find an element and update it yourself, which is where a whole class of bugs used to live.
refyour statetrack: as it renders, Vue notes every ref the view reads
change .value, and Vue triggers a re-run
The one thing that trips up almost everyone: in the template you write count, but in your script you read and set it as count.value. The template unwraps refs for you; your JavaScript does not. Reach for ref first, it holds anything, a number, a string, an object, or a list.
You can keep one value in sync. Next come the directives, the small pieces of syntax you use to wire that value into what people see and do.
The features you will write every day
Most components come down to a handful of directives. Here is the syntax behind the demos above, the pieces you reach for constantly.
- v-bind
- Bind any attributeSet an attribute from your state. The shorthand is a colon.
<template> <img :src="user.avatar" :alt="user.name" /> <p>Signed in as {{ user.name }}</p> </template>
- v-on
- Handle eventsRun a function on any DOM event. Modifiers like
@submit.preventhandle the boilerplate. <template> <button @click="addToCart">Add to cart</button> <form @submit.prevent="checkout">...</form> </template>
- v-if
- Render conditionallyShow an element only when a condition is true, and
v-elsecovers the other case.v-showis similar but toggles CSS visibility instead. <template> <p v-if="cart.length">You have {{ cart.length }} items</p> <p v-else>Your cart is empty</p> </template>
- v-for
- Render a listRepeat an element for each item in an array. Give it a stable
:key, an id rather than the index. <template> <li v-for="product in products" :key="product.id"> {{ product.name }} </li> </template>
- v-model
- Two-way form binding
v-modelkeeps an input and your state in sync in both directions at once. Here it drives a small to-do list, withv-forrendering it and a stable:keyon each item. <script setup> import { ref } from 'vue' let nextId = 1 const task = ref('') const tasks = ref([]) function addTask() { if (task.value) { tasks.value.push({ id: nextId++, text: task.value }) task.value = '' } } </script> <template> <input v-model="task" @keyup.enter="addTask" placeholder="Add a task" /> <ul> <li v-for="item in tasks" :key="item.id">{{ item.text }}</li> </ul> </template>
- computed
- Derive values from stateWhen a value depends on other state, do not recalculate it by hand. A
computedvalue updates itself whenever the state it reads from changes, the same reactivity from the counter, now doing your arithmetic for you. <script setup> import { ref, computed } from 'vue' const tasks = ref([ { id: 1, done: false }, { id: 2, done: true } ]) const remaining = computed( () => tasks.value.filter(t => !t.done).length ) </script> <template> <p>{{ remaining }} tasks left</p> </template>
That handful of directives covers most of what you will type. Two more pieces finish a single component: reacting when a value changes, and running code as the component comes and goes.
Watching for changes
Sometimes a change should do something, not just show something. That is a watcher: a function Vue runs whenever a value you name changes.
import { ref, watch } from 'vue' const query = ref('') // runs every time query changes watch(query, (value) => { search(value) })
A computed value gives you a new value from your state. A watch does the opposite job: it runs an action when state changes, calling an API, saving to storage, starting a timer. It hands you the new value and lets you decide what to do with it.
computedDerive it from state. It caches, and recomputes only when its inputs change.
- a filtered list
- a running total
- a formatted label
watchRun a side effect, something that reaches outside the component.
- call an API
- save to storage
- start a timer
The trap: reaching for watch to build a value. If it is derived from other state, it is a computed.
A watcher reacts to a value changing. A component also has moments in its own life, when it appears and when it leaves, and you can run code at each one.
The component lifecycle
Every component has a life. It is created, added to the page, and later removed, and you can run code at each of those moments.
import { ref, onMounted } from 'vue' const posts = ref([]) // runs once, after the component mounts onMounted(async () => { posts.value = await fetchPosts() })
These moments are called lifecycle hooks, and they always run in the same order as a component comes and goes. You hook into the few you need, most often just one.
- setupstate and logic are created
- onMountednow on the page: fetch data, measure the DOM
- on the pagereactivity keeps the view in sync
- onUnmountedremoved: clean up timers and listeners
Do not try to read the rendered page before the component has mounted, it is not there yet. onMounted is the earliest moment the DOM exists, which is exactly why it is where your first data fetch usually goes.
You can now build one component that holds state, reacts to it, and loads its own data. Real apps are many of these working together, which starts with passing data between them.
Build with components, props and events
Components are the reason to learn Vue. Each is a small, self-contained piece of a page, with its own markup, logic, and state. A parent passes data down to a child with props, and the child sends messages back up with events. That one pattern is how every real Vue app is put together.
<!-- Parent.vue --> <template> <TaskItem v-for="task in tasks" :key="task.id" :task="task" @toggle="toggleTask" /> </template>
<!-- TaskItem.vue --> <script setup> defineProps(['task']) const emit = defineEmits(['toggle']) </script> <template> <li> <input type="checkbox" @change="emit('toggle', task.id)" /> {{ task.text }} </li> </template>
Props in, events out. Once that clicks, you can build almost anything by composing small components.
Props and events move data between components. Two more tools round out how they fit together: passing markup in, and reaching a value that lives several layers up.
Slots: passing markup in
Props pass data into a component. Slots pass markup. They let a component wrap content it cannot know ahead of time.
<!-- Card.vue --> <template> <div class="card"> <slot /> <!-- the parent's markup lands here --> </div> </template> <!-- using it --> <Card> <h3>Anything you want</h3> </Card>
A slot is a hole a component leaves in its own template. The child draws the frame, the border, the padding, the shadow, and the parent decides what goes inside it. One Card can hold a heading in one place and a whole form in another.
Need more than one hole? Give them names with <slot name="header" />, and the parent fills each by name. This is how layout and wrapper components, cards, modals, buttons, stay reusable.
If you find yourself passing HTML through a prop as a string, you want a slot instead. Props are for data; slots are for markup. Keeping them apart keeps components clean.
Props and slots hand things down one level at a time. When a value is needed many levels deep, there is a way to skip the hand-off.
Provide and inject
When many nested components need the same value, passing it prop by prop gets tedious. An ancestor can offer a value that any descendant picks up directly.
// an ancestor offers a value provide('theme', 'dark') // any descendant, however deep, reads it const theme = inject('theme')
A parent calls provide with a name and a value. Any descendant, however deep, calls inject with the same name and gets it, with none of the components in between having to know about it.
Threading one value through five layers of props just to reach the bottom is called prop drilling, and this is the cure. Reach for it with values a whole subtree shares, like a theme, the current user, or a locale.
- App
provide('user') - Layoutpasses it by
- Sidebarpasses it by
- Avatar
inject('user')
This is not a replacement for props. For a direct parent and child, props are still the clearest choice. Provide and inject earns its place only when a value is shared widely and deeply.
You can move data around a component tree cleanly now. Some state, though, belongs to no single component, it belongs to the whole app, and that is where the next part begins.
Reusable logic with composables
When two components need the same stateful logic, you do not copy it. You lift it into a composable, and both share it.
// useCounter.js import { ref } from 'vue' export function useCounter() { const count = ref(0) const increment = () => count.value++ return { count, increment } }
A composable is simply a function whose name starts with use. Inside, it uses the reactivity you already know, ref, computed, watch, and returns the state and functions a component needs. Call it from anywhere and each caller gets its own independent copy.
This is how you share logic, not just data: fetching, form handling, tracking the mouse. The community keeps a large collection of ready-made ones in VueUse , worth a look before you write your own.
Small, reusable logic keeps components tidy. The next pieces are what turn a set of components into an application, starting with having more than one page.
More than one page: Vue Router
A real app has several pages. Vue Router maps each URL to a component and swaps them in without a full page reload.
const routes = [ { path: '/', component: Home }, { path: '/posts/:id', component: Post } ] // in the template: // <router-link to="/">Home</router-link> // <router-view />
You list your routes, each a path paired with a component. <router-link> moves between them and <router-view> renders whichever one matches the URL. A :id in the path becomes a parameter the page can read.
Routing is an official library rather than part of the core, kept in step with Vue. The Vue Router docs cover nested routes, navigation guards, and the rest when a project needs them.
Each page can hold its own state. But some state, a cart, the signed-in user, is shared across every page, and that needs a home of its own.
App-wide state with Pinia
Props and events pass state between neighbours. When state belongs to the whole app, a store keeps it in one place instead.
// stores/cart.js export const useCart = defineStore('cart', () => { const items = ref([]) const add = (product) => items.value.push(product) return { items, add } })
Pinia is Vue's official store. A store is one place for a piece of state and the functions that change it, written with the same ref and functions you already use. Any component calls the store and reads or updates it directly, with no props to thread through the middle.
Because you have felt the friction of passing state around by hand first, you will know a store when you need one. The Pinia docs take it from there.
Do not start every app with a store. Props and events handle most state cleanly. Reach for Pinia only when passing the same state around by hand starts to hurt, that ache is the signal.
State usually comes from somewhere, a server. Loading it is the last core piece of a real application.
Talking to a server
Most apps show data that lives somewhere else. You fetch it, keep it in a ref, and show a state for loading, for errors, and for the data itself.
const posts = ref([]) const loading = ref(true) onMounted(async () => { const res = await fetch('/api/posts') posts.value = await res.json() loading.value = false })
The shape is always the same: a ref for the data, one for whether it is loading, and the fetch in onMounted so it runs once the component is ready. Everything from reactivity and the lifecycle comes together right here.
Lift that into a composable, usePosts(), and any component can load the same data cleanly. For fetching on the server before the page is even sent to the browser, that is what Nuxt adds on top of Vue.
You now have every moving part of a real app. The last question is where all of it lives.
How a real Vue app is organised
As an app grows, where things go stops being obvious. A conventional folder layout keeps it easy to move around.
src/ components/ reusable UI pieces composables/ shared logic (useX) stores/ Pinia state views/ routed pages router/ route definitions
Nothing here is enforced by Vue, but almost every project settles on something close to it. Components hold UI, composables hold shared logic, stores hold app-wide state, and views are the pages your router points at.
The value is predictability: anyone opening the project knows where to look. Vue's own create-vue scaffolding gives you this shape from the first command.
That is a complete Vue application: pages, shared state, data, and a place for everything. What is left is depth, and knowing where to get it.
Go deeper on any piece
You have seen how the whole picture fits together. When you are ready to go deep on one part, here is where each is taught in full.
- Core
Reactivity
State the view follows, with
refandreactive. You met it in the counter above. - Core
Components
Split a page into reusable pieces, passing data down through props and sending messages back up through events.
- Core
Composition API
ref,computed,watch, and composables. The way modern Vue is written, and what most jobs now expect. - Routing
Vue Router
Turn your app into many pages without a full reload.
- State
Pinia
Share state across your whole app once passing props around gets tiring.
- Tooling
Vite
The build tool that runs your project in development and ships it for production, fast.
Options API or Composition API?
Vue has two ways to write a component. Learn the Composition API, the script setup style used in every example on this page. It is how modern Vue is written, what current courses teach, and what most job postings now expect.
You will still meet the older Options API in existing codebases, and it reads easily once you know the modern style, so you lose nothing by starting there.
| Composition API Recommended | Options API | |
|---|---|---|
| Style | script setup, functions and imports | one object with data, methods, and mounted |
| Best for | modern apps, shared logic, and TypeScript | reading and maintaining older code |
| For you | write your new Vue here | recognize it when you see it |
From your first component to production
You have seen the whole picture. This is the course path that turns it into practice, from the free fundamentals to a real application and, when you are ready, a certification.
- Step 01
The basics
Reactivity, components, and template syntax, the free Vue.js Fundamentals course, end to end.
- Step 02
Composition API
ref, computed, watch, and writing your own composables, the way modern Vue is built.
- Step 03
A real application
Vue Router, Pinia, and Vite, wired into a complete app in the Vue.js 3 Master Class .
- Step 04
Prove it
Testing, performance, and the Vue.js certification when you are ready to show what you know.
How long does it take to learn Vue?
You can build a small interactive component this afternoon. Getting comfortable enough to build and ship a real application usually takes a few weeks of steady practice, faster if your JavaScript is already solid.
Is Vue worth learning in 2026?
Yes. Vue has a stable, modern core in the Composition API, it pairs with Nuxt for full-stack and server-rendered work, and it runs in production at companies like GitLab, Nintendo, and Adobe. It is one of the easier frameworks to learn well, so the time you put in pays off quickly and the skills stay in demand.
- Stable, modern coreThe Composition API is how Vue 3 is written, and it is not going anywhere.
- Full-stack with NuxtThe same skills carry into server-rendered sites and complete applications.
- In production at scaleGitLab, Nintendo, and Adobe ship Vue to real users every day.
Why learn Vue with
Vue School
Vue School has taught Vue since the framework's earliest days. The courses are built by people who work in the ecosystem every day, including Alex Kyriakidis, who wrote the first book on Vue.js, and Daniel Kelly, our lead instructor.
The path stays current with how Vue is actually written, so the skills you build are the ones teams are hiring for. For the source of record, the official docs at vuejs.org , along with Vue Router and Pinia , sit alongside everything you learn here.
“VueSchool is always the first resource we recommend our customers use for learning Vue.js and Nuxt. We also use it internally to level up the skills of our own developers and catch up with the latest additions to the ecosystem and best practices. I couldn’t recommend VueSchool more!”

2,000,000+
developers trained worldwide, since Vue’s earliest days.
Engineers at these teams learn Vue with us








Frequently asked questions
How do I start learning Vue.js?
Start by running a small component, like the counter above, then take a structured beginner course so the concepts arrive in the right order. Our free Vue.js Fundamentals course is built for exactly this first step.
Can I learn Vue.js for free?
Yes. The Vue.js Fundamentals course is free and covers the core of the framework end to end, and the examples on this page run for free in the Vue Playground. You can go a long way before anything costs money.
Do I need to know JavaScript before learning Vue?
Yes, the basics. If you are comfortable with variables, functions, arrays, and objects, you have enough to start. You do not need to be a JavaScript expert, and you do not need TypeScript.
Should I learn Vue or React?
Both are strong choices and the core ideas carry across, so you rarely lose by learning either. Vue is often the friendlier place to start, with less boilerplate and a gentle learning curve, and this free Vue tutorial teaches it end to end so you can judge from real experience.
Is Vue hard or easy to learn?
Vue is easy to learn compared to other frameworks. If you know some JavaScript, you can have something working the same day, and the learning curve stays gentle as you go deeper. That gentle curve is a big part of why people pick it.
Ready to watch it react?
The Vue.js Fundamentals course is free. Start with your first component today.