Blazor Custom Elements: Using Blazor Components Outside Blazor
What if you could use your Blazor components in a React app, a plain HTML page, or a legacy MVC application? Blazor custom elements make this possible by wrapping Blazor components as standard web custom elements — the browser-native component model that works everywhere.
What Are Custom Elements?
Custom elements are a web standard that lets you define your own HTML tags. A custom element like <my-counter></my-counter> is a real HTML element with its own lifecycle, attributes, and behaviour. Blazor custom elements let you package a Blazor component as one of these elements.
Setting Up
Install the package:
dotnet add package Microsoft.AspNetCore.Components.CustomElements
Register your Blazor component as a custom element in Program.cs:
builder.Services.AddServerSideBlazor(options =>
{
options.RootComponents.RegisterCustomElement<Counter>("blazor-counter");
options.RootComponents.RegisterCustomElement<WeatherWidget>("weather-widget");
});
The first argument is the Blazor component type. The second is the custom element tag name (which must contain a hyphen, per the web standard).
Creating the Component
Your Blazor component doesn't need any special modifications. Standard components work as-is:
<div class="counter">
<p>Count: @CurrentCount</p>
<button @onclick="Increment">Click me</button>
</div>
@code {
[Parameter]
public int StartValue { get; set; } = 0;
private int CurrentCount { get; set; }
protected override void OnInitialized()
{
CurrentCount = StartValue;
}
private void Increment()
{
CurrentCount++;
}
}
Using in Plain HTML
Once registered, use it in any HTML page that includes the Blazor script:
<!DOCTYPE html>
<html>
<head>
<title>Custom Element Demo</title>
</head>
<body>
<h1>My Page</h1>
<!-- Use the Blazor component as a custom element -->
<blazor-counter start-value="10"></blazor-counter>
<script src="_framework/blazor.server.js"></script>
</body>
</html>
Notice that the StartValue parameter maps to the start-value HTML attribute. Blazor automatically converts PascalCase parameters to kebab-case attributes.
Passing Parameters
Parameters are passed as HTML attributes. Blazor handles type conversion for primitive types:
<!-- String parameter -->
<weather-widget city="London"></weather-widget>
<!-- Number parameter -->
<blazor-counter start-value="42"></blazor-counter>
<!-- Boolean parameter -->
<user-card show-email="true"></user-card>
For complex types, you can set parameters from JavaScript:
const widget = document.querySelector('weather-widget');
widget.setAttribute('city', 'Manchester');
// Or set the property directly for complex types
widget.config = { units: 'metric', refreshInterval: 30 };
Using in MVC or Razor Pages
This is particularly useful for gradually adopting Blazor in existing applications. Add interactive Blazor components to your existing Razor views:
@* In a Razor Page or MVC View *@
<h2>Dashboard</h2>
<div class="widget-grid">
<blazor-counter start-value="0"></blazor-counter>
<weather-widget city="@Model.UserCity"></weather-widget>
</div>
@section Scripts {
<script src="_framework/blazor.server.js"></script>
}
This approach lets you incrementally introduce interactivity without rewriting your entire application.
Custom Elements with WebAssembly
For WebAssembly-based custom elements, the setup is slightly different:
// In a Blazor WebAssembly project
builder.RootComponents.RegisterCustomElement<Counter>("blazor-counter");
<!-- In the host page -->
<blazor-counter start-value="5"></blazor-counter>
<script src="_framework/blazor.webassembly.js"></script>
WebAssembly custom elements are fully client-side — they don't need a server connection after the initial download.
Events and Callbacks
Custom elements can dispatch events back to the hosting page. Use EventCallback in your component and wire up JavaScript event listeners:
@code {
[Parameter]
public EventCallback<int> OnCountChanged { get; set; }
private int unreadCount;
private async Task MarkAllRead()
{
unreadCount = 0;
await OnCountChanged.InvokeAsync(unreadCount);
}
}
From JavaScript:
const bell = document.querySelector('notification-bell');
bell.addEventListener('oncountchanged', (event) => {
console.log('Unread count:', event.detail);
updateBadge(event.detail);
});
Limitations
Custom elements have some constraints worth knowing:
- No child content: You can't pass HTML children to a Blazor custom element. Only parameters/attributes work.
- Render mode: Server-based custom elements need an active SignalR connection. WebAssembly-based ones need the runtime downloaded.
- Shadow DOM: Blazor custom elements don't use shadow DOM, so their styles aren't encapsulated. CSS from the host page affects the component and vice versa.
- Serialisation: Complex parameter types must be set via JavaScript properties, not HTML attributes.
When to Use Custom Elements
Custom elements are ideal for:
- Incremental adoption — adding Blazor interactivity to existing MVC/Razor Pages applications
- Micro-frontends — embedding Blazor components in applications built with other frameworks
- Reusable widgets — distributing components that can be used by teams using different tech stacks
They're not a replacement for building a full Blazor application. If you're starting from scratch, use Blazor's native component model. Custom elements are the bridge for when Blazor needs to coexist with other technologies.