If you know Vue, you'll pick up Ivy quickly. The reactive model is familiar - state that triggers UI updates, computed values, watchers - just expressed in C# instead of JavaScript. No .vue files, no Vite config, no node_modules. Just your existing Vue intuition applied to a full-stack C# framework.
The Familiar Patterns
Components Are Views
In Vue, you build with Single File Components. In Ivy, you build with Views:
Vue:
<template>
<div class="card">
<h2>{{ name }}</h2>
<p>{{ email }}</p>
</div>
</template>
<script setup>
defineProps({
name: String,
email: String
})
</script>
Ivy:
public class UserCard(string name, string email) : ViewBase
{
public override object? Build()
{
return new Card()
| Text.H2(name)
| Text.P(email);
}
}
Same concept, different syntax. The Build() method is your template rendered in code.
Reactivity Works Similarly
This is where Ivy feels natural for Vue developers. Vue's reactivity primitives have direct equivalents:
| Vue (Composition API) | Ivy | Purpose |
|---|---|---|
ref() |
UseState |
Reactive state |
watch() |
UseEffect |
Side effects when dependencies change |
computed() |
UseMemo |
Derived/computed values |
inject() |
UseService |
Dependency injection |
Vue:
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue) => {
console.log(`Count: ${newValue}`)
})
</script>
<template>
<button @click="count++">
Count: {{ count }}
</button>
</template>
Ivy:
public class Counter : ViewBase
{
public override object? Build()
{
var count = UseState(0);
UseEffect(() => {
Console.WriteLine($"Count: {count.Value}");
}, [count]);
return new Button($"Count: {count.Value}",
onClick: _ => count.Set(count.Value + 1));
}
}
The mental model is the same. State changes trigger re-renders. Watchers (effects) run when dependencies change. You already understand this.
What's Different (and Better)
No Build Pipeline
Vue requires: npm, Vite, node_modules, package.json, vite.config.js...
Ivy requires:
dotnet watch
That's it. Hot reload included. No configuration needed.
No API Layer Needed
In Vue, you typically:
- Build your UI with components
- Create API endpoints (Express, FastAPI, etc.)
- Fetch data with
fetch/axiosinonMounted - Handle loading/error states
- Keep frontend and backend types in sync
In Ivy, you just... use your data:
public override object? Build()
{
var db = UseService<MyDbContext>();
var users = db.Users.Where(u => u.IsActive).ToList();
return users.ToTable();
}
Your C# code runs on the server. Direct database access. No REST endpoints to maintain. No type mismatches between frontend and backend.
Server-Side State = Security
In Vue, state lives in the browser. Sensitive logic requires careful API design.
In Ivy, state lives on the server. The browser only sees rendered widgets—never your business logic, database queries, or sensitive data. Updates to the widget tree are sent as patches efficiently over WebSocket.
Layout Composition
Vue developers love component composition. Ivy uses the | operator for the same pattern:
Vue:
<template>
<Layout direction="vertical" :gap="4">
<Header />
<Sidebar />
<Content />
</Layout>
</template>
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.
Event Handling
Vue's event model translates directly:
Vue:
<template>
<input v-model="search" />
<button @click="handleSubmit">Submit</button>
</template>
<script setup>
import { ref } from 'vue'
const search = ref('')
function handleSubmit() {
console.log(search.value)
}
</script>
Ivy:
var search = UseState("");
return Layout.Vertical()
| search.ToTextInput()
| new Button("Submit", onClick: _ => HandleSubmit(search.Value));
Same concepts: two-way binding (via state), event handlers, state updates triggering re-renders. Note how search.ToTextInput() gives you v-model-like behavior automatically.
Provide/Inject vs Services
Vue:
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
</script>
Ivy:
var theme = UseService<IThemeProvider>();
Ivy uses .NET's built-in dependency injection, which is more powerful than Vue's provide/inject for complex applications.
Computed Properties
Vue:
<script setup>
import { ref, computed } from 'vue'
const items = ref([1, 2, 3, 4, 5])
const evenItems = computed(() => items.value.filter(i => i % 2 === 0))
</script>
Ivy:
var items = UseState(new[] { 1, 2, 3, 4, 5 });
var evenItems = UseMemo(() =>
items.Value.Where(i => i % 2 == 0).ToList(), [items]);
Async Data Fetching
Vue:
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(true)
onMounted(async () => {
const response = await fetch('/api/users')
users.value = await response.json()
loading.value = false
})
</script>
<template>
<div v-if="loading">Loading...</div>
<UserList v-else :users="users" />
</template>
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 Vue 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 trying to replace Vue—it's bringing a similar reactive component model to the .NET ecosystem. If your team knows C# and you're tired of maintaining separate frontend and backend codebases, Ivy lets you use your Vue intuition without the JavaScript toolchain complexity.
Your reactive mental model transfers. Your composition patterns transfer. You just write C# instead.
Resources:
