Refs Explained: How Frameworks Handle DOM Direct Access
Modern frontend frameworks promise a declarative world where you describe what the UI should look like, and the framework handles the rest. But sometimes you need to step outside that model and touch the actual DOM. That’s where DOM refs come in.
Whether you’re working with React refs, Vue template refs, Angular ElementRef, or Svelte bind:this, every major framework provides an escape hatch for direct DOM access. Understanding when and how to use these tools—without breaking framework guarantees—separates competent developers from those who create subtle, hard-to-debug problems.
Key Takeaways
- Refs provide controlled escape hatches for direct DOM access when declarative patterns fall short
- Common use cases include focus management, scrolling, layout measurement, and third-party library integration
- Each framework implements refs differently: React uses mutable ref objects, Vue uses template refs with
defineExpose(), Angular providesElementRefandRenderer2, and Svelte usesbind:this - Refs only exist on the client after mount—always guard access with lifecycle checks during SSR
Why Frameworks Provide Refs
Frameworks manage the DOM through abstraction layers. React and Vue use a virtual DOM. Angular uses change detection. Svelte compiles away the framework entirely. These abstractions enable efficient updates, but they also mean the framework owns the DOM structure.
Direct DOM access becomes necessary when declarative patterns fall short. The browser provides APIs that simply cannot be expressed as state-to-UI mappings.
Common Use Cases for Direct DOM Access
Focus management tops the list. Calling .focus() on an input element requires a reference to that element. No amount of state manipulation will move the cursor into a text field.
Scrolling presents similar challenges. Programmatically scrolling to a specific element or position requires imperative DOM calls.
Measuring layout demands direct access. You cannot know an element’s dimensions or position until it exists in the DOM. Reading getBoundingClientRect() or integrating with ResizeObserver and IntersectionObserver requires a real node reference.
Third-party library integration often necessitates refs. Libraries like D3, video players, or canvas-based tools expect DOM nodes they can manipulate directly.
The Core Trade-Off
Refs break the declarative model. When you grab a DOM node and manipulate it directly, you’re working outside the framework’s knowledge. This creates tight coupling between your code and the rendered structure.
Use refs sparingly. If you find yourself reaching for a ref to solve a problem that could be handled through state or props, reconsider. Refs should remain an escape hatch, not a primary tool.
Discover how at OpenReplay.com.
Framework-Specific Implementations
React Refs
In React 19, refs can be passed as normal props to function components. The forwardRef wrapper is no longer mandatory for most use cases, simplifying component composition significantly.
In React 19, callback refs can return cleanup functions, allowing you to detach event listeners or perform teardown when the element unmounts (older refs still receive null for backward compatibility). Be aware that Strict Mode may invoke ref callbacks more than once during development—your code should handle this gracefully.
React refs are mutable containers. Changing .current doesn’t trigger re-renders, which makes them ideal for storing DOM references without causing update cycles.
Vue Template Refs
Vue exposes refs through the ref attribute in templates. In the Composition API, you create a ref with ref(null) and access the element after mount.
Vue encourages explicitly exposing component internals through defineExpose(). This prevents accidental coupling to implementation details while still allowing controlled access when needed.
Angular ElementRef
Angular provides ElementRef and Renderer2 for DOM access. The documentation explicitly labels these as last-resort tools. Using ElementRef does not automatically make DOM access “safe”—you still bypass Angular’s abstractions. Renderer2 mainly helps with platform abstraction (like SSR), not security.
Prefer Angular’s built-in directives and bindings whenever possible. Reserve ElementRef for cases where no declarative alternative exists.
Svelte bind:this
Svelte uses bind:this to capture element references. The binding populates after the component mounts, meaning you cannot access the element during initial script execution.
DOM access in Svelte happens only on the client after mount, typically via onMount or $effect (Svelte 5). Server-side rendering produces HTML without executing client-side bindings, so refs remain undefined until hydration completes.
SSR and Hydration Timing
Across all frameworks, refs only exist on the client after mount. During server-side rendering, there is no DOM—only HTML strings. Your code must account for this.
Guard ref access with lifecycle checks. In React, access refs in effects. In Vue, use onMounted. In Svelte, use onMount or reactive statements that run after hydration. Attempting to access refs during SSR will yield undefined values or errors.
When to Reach for Refs
Ask yourself: can this be solved declaratively? If yes, avoid the ref. If the browser API genuinely requires a DOM node—focus, scroll, measure, or integrate—then refs are the right tool.
Keep ref usage isolated. Wrap imperative logic in custom hooks or composables. This contains the coupling and makes the escape hatch explicit to other developers reading your code.
Conclusion
Refs aren’t deprecated or discouraged. They’re a deliberate part of every framework’s design. Use them intentionally, understand their trade-offs, and your applications will remain maintainable while still accessing the full power of the browser platform.
FAQs
Technically yes, but you should avoid it. Directly manipulating styles or content bypasses the framework's rendering cycle, which can lead to inconsistencies between your component state and the actual DOM. Use state-driven styling and content updates instead. Reserve refs for operations that truly require a DOM node, such as focus, scroll, or measurement.
Refs populate only after the component mounts. If you access a ref during initial render or in server-side rendering, it will be null or undefined. Always guard ref access with lifecycle hooks like useEffect in React, onMounted in Vue, or onMount in Svelte to ensure the DOM element exists before you interact with it.
Generally no. Refs create tight coupling between components and bypass the standard data flow. Prefer props and callbacks for parent-child communication. Use refs only when you need direct DOM access, such as triggering focus or scroll on a child element. In Vue, use defineExpose to control what a child component reveals.
The concept is similar, but implementations differ. React uses mutable ref objects with a current property. Vue uses template refs accessed via the Composition API. Angular provides ElementRef and Renderer2. Svelte uses bind:this directives. Each approach reflects the framework's architecture, so consult framework-specific documentation for correct usage.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.