ElementReference is a struct in Blazor that provides direct access to HTML DOM elements from C# code. Normally, Blazor abstracts DOM interaction and doesn't allow direct DOM manipulation. However, certain scenarios require direct access to DOM elements:
- ✅ Setting focus on input elements
- ✅ Drawing on canvas elements
- ✅ Playing video/audio
- ✅ Integration with third-party JavaScript libraries
- ✅ Getting element dimensions/position
Basic Syntax
To create a reference to an HTML element, use the @ref directive:
<input @ref="myInput" />
@code {
private ElementReference myInput;
}
⚠️ Important Rule: OnAfterRender
ElementReference is only valid after the component has been rendered. Therefore, it must be used in the OnAfterRenderAsync lifecycle method:
@code {
private ElementReference myInput;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Element now exists in DOM, reference can be used
await myInput.FocusAsync();
}
}
}
Note: Using ElementReference before the component renders will result in an invalid reference.
Example 1: Auto Focus (Built-in FocusAsync)
Starting with .NET 5, the built-in FocusAsync() extension method allows you to set focus without needing JavaScript:
<input @ref="searchInput" placeholder="Search..." />
@code {
private ElementReference searchInput;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await searchInput.FocusAsync();
}
}
}
Example 2: Focus on Button Click
<input @ref="emailInput" type="email" placeholder="Enter your email" />
<button @onclick="FocusEmailAsync">Go to Email</button>
@code {
private ElementReference emailInput;
private async Task FocusEmailAsync()
{
await emailInput.FocusAsync();
}
}
Example 3: Multiple Element References (Array)
When you need to manage multiple elements, you can use an array of ElementReference:
<input @ref="inputs[0]" placeholder="First Name" />
<input @ref="inputs[1]" placeholder="Last Name" />
<input @ref="inputs[2]" placeholder="Email" />
<button @onclick="() => FocusAsync(0)">Focus First Name</button>
<button @onclick="() => FocusAsync(1)">Focus Last Name</button>
<button @onclick="() => FocusAsync(2)">Focus Email</button>
@code {
private ElementReference[] inputs = new ElementReference[3];
private async Task FocusAsync(int index)
{
await inputs[index].FocusAsync();
}
}
Example 4: Reading Values with JavaScript Interop
You can pass ElementReference to JavaScript functions using IJSRuntime:
Razor Component:
@inject IJSRuntime JS
<input @ref="valueInput" placeholder="Type something..." />
<button @onclick="GetValueAsync">Read Value</button>
<p>Value: @readValue</p>
@code {
private ElementReference valueInput;
private string readValue = string.Empty;
private async Task GetValueAsync()
{
readValue = await JS.InvokeAsync<string>("getElementValue", valueInput);
}
}
JavaScript (elementReference.js):
window.getElementValue = (element) => {
return element.value || '';
};
Example 5: Measuring Element Dimensions
Razor Component:
@inject IJSRuntime JS
<div @ref="sizeElement" style="width: 300px; height: 150px; border: 1px solid #ccc;">
Content to measure
</div>
<button @onclick="GetDimensionsAsync">Measure Dimensions</button>
<p>@dimensionText</p>
@code {
private ElementReference sizeElement;
private string dimensionText = string.Empty;
private async Task GetDimensionsAsync()
{
var dimensions = await JS.InvokeAsync<ElementDimensions>(
"getElementDimensions", sizeElement);
dimensionText = $"Width: {dimensions.Width}px, Height: {dimensions.Height}px";
}
private record ElementDimensions(double Width, double Height);
}
JavaScript:
window.getElementDimensions = (element) => {
const rect = element.getBoundingClientRect();
return { width: rect.width, height: rect.height };
};
Example 6: Scroll Control
Razor Component:
@inject IJSRuntime JS
<div @ref="scrollContainer" style="height: 200px; overflow: auto; border: 1px solid #ccc;">
@for (int i = 1; i <= 100; i++)
{
<p>Line @i</p>
}
</div>
<button @onclick="ScrollTopAsync">Scroll to Top</button>
<button @onclick="ScrollBottomAsync">Scroll to Bottom</button>
@code {
private ElementReference scrollContainer;
private async Task ScrollTopAsync()
{
await JS.InvokeVoidAsync("scrollToPosition", scrollContainer, 0);
}
private async Task ScrollBottomAsync()
{
await JS.InvokeVoidAsync("scrollToBottom", scrollContainer);
}
}
JavaScript:
window.scrollToPosition = (element, position) => {
element.scrollTop = position;
};
window.scrollToBottom = (element) => {
element.scrollTop = element.scrollHeight;
};
Example 7: Working with Canvas
Razor Component:
@inject IJSRuntime JS
<canvas @ref="canvasElement" width="400" height="200" style="border: 1px solid black;"></canvas>
<button @onclick="DrawRectangleAsync">Draw Rectangle</button>
@code {
private ElementReference canvasElement;
private async Task DrawRectangleAsync()
{
await JS.InvokeVoidAsync("drawRectangle", canvasElement);
}
}
JavaScript:
window.drawRectangle = (canvas) => {
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 150, 100);
};
Common Pitfalls
| ❌ Don't Do This✅ Do This Instead |
Use in OnInitialized | Use in OnAfterRenderAsync |
| Use during prerendering | Check firstRender flag |
| Modify DOM directly without Blazor knowing | Keep Blazor state synchronized |
| Assume reference is valid immediately | Wait for render completion |
Prerendering Considerations
When using Server-Side Rendering (SSR) or prerendering, ElementReference operations will fail because there's no browser DOM. Always wrap your code:
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Safe to use ElementReference here
await myElement.FocusAsync();
}
}
}
Summary Table
| PropertyDescription |
| Declaration | @ref="elementRef" |
| Type | ElementReference (struct) |
| Validity | After render (OnAfterRenderAsync) |
| Built-in Method | FocusAsync() (.NET 5+) |
| JS Interop | IJSRuntime.InvokeAsync("funcName", elementRef) |
Best Practices
- Always use
OnAfterRenderAsync - ElementReference is only valid after rendering - Check
firstRender - Avoid unnecessary operations on subsequent renders - Use built-in
FocusAsync() - No need for JavaScript for simple focus operations - Handle prerendering - Be aware that ElementReference won't work during SSR
- Keep references minimal - Only create references when truly needed