Reactor is a declarative, component-based C# framework for building WinUI 3 desktop apps. It renders real WinUI controls via a virtual element tree and reconciler — similar to React's programming model but targeting native Windows UI.
# Build (platform defaults to machine arch for apps; libraries are AnyCPU)
dotnet build Reactor.slnx
# Unit tests — xUnit, headless, fast (~2200 tests incl. 590 Yoga fixtures)
dotnet test tests/Reactor.Tests
# Single test class
dotnet test tests/Reactor.Tests --filter "FullyQualifiedName~ReconcilerMountUpdateTests"
# Selftests — real WinUI window, in-process (~10s)
dotnet test tests/Reactor.SelfTests
# Raw TAP output (faster iteration, supports --filter prefix)
dotnet run --project tests/Reactor.AppTests.Host -- --self-test --filter "Flex"
# E2E — Appium/WinAppDriver (requires WinAppDriver installed)
dotnet test tests/Reactor.AppTests
# Single E2E class
dotnet test tests/Reactor.AppTests --filter "ClassName=Reactor.AppTests.Tests.AccessibilityTests"CI runs unit tests + selftests + full solution build on every PR. .NET 10 SDK, windows-latest runner.
Full testing guide — tier selection, NativeAOT runs, code coverage — in TESTING.md.
UI is described as immutable C# records (Element subclasses), not WinUI controls. The reconciler diffs old vs. new element trees and patches only what changed on real controls.
Component.Render() → Element tree (records)
↓
Reconciler
├── Mount → creates WinUI controls
└── Update → diffs & patches controls
Reconciler.cs— orchestration, child reconciliation, unmount, helpersReconciler.Mount.cs—MountXxx()handler per control typeReconciler.Update.cs—UpdateXxx()handler per control type
Hooks (UseState, UseEffect, UseReducer, UseMemo, etc.) are tracked by call order in RenderContext. They must be called unconditionally, in the same order every render — no conditional hooks. Pass threadSafe: true for cross-thread state updates.
Echo handling is a documented hybrid (spec-047 §8.3). Synchronous, exact-comparable, single-controlled-value round-trips (ComboBox, FlipView, GridView, ListBox, Pivot, PipsPager, RadioButtons, SelectorBar, TabView, TemplatedFlipView, ToggleSwitch, TextBox) use a value-diff arm (ReactorState.PendingEchoMatch + ArmExpectedEcho/ShouldSuppressEcho, opt-in valueDiffEcho). ChangeEchoSuppressor is retained as the suppress-counter fallback for the rest: doubles (Slider/NumberBox value), NumberBox coercion, CalendarView collection diff, deferred/coercion strings (AutoSuggest/Password/RichEdit), Expander, CheckBox path-B, the ApplySetters suppression scope, and the public WriteSuppressed primitive. Authors keep using the stable WriteSuppressed primitive (or declare .Controlled / valueDiffEcho on a descriptor) — never the suppressor directly.
ElementPool recycles WinUI controls. Poolable types track one-time event wiring via ConditionalWeakTable<FrameworkElement, PoolableWireFlags> to avoid double-subscribing across rent/return cycles.
ReactorAttached.StateProperty stores ReactorState (Element pointer + ModifierEventHandlerState — the routed-input family, lazily allocated — + per-control ControlEventStateBox for control-intrinsic events) on native elements — not FrameworkElement.Tag or a CWT.
public record MyControlElement(string Label, Action? OnClick = null) : Element;Use with expressions for variations. Never mutate.
The DSL entry point is using static Microsoft.UI.Reactor.Factories;. Factory methods return Element records, never WinUI controls:
TextBlock("hello") // not new TextBlockElement("hello")
Button("+", () => ...) // not new ButtonElement(...)
VStack(child1, child2) // layout containersFactories is public static partial class — factory methods can be added from multiple files.
Extension methods use <T> where T : Element to maintain the concrete type through chains:
Text("Hello").Bold().Margin(16).Set(tb => tb.TextWrapping = TextWrapping.Wrap)
// Still TextBlockElement throughout the chainThe legacy Element-record + MountXxx/UpdateXxx dispatch-switch path is gone. The current path:
- Element record in
src/Reactor/Core/Element.cs - Authoring shape — a
ControlDescriptor<TElement, TControl>(the primary path) or a hand-codedIElementHandler<TElement, TControl>for irregular controls. - Register it in
RegisterV1BuiltInHandlers. - Selftest fixture in
tests/Reactor.AppTests.Host/SelfTest/Fixtures/.
See docs/guide/extensibility-preview.md for the authoring-shape decision tree (prop/engine shapes, children strategies, echo handling, pooling).
Optionally: a factory method in src/Reactor/Elements/Dsl.cs, fluent modifiers in ElementExtensions.cs, and unit tests in Reactor.Tests/.
| Testing… | Write a… | Location |
|---|---|---|
| Algorithm, pure function, hook bookkeeping, D3 math | Unit test (xUnit) | tests/Reactor.Tests/ |
| Element mount/update against real WinUI controls | Selftest fixture | tests/Reactor.AppTests.Host/SelfTest/Fixtures/ |
| Real user input, UIA properties, cross-process | E2E test (Appium) | tests/Reactor.AppTests/Tests/ |
Start with unit tests. Use selftests only when you need a live WinUI control. E2E is the slowest tier.
Tests that write to Console.Out/Console.Error must be grouped with [Collection("ConsoleTests")] to prevent cross-test interference.
IsAotCompatible=true is set for all net10.0+ projects. The core Reactor library promotes IL trimming/AOT warnings to errors — new reflection usage must be annotated before merging. Non-Reactor projects (tests, samples) suppress these warnings.
Class libraries must set WindowsAppSDKSelfContained=false. Only app executables own Windows App SDK self-contained packaging.
Everything is C#. No .xaml files for UI (except ReactorApplication.xaml which loads XamlControlsResources for AOT compatibility).
Docs under docs/guide/ are compiled from docs/_pipeline/templates/*.md.dt via mur docs compile. Edit the templates, not the compiled output.
src/Reactor/ Core framework
Core/ Reconciler, Component, Element, Hooks, RenderContext
Elements/ DSL factories (Dsl.cs) + fluent modifiers (ElementExtensions.cs)
Flex/ FlexPanel — CSS Flexbox via Yoga
Yoga/ Pure C# port of Meta's Yoga layout engine
Hosting/ ReactorApp entry point, render loop, hot reload
src/Reactor.Cli/ CLI tool (scaffolding, localization, preview)
src/Reactor.Analyzers/ Roslyn analyzers (theming, accessibility)
src/vscode-reactor/ VS Code live preview extension
tests/
Reactor.Tests/ Unit tests (xUnit, headless)
Reactor.SelfTests/ Selftest runner (MSTest, wraps TAP subprocess)
Reactor.AppTests.Host/ Selftest host app + Appium fixture navigator
Reactor.AppTests/ E2E tests (MSTest + Appium/WinAppDriver)
samples/ Demo apps and samples
docs/
guide/ User documentation (generated from templates)
specs/ Numbered design specs
reference/ API and subsystem reference