Skip to content

Alpine.js x-ref Directive — Complete Guide with Examples

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you'll learn about the Alpine.js x-ref directive. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Alpine.js x-ref directive assigns a name to a DOM element so you can reference it directly in Alpine expressions using the $refs magic property.

What You'll Learn

By the end of this tutorial, you'll use x-ref to access DOM elements, read their properties, integrate with third-party libraries, and manage focus and measurements.

Why It Matters

Sometimes you need direct access to a DOM element: to focus an input, measure its dimensions, scroll to it, or pass it to a third-party library. Alpine's reactivity handles state, but x-ref bridges the gap to raw DOM access.

Real-World Use

Doda Browser's search bar uses x-ref to auto-focus the input when the user presses Ctrl+K. The ref provides direct access to the input element for calling .focus() without needing document.querySelector.

Where This Fits in Your Learning Path

flowchart LR
    A["x-html Directive"] --> B["**x-ref Directive**"]
    B --> C["x-teleport Directive"]
    C --> D["x-init & x-effect"]
    D --> E["Advanced Alpine Patterns"]
    style B fill:#f97316,stroke:#c2410c,color:#fff
    style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
    style E fill:#22c55e,stroke:#16a34a,color:#fff

What is x-ref?

x-ref assigns a reference name to an element. You access it via $refs.name in any Alpine expression within the same component.

Think of x-ref like putting a sticky note on an item. Instead of searching through the room (the DOM) every time you need it, the sticky note tells you exactly where it is.

<div x-data>
  <input type="text" x-ref="nameInput" placeholder="Enter name">
  <button @click="$refs.nameInput.focus()">Focus Input</button>
</div>

Expected output: Clicking the button places focus on the input field.

Reading Element Properties

Use $refs to read element properties like value, offsetHeight, or scrollTop.

<div x-data="{ scrollPos: 0 }">
  <div x-ref="scrollContainer" @scroll="scrollPos = $refs.scrollContainer.scrollTop" style="height:100px;overflow:auto" class="border p-2">
    <div style="height:300px">
      Scroll down to see position
    </div>
  </div>
  <p>Scroll position: <span x-text="scrollPos"></span></p>
</div>

Expected output: As you scroll the container, the scroll position updates in real time.

Integrating with Third-Party Libraries

Pass $refs elements to external libraries like charts or date pickers.

<div x-data x-init="() => {
  // Simulate initializing a chart on the canvas
  const ctx = $refs.canvas.getContext('2d')
  ctx.fillStyle = 'blue'
  ctx.fillRect(10, 10, 100, 50)
}">
  <canvas x-ref="canvas" width="200" height="100" class="border"></canvas>
</div>

Expected output: A blue rectangle drawn on the canvas element.

Managing Focus with $nextTick

When you need to focus an element after a state change, use $nextTick to wait for Alpine to update the DOM.

<div x-data="{ editing: false, name: 'Alice' }">
  <template x-if="editing">
    <input x-ref="editInput" x-model="name" @keydown.escape="editing = false"
           x-init="$nextTick(() => $refs.editInput.focus())">
  </template>
  <button @click="editing = true" x-show="!editing">Edit</button>
  <p x-text="name"></p>
</div>

Expected output: Clicking Edit replaces the name with an input that is immediately focused. Pressing Escape cancels editing.

Common Mistakes

1. Trying to access $refs before the element exists

If the element is behind an x-if or x-show, $refs may be undefined. Always use optional chaining or check existence.

2. Using $refs outside an Alpine expression

$refs is only available inside Alpine expressions (x-text, x-init, @click, etc.). It is not available in external script tags.

3. Using $refs between different components

Each component has its own $refs scope. You cannot access a ref from a parent or sibling component directly.

4. Forgetting that $refs is not reactive

$refs provides a reference to the DOM element itself, not a reactive property. Changes to the element's properties do not trigger Alpine updates.

5. Using the same ref name multiple times

If multiple elements share the same x-ref name, $refs.name returns the last one in the DOM. Use unique names.

Practice Questions

  1. How do you access a referenced element? Use $refs.refName in any Alpine expression within the same component.

  2. What can you do with a $refs reference? Call DOM methods (focus, click, scroll), read properties (value, offsetHeight, scrollTop), and pass to libraries.

  3. Why use $nextTick with $refs? $nextTick ensures the DOM has updated before accessing the ref, which is necessary after Alpine creates or modifies elements.

  4. Is $refs available across components? No. Each component has its own isolated $refs scope.

  5. Can you use x-ref on SVG elements? Yes. x-ref works on any DOM element, including SVG.

Challenge

Build an inline editor that switches between text display and an input on click. Use x-ref and $nextTick to auto-focus the input when it appears. On Enter or blur, save and switch back.

FAQ

What is the difference between x-ref and id?

An id is globally unique and accessible via document.getElementById. x-ref is scoped to the Alpine component and accessed via $refs.

Can I use $refs in x-init?

Yes. $refs is available in x-init. However, references to elements inside x-if templates are not available until the condition is true.

Does $refs work with Alpine's scope nesting?

Yes. A child component can access its own $refs, but not its parent's $refs. Each component has a separate $refs scope.

What happens if the referenced element is destroyed?

The $refs property becomes undefined. Always check for existence before accessing properties on $refs.

Can I modify DOM attributes via $refs?

Yes. Use $refs.element.setAttribute or directly set properties like $refs.input.value = 'new value'.


Mini Project

Build a draggable panel using x-ref. The panel header is the drag handle. Use mouse events to read the mouse position and apply it as CSS transform on the panel element via $refs.

<div x-data="{ dragging: false, offsetX: 0, offsetY: 0, posX: 0, posY: 0 }"
     @mousemove="if (dragging) { posX = $event.clientX - offsetX; posY = $event.clientY - offsetY }"
     @mouseup="dragging = false">
  <div x-ref="panel"
       :style="`transform: translate(${posX}px, ${posY}px); cursor: ${dragging ? 'grabbing' : 'grab'}`"
       class="absolute w-64 p-4 bg-white shadow-lg rounded-lg border">
    <div @mousedown="offsetX = $event.offsetX; offsetY = $event.offsetY; dragging = true"
         class="font-bold mb-2 pb-2 border-b" style="cursor: grab">
      Drag me
    </div>
    <p>This panel can be dragged around the page.</p>
  </div>
</div>

What's Next

Continue with advanced directives:

Tutorial What You'll Learn
x-teleport Directive Move elements to different parts of the DOM
x-init Directive Run initialization code in components

Related topics: DOM API reference, event handling.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro