https://github.com/wieslawsoltes/nxui
NXUI (nex-ui), next-gen UI - Create minimal Avalonia applications using C# 10 and .NET 8
https://github.com/wieslawsoltes/nxui
avalonia avaloniaui csharp dotnet minimal xaml
Last synced: 5 months ago
JSON representation
NXUI (nex-ui), next-gen UI - Create minimal Avalonia applications using C# 10 and .NET 8
- Host: GitHub
- URL: https://github.com/wieslawsoltes/nxui
- Owner: wieslawsoltes
- License: mit
- Created: 2021-09-01T20:57:37.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2025-11-19T21:16:00.000Z (7 months ago)
- Last Synced: 2026-01-14T07:53:24.861Z (5 months ago)
- Topics: avalonia, avaloniaui, csharp, dotnet, minimal, xaml
- Language: C#
- Homepage:
- Size: 7.34 MB
- Stars: 229
- Watchers: 6
- Forks: 6
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README
# NXUI (next-gen UI)
[](https://www.nuget.org/packages/NXUI)
[](https://www.nuget.org/packages/NXUI)
Creating minimal [Avalonia](https://avaloniaui.net/) next generation (NXUI, next-gen UI) application using C# 10 and .NET 8
https://user-images.githubusercontent.com/2297442/132313187-32f18c4b-e894-46db-9a9d-9de02f30835e.mp4
# Requisites
### NXUI
```xml
```
Additionally, depending on the application type:
### Desktop
For Desktop extensions:
```xml
```
or using plain Avalonia:
```xml
```
### Browser
```xml
```
```
dotnet workload install wasm-tools
```
# Usage
NXUI builders now capture the full UI tree so hot reload can diff it. Return the builder
from your entry point and run the app through `HotReloadHost.Run` (shipped with
`NXUI.Desktop`) so NXUI can attach the component and reconcile live updates:
```csharp
object Build() =>
Window()
.Title("NXUI")
.Content(Label().Content("NXUI"));
return HotReloadHost.Run(Build, "NXUI", args);
```
```csharp
var count = 0;
object Build()
=> Window(out var window)
.Title("NXUI").Width(400).Height(300)
.Content(
StackPanel()
.Children(
Button(out var button)
.Content("Welcome to Avalonia, please click me!"),
TextBox(out var tb1)
.Text("NXUI"),
TextBox()
.Text(window.BindTitle()),
Label()
.Content(button.ObserveOnClick().Select(_ => ++count).Select(x => $"You clicked {x} times."))))
.Title(tb1.ObserveText().Select(x => x?.ToUpper()));
return HotReloadHost.Run(Build, "NXUI", args);
```
`HotReloadHost` automatically falls back to materializing the window when hot reload is
disabled, so the same entry point works for Release builds without extra flags.
### Running without Hot Reload
If you need to pass a concrete window to Avalonia (for example when integrating into a
custom lifetime), call `.Mount()` on the builder before returning it:
```csharp
Window Build()
=> Window()
.Title("NXUI")
.Content(Label().Content("NXUI"))
.Mount();
AppBuilder.Configure()
.UsePlatformDetect()
.UseFluentTheme()
.StartWithClassicDesktopLifetime(Build, args);
```
Minimalistic Desktop app:
```csharp
return HotReloadHost.Run(
() => Window().Content(Label().Content("NXUI")),
"NXUI",
args,
ThemeVariant.Dark);
```
# Generate
C#
```bash
cd src/Generator
dotnet run -- ../NXUI/Generated
```
F#
```bash
cd src/Generator
dotnet run -- ../NXUI.FSharp/Generated -fsharp
```
# dotnet run app.cs
Using .NET 10 you can run GUI apps using scripts: https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/#using-shebang-lines-for-shell-scripts
Note: You might need to adjust shebang line to `#!/usr/bin/dotnet run`
App.cs
```csharp
#!/usr/local/share/dotnet/dotnet run
#:package NXUI.Desktop@11.3.0
using Avalonia.Controls;
using Avalonia.Styling;
using NXUI.HotReload;
using static NXUI.Builders;
return HotReloadHost.Run(
() => Window().Content(Label().Content("NXUI")),
"NXUI",
args,
ThemeVariant.Dark,
ShutdownMode.OnLastWindowClose);
```
```bash
chmod +x App.cs
./App.cs
```

More complex app:
```csharp
#!/usr/local/share/dotnet/dotnet run
#:package NXUI.Desktop@11.3.0
using NXUI.HotReload;
using static NXUI.Builders;
var count = 0;
object Build()
=> Window(out var window)
.Title("NXUI").Width(400).Height(300)
.Content(
StackPanel()
.Children(
Button(out var button)
.Content("Welcome to Avalonia, please click me!"),
TextBox(out var tb1)
.Text("NXUI"),
TextBox()
.Text(window.BindTitle()),
Label()
.Content(button.ObserveOnClick().Select(_ => ++count).Select(x => $"You clicked {x} times."))))
.Title(tb1.ObserveText().Select(x => x?.ToUpper()));
return HotReloadHost.Run(Build, "NXUI", args);
```

## F# Support
From F# 9.0 and above the compiler resolves [extension methods instead of instrinsic properties](https://github.com/dotnet/fsharp/pull/16032) so, there's no need for a separate F# package or any additional changes to your project files.
Extension methods provided by the main package `NXUI`
```fsharp
open Avalonia
open Avalonia.Controls
open NXUI.Extensions
open NXUI.HotReload
open type NXUI.Builders
let Build () : obj =
let mutable count = 0
let mutable window = Unchecked.defaultof
let mutable button = Unchecked.defaultof
let mutable tb1 = Unchecked.defaultof
Window(window)
.Title("NXUI")
.Width(400)
.Height(300)
.Content(
StackPanel()
.Children(
Button(button).Content("Welcome to Avalonia, please click me!"),
TextBox(tb1).Text("NXUI"),
TextBox().Text(window.BindTitle()),
Label()
.Content(
button.ObserveOnClick()
|> Observable.map (fun _ ->
count <- count + 1
count)
|> Observable.map (fun x -> $"You clicked {x} times.")
|> _.ToBinding()
)
)
)
.Title(tb1.ObserveText())
|> box
[]
let Main argv = HotReloadHost.Run(Build, "NXUI", argv)
```
> ### F# 8.0 Support
>
> The compiler feature is available in the .NET9 SDK and above so even if you target a lower dotnet version you don't need to change your project files.
>
> However, if you must to use the .NET8 SDK you only need to set the language version to preview
> In your \*.fsproj project and you'll get the same benefits.
>
> ```xml
>
> net8.0
> preview
>
> ```
## Extensions
NXUI ships with a rich set of extension methods and builder helpers so that all
UI composition can be expressed in C#. The code generator produces most of
these members for every Avalonia control and property.
### Builders
`NXUI.Builders` exposes factory methods for every control type. Each method
creates the control instance and overloads let you capture it via `out var` for
later use.
### Property helpers
For each Avalonia property the following methods are generated:
* **`(value)`** – set the property value.
* **`(IBinding, mode, priority)`** – bind with an Avalonia binding.
* **`(IObservable, mode, priority)`** – bind from an observable.
* **`Bind(mode, priority)`** – create a binding descriptor.
* **`Observe()`** – observable of property values.
* **`On(handler)`** – pass the observable to a handler.
* **`ObserveBinding()`** – observe binding values including errors.
* **`OnBinding(handler)`** – receive the binding observable.
* **`ObserveChanged()`** – observe full change events.
* **`OnChanged(handler)`** – handler for change observable.
Enum properties get convenience methods for each enum value, e.g.
`HorizontalAlignmentCenter()`.
### Event helpers
For routed and CLR events:
* **`ObserveOn(routes)`** – returns an `IObservable` sequence.
* **`On(handler, routes)`** – handler receiving the observable.
* **`OnHandler(action, routes)`** – attach a simple callback.
### Style setters
`Set` methods on `Style` and `KeyFrame` let you define
style values using constants, bindings or observables.
### Core runtime helpers
`NXUI.Extensions.AvaloniaObjectExtensions` provides `BindOneWay` and
`BindTwoWay` to link properties or observables without verbose binding code.
`NXUI.Extensions.ReactiveObservableExtensions` adds utilities for reactive
workflows:
- `ObserveOnUiThread` / `SubscribeOnUiThread`
- `TakeUntilDetachedFromVisualTree` / `SubscribeUntilDetached`
- `DisposeWith`
- `DataTemplate`
- `WhenAnyValue` (single or multiple expressions)
Together these extensions enable complex, reactive UIs built entirely in code
while managing resources with minimal overhead.
## Hot Reload
### Using NXUI Hot Reload
1. **Hot reload is always on** – every NXUI project now emits builder-based controls and ships the hot reload runtime by default. No MSBuild properties or preprocessor symbols are required.
2. **Run through the host** – wrap your entry point with `HotReloadHost.Run` so NXUI can register the component and reconcile live instances:
```csharp
using NXUI.HotReload;
object Build() =>
Window()
.Title("NXUI Hot Reload")
.Content(Label().Content("Edit a file and save to trigger hot reload."));
return HotReloadHost.Run(Build, "SampleApp", args);
```
3. **Use `dotnet watch` or IDE hot reload** – the runtime listens for metadata updates and calls `NodeRenderer.Reconcile` with the previous node snapshot. The window stays mounted and control state (e.g., `TextBox.Text`) survives.
4. **Turn on diagnostics when needed** – set `NXUI_HOTRELOAD_DIAGNOSTICS=1` to see per-reconciliation summaries, including counts for property sets, child add/remove/move operations, and replacements.
5. **Implicit boundaries (optional)** – set `true` to weave `[HotReloadBoundary]` onto controls listed in `build/HotReloadBoundaries.json`. Samples do this automatically for Debug builds via `samples/Directory.Build.props`. Use `dotnet run --project src/NXUI.Cli -- hotreload boundaries --manifest build/HotReloadBoundaries.json --assembly path/to/MyApp.dll` to inspect which controls were annotated (manifest hits, explicit attributes, or state-adapter skips). The same Fody pass also injects `[assembly: MetadataUpdateHandler(typeof(NXUI.HotReload.HotReloadMetadataUpdateHandler))]`, so IDE / `dotnet watch` notifications reach NXUI without manual attributes.
### Troubleshooting
- **No updates apply** – ensure your builder delegate returns an `ElementNode` tree (not `.Mount()`ed controls) and that it is hosted by `HotReloadHost.Run`.
- **State resets on list changes** – provide explicit `.Key("stable-id")` for repeating builders or wrap complex containers with `.HotReloadBoundary()` so the diff engine can reason about reuse. See `docs/hot-reload-best-practices.md` for patterns.
- **Bindings/events stop firing** – check analyzer warnings (see `NXUI.Analyzers`) and confirm you are not instantiating Avalonia controls manually; the builder pipeline must remain in control for hot reload to succeed.
- **Layout thrash during updates** – enable diagnostics to ensure excessive replacements are not happening; large replace counts often mean missing keys or boundaries.
### Release Builds
- The hot reload runtime now ships in every build configuration. `NXUI.props` injects the required `RuntimeHostConfigurationOption` automatically so metadata updates flow without extra project tweaks.
- When trimming, keep `NXUI.HotReload.*` rooted (the shipped linker descriptors already do this) if you want hot reload support in the published app.
### Further Reading
- `docs/hot-reload-architecture.md` – end-to-end design.
- `docs/hot-reload-implementation-plan.md` – milestone tracking.
- `docs/hot-reload-best-practices.md` – guidance for `Key()` and `HotReloadBoundary()` usage.