# Vue 3 Composition API Standards · VUE-02

[The Composition API](https://vuejs.org/guide/extras/composition-api-faq.html) in Vue is a powerful and flexible way to write components, offering an alternative to the Options API used in Vue 2.

It provides a more granular and function-based approach to managing component logic.

## Standard Metadata

- Area: Vue
- Owner: Frontend Steering Group
- Last reviewed: 2026-09-09
- Next review due from: 2026-12-01
- Enforcement: Code review
- Status: Active

## Use `ref` over `reactive` for State Management · VUE-02.01 · **MUST**

Providing explicit reactivity control through `ref` improves performance with more granular reactivity. With Typescript, these provide strong type inference.

The same reactivity is exposed via `reactive`, but it has many limitations that `ref` does not. Furthermore, `reactive` causes maintainability issues.

Links supporting why we use `ref` over `reactive`:

- [Vue JS Docs Limitations of Reactive](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive)
- [Please stop using reactive() in Vue 3 Composition API](https://michalkuncio.com/please-stop-using-reactive-in-vue-composition-api)
- [Should I Use Ref or Reactive in Vue 3?](https://www.telerik.com/blogs/should-use-ref-reactive-vue-3)

### Key Points 🔑

- Vue.js recommend using `ref` as the primary API for declaring reactive state. In 99% of cases, you will want to use `ref` for reactivity.
- `reactive` only takes objects, and not JS primitives (`string`, `boolean`, `number`, etc.)
- `ref` works on any type, primitive or complex.
- `ref` is calling `reactive` behind the scenes, so they both work with objects.
- `ref` has a `.value` property for reassignging, `reactive` does not and therefore _cannot be reassigned_.
- Due to the above reasons, in most cases you should use `ref` for reactivity.

### Use `ref` for Reactive State · VUE-02.01.01 · **MUST**

Use `ref` when:

- Creating a reactive state of a primitive.
- Creating a reactive state for an object you need to later reassign (such as an `array`).
- Directly binding values to the component template.

#### `ref` Example

```ts
<script setup>
import { ref } from 'vue';

// Use ref for a single reactive variable
const count = ref(0);

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

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>
```

### Only use `reactive` for Reactive State of Immutable Objects · VUE-02.01.02 · **MUST**

Use `reactive` when:

- Creating a reactive state of a complex `object` or `array`.
- Working with an object you don't need to reassign.

#### `reactive` Example

```ts
<script setup>
import { reactive } from 'vue';

// Use reactive for an object with multiple properties
const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
  age: 30
});

function updateAge() {
  user.age++;
}
</script>

<template>
  <div>
    <p>{{ user.firstName }} {{ user.lastName }} - Age: {{ user.age }}</p>
    <button @click="updateAge">Increase Age</button>
  </div>
</template>
```

### Further limitations of `reactive`

Using `reactive` has [some limitations](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive) due to being an unwrapped JS `Proxy`.

The value type you assign to `reactive` cannot be reassigned, only the properties may be manipulated.

It also does not support destructuring, and any destructured properties will lose reactivity. This can lead to unmaintainable code.

## Use Lifecycle Hooks to manage behaviour at different component stages · VUE-02.02 · **MUST**

### Use `onMounted()` to Encapsulate Logic that Needs to Run After the Component is Added to the DOM · VUE-02.02.01 · **MUST**

Ensure consistent and efficient use of the `onBeforeMount`, `onMounted`, `onUpdated`, `onErrorCaptured` lifecycle hook in Vue components for executing code after the component is mounted.

#### `onMounted()` Example (✅)

```ts
<script setup>
import { ref, onMounted } from 'vue';

const message = ref('');

onMounted(() => {
  message.value = 'Component is mounted!';
  initializeComponent();
});

function initializeComponent() {
  // Additional initialization logic
}
</script>
```

### Use `onBeforeMount()` to Encapsulate Logic that Needs to Run Before the Component is Added to the DOM · VUE-02.02.04 · **MUST**

```ts
<script setup>
import { onBeforeMount, onUpdated } from 'vue';

onBeforeMount(() => {
  console.log('Component is about to be mounted.');
});

</script>
```

### Use `onBeforeUpdate()` & `onUpdated()` to Encapsulate Logic that Needs to Run When the Component Updated on the DOM · VUE-02.02.02 · **MUST**

#### Reactive State Example

```ts
<script setup>
import { onBeforeMount, onUpdated } from 'vue';

const count = ref(0)
const val = ref(0)

onBeforeUpdate(() => {
  count.value++
  console.log('beforeUpdate')
})

onUpdated(() => {
  console.log('updated() val: ' + val.value)
})
</script>
```

### Use `onBeforeUnmount()` & `onUnmounted()` to Encapsulate Logic that Needs to Run When the Component is Being Destroyed · VUE-02.02.03 · **MUST**

#### Cleanup Example

```ts
<script setup>
import { onBeforeUnmount, onUnmounted } from 'vue';

onBeforeUnmount(() => {
  console.log('Component is about to be unmounted.');
});

onUnmounted(() => {
  console.log('Component has been unmounted.');
});
</script>
```

### Use `onErrorCaptured()` to Encapsulate Logic that Runs for Errors In Child Components · VUE-02.02.05 · **MUST**

#### Error Handling Example

```ts
<script setup>
import { onErrorCaptured } from 'vue';

onErrorCaptured((error, instance, info) => {
  console.error('Error captured:', error, info);
  return false; // Prevent further propagation
});
</script>
```

### Use Watchers `watchEffect` & `watch` to Encapsulate Logic that Runs on Reactive Effects · VUE-02.02.06 · **MUST**

#### Watchers Example

```ts
<script setup>
import { ref, watch, watchEffect } from 'vue';

const count = ref(0);

watch(count, (newVal) => {
  console.log('Count changed:', newVal);
});

watchEffect(() => {
  console.log('Count is:', count.value);
});
</script>
```

## Use Slots for Flexible Component Composition · VUE-02.3 · **MUST**

Slots provide a way to compose components and pass content into them. They allow for flexible and reusable component design by letting parent components pass content into child components.

Slots in Vue are a way to pass content from a parent component into a child component. They allow you to define placeholders in a component’s template where dynamic content can be inserted.

This makes components highly flexible and reusable, as they enable injection of customizable content based on their usage context.

### Use Default Slots for Singular Data Transmission · VUE-02.3.1 · **MUST**

Use default slots to pass content into a component when no specific slot name is provided.

#### Default Slots Example (✅)

##### Default Slots ParentComponent.vue

```ts
<template>
  <ChildComponent>
    <p>This is default slot content.</p>
  </ChildComponent>
</template>
```

##### Default Slots ChildComponent.vue

```ts
<template>
  <div>
    <slot></slot> <!-- Placeholder for default slot content -->
  </div>
</template>
```

### Use Named Slots when Placing Multiple Outlets in a Single Component · VUE-02.3.2 · **MUST**

Named Slots allow for more control over where the content is placed within the child component's template.

#### Named Slots Example (✅)

##### Named Slots ParentComponent.vue

```ts
<template>
  <ChildComponent>
    <template #header>
      <h1>Header Content</h1>
    </template>
    <template #footer>
      <p>Footer Content</p>
    </template>
  </ChildComponent>
</template>
```

##### Named Slots ChildComponent.vue

```ts
<template>
  <div>
    <header>
      <slot name='header'></slot>
    </header>
    <main>
      <p>Main Content</p>
    </main>
    <footer>
      <slot name='footer'></slot>
    </footer>
  </div>
</template>
```

### Use Scoped Slots to Share Reusable Logic or Structure Across Different Components with Varying Data or Content · VUE-02.3.3 · **MUST**

Scoped Slots allows a parent component to access the state in a child component, enabling more flexible and reusable component design.

#### Scoped Slots Example (✅)

##### Scoped Slots ParentComponent.vue

```ts
<template>
  <ItemList :items="items">
    <template #default="{ item }">
      <div>{{ item.name }} - {{ item.description }}</div>
    </template>
  </ItemList>
</template>

<script setup>
import { ref } from 'vue';
import ItemList from './ItemList.vue';

const items = ref([
  { name: 'Item 1', description: 'Description 1' },
  { name: 'Item 2', description: 'Description 2' }
]);
</script>
```

##### Slots ChildComponent.vue

```ts
<template>
  <div>
    <div v-for="item in items" :key="item.name">
      <slot :item="item"></slot>
    </div>
  </div>
</template>

<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  items: {
    type: Array,
    required: true
  }
});
</script>
```

## Use `setup` Function for Component Initialization · VUE-02.4 · **MUST**

The `setup()` hook centralises all component initialisation and initialises reactive state. Making it easier to understand a component's dependencies, and the logic it encapsulates.

### Setup Example

```ts
<template>
  <div>{{ message }}</div>
</template>

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

export default {
  setup() {
    const message = ref('Hello, Composition API!');

    return {
      message,
    };
  },
};
</script>
```
