ref()
ref() takes an initial value and returns a reactive ref object. You read and write the value through .value in script, while templates unwrap a top-level ref automatically.
Definition
ref() is the primary way to declare reactive state in Vue's Composition API. You call it with an initial value and it returns a *ref object* — a reactive container that holds that value and lets Vue track every read and write. It accepts any type: a number, a string, a boolean, an array, or a nested object, which it makes deeply reactive.
The companion to ref() is computed(), which derives a read-only ref from existing state, and its wider context is the Vue reactivity model. Where reactive() handles only objects, ref() handles a single value of any type, which is why it is the default choice for component state.
Reaching for ref() first keeps a component consistent: one function covers a counter, a flag, a text field and a nested object alike, and the explicit .value in your script code makes every read and write of reactive state visible on the page. That uniformity is why most Vue teams treat ref() as the baseline and reserve other tools for the cases that genuinely need them.
Syntax
import { ref } from 'vue';
const count = ref(0); // initial value → ref object
count.value; // read (in script)
count.value = 5; // write (in script)
// value: any — number | string | boolean | object | array
Parameters
| Parameter | Type | Description |
|---|---|---|
value | any | The initial value the ref holds. Any type is accepted. A primitive is stored as-is; an object or array is made deeply reactive so nested changes are tracked too. This is the only argument. |
There is a single parameter — the initial value. Passing an object means Vue wraps it in a reactive proxy internally, so user.value.name = 'x' is tracked just as a top-level change would be.
Return value
ref() returns a ref object with one public property, .value, which holds the current data. Reading .value inside a render subscribes the component to the ref; writing .value notifies Vue and schedules a re-render of everything that depended on it. The ref object itself is stable — you keep the same reference for the component's lifetime and only its .value changes.
- In
<script setup>you always go through.value—count.valueto read,count.value = nto write. - In a template, a top-level ref is *auto-unwrapped*: write
{{ count }}, not{{ count.value }}. This unwrapping applies only to top-level refs, not to a ref nested inside a plain object. - When the ref holds an object, that object is deeply reactive, so mutating a nested property through
.valueis tracked without reassigning the whole value.
Examples
A counter with ref()
The canonical example: a single numeric ref, incremented on click. The template reads {{ count }} with no .value, because a top-level ref auto-unwraps there.
A ref holding a string and an object
A ref is not limited to numbers. Here one ref holds a string and another holds an object; both are mutated through .value in script, and the object case shows the deep reactivity — changing user.value.role updates the view on its own.
Reading and writing in script
const open = ref(false);
function toggle() {
open.value = !open.value; // .value on both sides, in script
}
Browser & runtime support
ref() is a core part of Vue 3's Composition API and is available in every Vue 3 build — imported by name from the vue package. It works identically in <script setup>, in a plain setup() function, and inside composables you extract into their own modules. There is no configuration to enable it.
Frequently asked questions
What does ref() return in Vue?
.value property holding your data. Read .value to get the current value and assign to .value to change it; in templates the ref auto-unwraps so you use the bare name.Why do I have to use .value with a ref?
.value is how you reach the data it holds inside script code. This indirection is what lets Vue intercept reads and writes to track reactivity. Templates unwrap top-level refs for you, so .value is only needed in script.Can a ref hold an object or array?
ref() accepts any type. When you pass an object or array, Vue makes it deeply reactive, so mutating a nested property through .value is tracked without reassigning the whole ref.