If you know Blazor, you'll feel right at home with Ivy. You are already used to writing C# on the server (Blazor Server) or client. The main shift is moving from the HTML-mixed Razor syntax to a pure, fluent C# API. No more context switching between markup and logic it's all just C#.
The Familiar Patterns
Components Are Classes
In Blazor, you typically build with .razor files which compile down to classes. In Ivy, you write the C# classes directly by inheriting from ViewBase.
Blazor:
<div class="card">
<h2>@Name</h2>
<p>@Email</p>
</div>
@code {
[Parameter]
public required string Name { get; set; }
[Parameter]
public required string Email { get; set; }
}
Ivy:
public class UserCard : ViewBase
{
public required string Name { get; init; }
public required string Email { get; init; }
public override object? Build()
{
return new Card()
| Text.H2(Name)
| Text.P(Email);
}
}
Same concept. Instead of [Parameter] properties and a razor template, you use standard properties and a Build() method that returns the widget tree.
State Management
Blazor developers are used to modifying fields and calling StateHasChanged() (or relying on the event handler to do it). Ivy uses a hook-like pattern similar to UseState , which makes reactivity more explicit and fine-grained.
Blazor
<p>Count: @currentCount</p>
<button @onclick="IncrementCount">Count</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
// StateHasChanged() is called automatically for EventCallbacks
}
}
Ivy:
public class Counter : ViewBase
{
public override object? Build()
{
// define reactive state
var count = UseState(0);
return new Button($"Count: {count.Value}",
onClick: _ => count.Set(count.Value + 1));
}
}
The mental model is similar: data changes update the UI. However, Ivy's UseState eliminates the ambiguity of when to call StateHasChanged() setting the value automatically triggers a targeted re-render.
What's Different (and Better)
No Razor Syntax
Blazor requires you to mix HTML and C#:
@if (isVisible) {
<div class="panel">...</div>
}
Ivy allows you to use standard C# control flow directly in your component construction:
if (isVisible.Value)
{
return new Panel()...;
}
You don't need to learn a templating language. If you know C#, you know how to build Ivy UIs.
Lifecycle vs. Hooks
Blazor relies on lifecycle methods like OnInitializedAsync and OnParametersSet . Ivy uses the UseEffect pattern, which creates side effects based on dependency changes.
Blazor:
protected override async Task OnInitializedAsync()
{
users = await UserService.GetUsers();
}
Ivy
UseEffect(async () => {
var data = await userService.GetUsers();
users.Set(data);
}, []); // Empty array means "run once on mount"
Server-Side Simplicity
Like Blazor Server, Ivy runs on the server. However, Ivy sends efficiently diffed JSON patches to render native HTML widgets on the client, rather than managing a virtual DOM diff over SignalR in the same way. The browser only ever sees the rendered result, keeping your logic secure.
Layout Composition
Blazor uses RenderFragment and <ChildContent> for composition. Ivy uses the pipe operator | for a more fluent, functional composition style.
Blazor
<StackLayout Gap="4">
<Header />
<Sidebar />
<Content />
</StackLayout>
Ivy:
Layout.Vertical().Gap(4)
| new Header()
| new Sidebar()
| new Content()
The pipe operator (|) is syntactic sugar for adding children. It's composable, readable, and feels natural once you use it.
##Dependency Injection
Blazor uses the @inject directive. Ivy uses UseService , leveraging the same underlying ASP.NET Core DI container you already know.
Blazor
@inject IWeatherService WeatherService
@code {
// use WeatherService
}
Ivy:
var weatherService = UseService<IWeatherService>();
Async Data Loading
Handling async data in Blazor often involves checking for null or loading flags in the markup. Ivy handles this with standard C# flow.
Blazor
@if (forecasts == null)
{
<p>Loading...</p>
}
else
{
<Grid Items="forecasts" />
}
Ivy
public override object? Build()
{
var users = UseState<List<User>>([]);
var loading = UseState(true);
var userService = UseService<UserService>();
UseEffect(async () => {
var data = await userService.GetUsersAsync();
users.Set(data);
loading.Set(false);
}, []);
if (loading.Value) return new Skeleton();
return users.Value.ToTable();
}
Getting Started
If you're a Blazor developer ready to try Ivy:
First, make sure you have installed the .NET 9 SDK.
dotnet tool install -g Ivy.Console
ivy init --hello
dotnet watch
You'll have a running app with hot reload in seconds. Open the code, and you'll feel right at home.
The Bottom Line
Ivy isn't asking you to learn a new language. It's asking you to use the C# you already love for the entire stack including the UI structure. If you've ever felt that context-switching between Razor syntax and C# logic was friction, Ivy removes that barrier.
Your backend logic stays the same. Your DI stays the same. You just write pure C# Views instead of Razor files.
Resources:
