Nuri.WPF 0.3.0

dotnet add package Nuri.WPF --version 0.3.0
                    
NuGet\Install-Package Nuri.WPF -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nuri.WPF" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nuri.WPF" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Nuri.WPF" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nuri.WPF --version 0.3.0
                    
#r "nuget: Nuri.WPF, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nuri.WPF@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nuri.WPF&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Nuri.WPF&version=0.3.0
                    
Install as a Cake Tool

Nuri

NuGet NuGet Downloads

Nuri is a C# MVU UI library. Components describe UI with platform-neutral virtual elements, and renderer adapters materialize those descriptions into native controls.

The supported renderer paths are WPF through Nuri.WPF and Duxel on Windows through Nuri.Duxel.Windows. The platform-neutral immediate-mode Duxel renderer is packaged as Nuri.Duxel and is referenced by the Windows host.

Duxel remains the primary backend parity and expansion priority. The existing Avalonia adapter remains available as a regression baseline.

What Nuri Does

  • Component Render() methods return virtual UI descriptions, not native WPF controls.
  • State changes re-render the dirty component subtree when possible.
  • Virtual trees are diffed into patch operations and applied by the renderer.
  • Keys preserve row/component identity across list insert, remove, move, and replace.
  • Events, values, animation descriptions, routing, and lifecycle hooks live in Core.
  • WPF-specific control creation, property mapping, event mapping, and animation materialization stay in Nuri.WPF.

Project Layout

  • src/Nuri: platform-neutral runtime, DSL, virtual DOM, diffing, patch operations, values, events, routing, and lifecycle hooks.
  • src/Nuri.WPF: WPF renderer adapter, WPF control registry, WPF property/event mapping, WPF animation materialization, and application host.
  • src/Nuri.WPF.Diagnostics: WPF runtime inspector package and UseAttachDevTools() integration.
  • src/Nuri.Avalonia.Diagnostics: Avalonia runtime inspector package and UseAttachDevTools() integration.
  • src/Nuri.Duxel/Nuri.Duxel: Duxel immediate-mode renderer adapter over Duxel.App.
  • src/Nuri.Duxel/Nuri.Duxel.Windows: Windows application/frame integration over Duxel.Windows.App.
  • src/Nuri.Duxel/Nuri.Duxel.Diagnostics: Duxel runtime inspector package and UseAttachDevTools() integration.
  • src/Nuri.WPF.PreviewHost and src/Nuri.Duxel/Nuri.Duxel.PreviewHost: out-of-process preview hosts.
  • src/Nuri.Avalonia: existing Avalonia renderer adapter retained as a regression baseline rather than the next backend expansion target.
  • src/Nuri.Formatter: conservative C# formatter used by the Visual Studio integration.
  • samples/WPF: focused WPF samples that exercise concrete behavior.
  • samples/Duxel: focused Duxel samples used to drive the next backend implementation slices.
  • tests/Nuri.Tests: lightweight Core behavior tests.
  • perf: performance sanity harnesses.

Basic WPF App

Start a Nuri WPF app through NuriApplication:

using Nuri.WPF;

namespace NuriSample;

internal static class Program
{
    private static void Main()
    {
        NuriApplication.Run<CounterComponent>("Nuri Sample", width: 480, height: 320);
    }
}

NuriApplication.Run<TComponent> uses the current thread when it is already STA. Otherwise it creates and owns a WPF STA application thread, so a Program.cs entry point does not need [STAThread] or manual SetApartmentState calls. APIs that return native WPF objects, such as Show and Attach, still need to be called from their owning WPF STA thread.

Create components by inheriting Component and returning IElement from Render():

using Nuri.UI.Dsl;

namespace NuriSample;

public sealed class CounterComponent : Component
{
    public override IElement Render()
    {
        var (count, setCount) = useState(0);

        return Div(
            Button($"Count: {count}", () => setCount(current => current + 1)),
            Button("Reset", () => setCount(_ => 0))
        );
    }
}

useState setters receive the current stored value. Use setCount(current => current + 1) for updates based on existing state, and _ => value to assign a specific value.

WPF-familiar factory aliases are available while still producing platform-neutral Nuri elements:

Button("Save", Save);
TextBox().OnTextChanged(value => setText(_ => value));
CheckBox("Enabled", value => setEnabled(_ => value));
RadioButton("Option A", value => setSelected(_ => value));
ToggleButton("Pinned", value => setPinned(_ => value));
PasswordBox();

State And Hooks

Use useState for local component state:

var (text, setText) = useState(string.Empty);

return TextBox(text, value => setText(_ => value));

Use useEffect for post-render effects. Omitting dependencies runs after every render. Passing [] runs on mount and cleans up on unmount.

If no cleanup is needed, use the Action overload and do not return anything:

useEffect(() =>
{
    TrackRender();
}, [route]);

Return a cleanup action only when the effect owns something that should be disposed or unsubscribed:

useEffect(() =>
{
    StartSubscription();
    return StopSubscription;
}, []);

Track dependencies with C# collection expressions:

useEffect(() =>
{
    Refresh(route);
    return null;
}, [route]);

Routing

Use useNavigation when a component owns local navigation state:

var (navigation, navigator) = useNavigation("overview");

return Div(
    Button("Overview", () => navigator.Navigate("overview")),
    Button("Details", () => navigator.Navigate("details")),
    Button("Back", navigator.GoBack),
    Router(navigation,
        Route("overview", () => Text("Overview")),
        Route("details", () => Text("Details")))
);

Navigator supports:

  • Navigate(route): push current route and move to route.
  • Replace(route): change route without adding history.
  • GoBack(): return to the previous route when available.
  • CanGoBack: inspect whether the back stack has entries.

Layout

Use Grid(...) with fluent .Rows(...) and .Columns(...) for layout:

return Grid(
        Header().Row(0).ColumnSpan(2),
        Sidebar().Row(1).Column(0),
        Content().Row(1).Column(1)
    )
    .Rows("Auto,*")
    .Columns(240, Star);

Numeric row and column values use pixels. A comma-separated string can combine pixel values with Auto, *, and weighted star values such as 2*. The explicit Pixels(240) form remains available for compatibility.

Scroll is a single-content viewport. Put vertical layout and spacing on its one Column child:

return Grid(Rows(Auto, Star),
    Toolbar().Row(0),
    Div(DivTypes.Scroll,
        Div(rows).Spacing(8))
        .Row(1));

Keys

Use explicit keys for rows and components whose identity must survive reorder, filter, edit, or remove operations:

Div(items.Select(item =>
    (IElement)new TodoItemComponent(item).Key(item.Id)
).ToArray());

Name remains a key fallback for compatibility, but new code should prefer .Key("...").

Animation

Call .Transition(...) after the property setter that should animate:

Text("Play")
    .Margin(30, isPlaying ? 0 : 100, 0, 0)
    .Transition(500, EasingValue.CubicInOut);

Use .Transitions("Margin", ...) only when the animated property needs to be selected explicitly.

Basic Duxel App

Run a Duxel application on Windows through the Nuri.Duxel.Windows host:

using Nuri.Duxel;

var app = NuriApplication.Create<CounterComponent>(
    title: "Nuri Duxel",
    width: 720,
    height: 480);

app.Run();

Nuri.Duxel owns immediate-mode projection, while Nuri.Duxel.Windows owns the native window, input bridge, and frame-loop integration.

Runtime Diagnostics

Reference the diagnostics package that matches the renderer: Nuri.WPF.Diagnostics, Nuri.Avalonia.Diagnostics, or Nuri.Duxel.Diagnostics. All expose UseAttachDevTools(), default to F12, and compile the same platform-neutral inspector UI into the renderer-specific package.

var app = NuriApplication.Create<AppComponent>("Nuri App", 940, 620);

#if DEBUG
app.UseAttachDevTools();
#endif

app.Run();

Import Nuri.WPF.Diagnostics for WPF, Nuri.Avalonia.Diagnostics for Avalonia, or Nuri.Duxel.Diagnostics for Duxel. The matching samples are Nuri.WPFDiagnosticsSample and Nuri.DuxelDiagnosticsSample.

WPF Samples

Run samples with dotnet run --project ... -c Release.

dotnet run --project "samples\WPF\Nuri.TodoValidationSample\Nuri.TodoValidationSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.SettingsPreferencesSample\Nuri.SettingsPreferencesSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.ModalDialogSample\Nuri.ModalDialogSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.CommandPaletteSample\Nuri.CommandPaletteSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.ExplorerTreeSample\Nuri.ExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.VirtualExplorerTreeSample\Nuri.VirtualExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\WPF\Nuri.WPFDiagnosticsSample\Nuri.WPFDiagnosticsSample.csproj" -c Debug
dotnet run --project "samples\WPF\RouterSample\RouterSample.csproj" -c Release

Sample coverage:

  • Nuri.TodoValidationSample: controlled input, list diff, filter, item edit, remove, keyed rows, scroll layout.
  • Nuri.SettingsPreferencesSample: checkbox, radio, toggle, form grouping, validation.
  • Nuri.ModalDialogSample: mount/unmount, effect cleanup, overlay layering.
  • Nuri.CommandPaletteSample: controlled search input, keyboard events, filtered keyed list, selection, command execution.
  • Nuri.ExplorerTreeSample: recursive keyed components, subtree lifecycle cleanup, rename, add, and delete behavior.
  • Nuri.VirtualExplorerTreeSample: 10,101 generated tree nodes flattened into a fixed-extent WPF recycling viewport.
  • RouterSample: router, nested router, useNavigation, effects, keyed list behavior.
  • Nuri.WPFDiagnosticsSample: WPF runtime inspector, hooks, stores, patch counts, console capture, and component highlighting.

Duxel Samples

dotnet run --project "samples\Duxel\Nuri.DuxelSample\Nuri.DuxelSample.csproj" -c Release
dotnet run --project "samples\Duxel\Nuri.DuxelExplorerTreeSample\Nuri.DuxelExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\Duxel\Nuri.DuxelVirtualExplorerTreeSample\Nuri.DuxelVirtualExplorerTreeSample.csproj" -c Release
dotnet run --project "samples\Duxel\Nuri.DuxelDiagnosticsSample\Nuri.DuxelDiagnosticsSample.csproj" -c Debug

Validation

Build the solution after meaningful changes:

dotnet build "Nuri.sln" -c Release

Run the Core, renderer, and diagnostics tests:

dotnet run --project "tests\Nuri.Tests\Nuri.Tests.csproj" -c Release
dotnet run --project "tests\Nuri.RendererTests\Nuri.RendererTests.csproj" -c Release
dotnet run --project "tests\Nuri.DuxelRendererTests\Nuri.DuxelRendererTests.csproj" -c Release
dotnet run --project "tests\Nuri.DevToolsTests\Nuri.DevToolsTests.csproj" -c Release
dotnet run --project "tests\Nuri.DuxelDiagnosticsTests\Nuri.DuxelDiagnosticsTests.csproj" -c Release
dotnet run --project "tests\Nuri.FormatterTests\Nuri.FormatterTests.csproj" -c Release

Performance sanity checks:

dotnet run --project "perf\Nuri.Performance\Nuri.Performance.csproj" -c Release -- --label after
dotnet run --project "perf\Nuri.WPFPerformance\Nuri.WpfPerformance.csproj" -c Release -- --label after
dotnet run --project "perf\Nuri.DuxelPerformance\Nuri.DuxelPerformance.csproj" -c Release -- --label after

Patch count matters, especially for keyed reconciliation and reorder behavior.

Product Compatible and additional computed target framework versions.
.NET net6.0-windows7.0 is compatible.  net7.0-windows was computed.  net7.0-windows7.0 is compatible.  net8.0-windows was computed.  net8.0-windows7.0 is compatible.  net9.0-windows was computed.  net9.0-windows7.0 is compatible.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0-windows7.0

  • net7.0-windows7.0

  • net8.0-windows7.0

  • net9.0-windows7.0

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Nuri.WPF:

Package Downloads
Nuri.WPF.Diagnostics

Runtime inspector and diagnostics window for Nuri WPF applications.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 38 8/25/2026
0.2.1 110 8/3/2026
0.2.0 119 7/15/2026
0.1.0 108 7/13/2026

Moves the UI/runtime model into the platform-neutral Nuri package and keeps WPF materialization in Nuri.WPF.