Progressive Framework

Vue

Vue is the progressive framework: adopt as little or as much as you need, with a template syntax that feels like writing HTML and reactivity that just works.

intermediate15 min readUpdated Sep 15, 2026
Counter.vue
vue
<!-- Counter.vue -->
<script setup>
import { ref } from "vue";

const count = ref(0);
</script>

<template>
  <button @click="count++">
    Clicked {{ count }} times
  </button>
</template>
First release
2014, by Evan You
Current major
Vue 3
Model
Progressive framework
Reactivity
ref, reactive, computed
Components
Single-file .vue files
Tooling
Vite, Nuxt, Vue Router, Pinia

Why it matters

Why developers like Vue

Approachable templates

The template syntax is HTML with a few directives, so designers and newcomers can read a component without learning JSX first.

Automatic reactivity

Change a value and the view updates. Vue tracks dependencies for you, so there is no manual diffing or setState.

Adopt it gradually

Drop Vue into a single page with a script tag, or build a full application with Nuxt. The same model scales both ways.

The big picture

The three ideas behind Vue

A template, a reactivity system and single-file components. Vue keeps the first approachable and makes the second invisible.

Templates

Markup

HTML extended with directives like v-if, v-for and v-model that bind the view to your data.

Reactivity

Data

ref and reactive make values observable, while computed and watch respond to changes.

Components

Composition

Single-file components keep template, logic and scoped styles together in one .vue file.

Vue at a glance

The core of Vue

Single-file components

Template, script and scoped style in one .vue file.

Template syntax

Interpolation with double braces and directives like v-if and v-for.

ref and reactive

The two reactivity primitives for holding state in the Composition API.

computed and watch

Derive values automatically or run side effects when something changes.

Props and emits

Pass data down with props and send events up with emits.

The Composition API

Group logic by feature in script setup instead of scattering it across options.

A short history

From side project to a top-three framework

  1. 2014

    Vue 1

    Evan You releases Vue after working on Angular, aiming for a lighter, more approachable framework.

    14
  2. 2016

    Vue 2

    The virtual DOM and a thriving ecosystem make Vue one of the most popular frameworks in the world.

    16
  3. 2020

    Vue 3

    The Composition API, better TypeScript support and a faster reactivity system arrive.

    20
  4. 2022

    script setup

    A compiler macro that makes the Composition API concise and the recommended default.

    22
  5. Today

    A stable favourite

    Vue remains a top choice for teams that want power without a steep learning curve.

    Today

The complete guide

Vue: Everything you need to know

What is Vue?

Vue is a progressive JavaScript framework for building user interfaces. The word is deliberate: you can drop Vue into a single page and enhance one widget, or use it with a build tool to build a full application. There is no rewrite required to get value, and the framework grows with your needs.

At its heart Vue is two things: a template syntax that extends HTML, and a reactivity system that tracks your data and updates the view automatically. You declare what the UI should show, and Vue keeps it in sync.

Single-file components

A Vue component usually lives in a .vue file with three blocks: a template, a script and styles. Keeping them together makes a component a single, self-contained unit.

<!-- Card.vue -->
<script setup>
defineProps({ title: String });
</script>

<template>
  <article class="card">
    <h3>{{ title }}</h3>
    <slot />
  </article>
</template>

<style scoped>
.card {
  border-radius: 1rem;
  padding: 1.5rem;
}
</style>

<style scoped> automatically limits the CSS to this component, so class names cannot leak out. <script setup> is a compiler macro that makes the Composition API concise and is the modern default.

Template syntax

Vue templates are HTML with a small set of directives. Interpolation uses double braces, and directives are attributes prefixed with v-.

<!-- profile.vue -->
<template>
  <h1>{{ user.name }}</h1>
  <p v-if="user.online">Online</p>
  <p v-else>Offline</p>

  <ul>
    <li v-for="post in posts" :key="post.id">
      {{ post.title }}
    </li>
  </ul>

  <img :src="user.avatar" :alt="user.name" />
  <button @click="follow">Follow</button>
</template>

Read the shorthands as: :src binds an attribute to an expression, and @click listens for an event. v-if conditionally renders, and v-for loops with a :key for efficient updates. The template compiles to render functions, but you rarely need to see them.

Reactivity: ref and reactive

Vue’s reactivity system watches your data and re-renders when it changes. In the Composition API you create reactive values with ref or reactive.

<script setup>
import { ref, reactive } from "vue";

const count = ref(0);           // .value in script
const user = reactive({         // proxy, no .value
  name: "Ada",
  online: true,
});

function increment() {
  count.value += 1;
}
</script>

ref wraps any value and is accessed through .value in script, though templates unwrap it for you. reactive returns a deeply reactive proxy for an object. A common convention is to use ref consistently, since it works for primitives and is easy to reassign.

Computed and watch

Two functions handle derived values and side effects.

<script setup>
import { ref, computed, watch } from "vue";

const items = ref([10, 20, 30]);

const total = computed(() =>
  items.value.reduce((sum, n) => sum + n, 0),
);

watch(items, (next, prev) => {
  console.log("items changed", prev, next);
});
</script>

computed caches its result and recalculates only when a dependency changes — use it whenever a value is derived from other state. watch runs a callback when a source changes and is for side effects such as logging, fetching or saving. Reaching for watch to compute a value is a common mistake.

Props, emits and slots

Data flows down through props and up through emitted events.

<!-- Child.vue -->
<script setup>
const props = defineProps({
  label: { type: String, required: true },
});
const emit = defineEmits(["select"]);

function handleClick() {
  emit("select", props.label);
}
</script>

<template>
  <button @click="handleClick">{{ label }}</button>
</template>
<!-- Parent.vue -->
<Child label="Save" @select="onSelect" />

Props are read-only in the child, which keeps the source of truth in the parent. Slots let a parent pass markup into a child, the equivalent of React’s children.

Composables

The Composition API’s biggest win is reusable logic. A composable is a function that uses reactivity and returns state, named with a use prefix.

// useFetch.js
import { ref, watchEffect } from "vue";

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);

  watchEffect(async () => {
    data.value = null;
    error.value = null;
    try {
      const res = await fetch(url.value);
      data.value = await res.json();
    } catch (err) {
      error.value = err;
    }
  });

  return { data, error };
}

Composables share logic, not state, so each component that calls useFetch gets its own independent data. This is the Vue equivalent of a custom hook.

Routing and state

Vue Router handles navigation with a declarative mapping from URL to component, and Pinia is the official store for shared state.

// router.js
import { createRouter, createWebHistory } from "vue-router";

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: "/", component: Home },
    { path: "/users/:id", component: UserProfile },
  ],
});

Reach for a store when several distant components need the same state. For local and parent-child data, props and composables are usually enough.

Best practices

  • Use script setup and the Composition API for new code.
  • Prefer computed over watch for derived values.
  • Keep props read-only and emit events for changes.
  • Extract reusable logic into composables.
  • Use scoped styles, or CSS Modules, to keep styles local.
  • Name composables with a use prefix.
  • Avoid reaching into child state; communicate through props and emits.

Common mistakes

  • Forgetting .value on a ref inside script.
  • Using watch to compute a value that computed should derive.
  • Mutating props in a child instead of emitting an event.
  • Destructuring a reactive object and losing reactivity.
  • Putting all state in a global store when local state would do.
  • Mixing the Options API and Composition API without a clear reason.

Where to go next

Vue is a gentle entry into component frameworks and scales all the way up. Add Nuxt for server rendering and routing conventions, compare the approach with React and Svelte, and keep your JavaScript sharp. Then build something small — a filterable list or a form with validation — and let the reactivity do the work.

Derived values

Use computed for values that depend on other state. watch is for side effects, not for calculating.

Prefer
const items = ref([1, 2, 3]);
const total = computed(() =>
  items.value.reduce((a, b) => a + b, 0),
);
Avoid
const total = ref(0);
watch(items, () => {
  total.value = items.value.reduce(
    (a, b) => a + b, 0,
  );
});

Binding inputs

v-model handles the value and the update event, so you do not wire them by hand.

Prefer
<input v-model="query" />

<script setup>
const query = ref("");
</script>
Avoid
<input
  :value="query"
  @input="query = $event.target.value"
/>

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Vue?

Our interactive tutorial walks you through Vue step by step — with quizzes and real code you can run in the browser.