x-ref and $refs
Usually Alpine changes the page by itself, from data. But sometimes you need the element itself: put the cursor into a field, scroll a list, measure a size. The x-ref directive gives an element a name, and the $refs object returns the element by that name — no querySelector needed.
Naming and reaching
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data>
<input x-ref="title" placeholder="New task">
<button @click="$refs.title.focus()">Go to the field</button>
</div>Clicking the button puts the cursor into the input. $refs.title is a regular DOM element with every method from the tutorial lessons on working with the page.
Focus after adding
In the to-do list it is handy to return the cursor to the field after a task is added, so the next one can be typed right away:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data="{
title: '',
tasks: [],
add() {
if (this.title === '') return;
this.tasks.push(this.title);
this.title = '';
this.$refs.title.focus();
}
}">
<input x-ref="title" x-model="title" @keydown.enter="add()" placeholder="New task">
<button @click="add()">Add</button>
<p x-text="tasks.join(', ')"></p>
</div>Click the button with the mouse — the task is added and the cursor returns to the field. Inside methods $refs is reached through this, just like the data.
Scrolling a list
Another common case is scrolling a container to its last item:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data="{ tasks: ['Task 1', 'Task 2', 'Task 3'] }">
<ul x-ref="list" style="height: 80px; overflow: auto; border: 1px solid #ccc">
<template x-for="task in tasks">
<li x-text="task"></li>
</template>
</ul>
<button @click="tasks.push('Task ' + (tasks.length + 1)); $nextTick(() => $refs.list.scrollTop = $refs.list.scrollHeight)">
Add
</button>
</div>Each new task is added at the bottom, and the list scrolls to it. $nextTick waits until Alpine has added the item to the page — more on it in the lesson on magic properties.
When you do not need $refs
If a task can be solved with data, solve it with data. Reading a field's text is x-model, not $refs.title.value. Hiding an element is x-show, not $refs.box.style.display. Keep $refs for actions that data cannot express: focus, scrolling, sizes, calling methods of third-party libraries.
Common mistake
Reaching for an x-ref from a sibling component. $refs sees the elements of its own component and of the components it sits inside, but not of components next to it. If the button and the field are in two sibling elements with x-data, $refs.title is undefined and the focus() call throws an error. Put them in one component, or send a signal with an event through $dispatch.