ShellUI Native follows a modular architecture designed to support multiple native platforms while maintaining a consistent developer experience and design system. It is the native counterpart to ShellUI (Blazor), inspired by shadcn/ui's copy-and-own approach.
ShellUI Native does NOT use Tailwind CSS. Native platforms use XAML styles, not CSS.
| Aspect | ShellUI Blazor | ShellUI Native |
|---|---|---|
| Styling | Tailwind CSS | XAML Styles/ResourceDictionary |
| Rendering | HTML/CSS in browser | Native platform controls |
| Performance | Web rendering | Native rendering (faster) |
| Bundle size | Includes CSS | Zero CSS overhead |
The design tokens (colors, spacing, typography) are identical to maintain visual consistency, but implemented natively for better performance.
shellui-native/
├── Directory.Build.props # Centralized versioning + package metadata
├── global.json # Pins .NET 10 SDK
├── ShellUI.Native.slnx # XML-format solution (post .NET 9)
├── src/
│ ├── ShellUI.Native.Core/ # Shared models and abstractions
│ ├── ShellUI.Native.Templates/ # Component templates (MAUI today; per-platform planned)
│ ├── ShellUI.Native.CLI/ # Command-line tool
│ ├── ShellUI.Native.MAUI/ # MAUI reference implementation (optional)
│ └── ShellUI.Native.Avalonia/ # Avalonia reference implementation (planned, Phase 2)
├── tests/
│ └── ShellUI.Native.Tests/ # xUnit — registry invariants, template hygiene, ProjectDetector
├── examples/
│ └── MAUI.Demo/
└── docs/
ShellUI.Native.CLI
├── ShellUI.Native.Core
└── ShellUI.Native.Templates
└── ShellUI.Native.Core
Components are stored as string templates in the Templates project. Each template includes:
- Metadata - Name, description, category, dependencies, file path
- Content - The actual C# code with namespace placeholders
public class ButtonTemplate
{
public static ComponentMetadata Metadata => new()
{
Name = "button",
DisplayName = "Button",
Description = "Interactive button with variants",
Category = ComponentCategory.Form,
FilePath = "Button.cs",
Dependencies = new List<string> { "button-variants" }
};
// Template System v2: content is keyed by target NativePlatform. Adding Avalonia
// support to a component means adding one dict entry here — no registry edit needed.
public static IReadOnlyDictionary<NativePlatform, string> Contents { get; } = new Dictionary<NativePlatform, string>
{
[NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI;
// ... MAUI component code",
// [NativePlatform.Avalonia] = @"..." // added in Phase 2
};
}Registry lookup (Template System v2): ComponentRegistry.GetComponentContent(name, NativePlatform)
returns the source for the target platform, or null if the component doesn't exist OR exists
but has no template for that platform. Callers use SupportsPlatform(name, platform) and
GetSupportedPlatforms(name) to distinguish those two failure modes. Landed on
feat/template-system-v2 (2026-07-18).
When components are installed, the placeholder YourProjectNamespace is replaced with the actual project namespace detected from the .csproj file.
Components can declare dependencies on other components. The CLI automatically installs dependencies when a component is added.
The shellui-native.json file tracks:
- Target platform (MAUI, Avalonia, WinUI, or WPF — see
NativePlatformin ShellUINativeConfig.cs) - Components path
- Installed components with versions
- Theme settings
The CLI detects project types by examining the .csproj file:
| Detection | Platform |
|---|---|
UseMaui=true or SDK contains "Maui" |
MAUI |
PackageReference starting with "Avalonia", or an App.axaml file present |
Avalonia |
UseWinUI=true |
WinUI 3 |
UseWPF=true or SDK contains "Wpf" |
WPF (detection only — see PLAN.md) |
Detection order matters: MAUI is checked first (via SDK/UseMaui), then Avalonia (via
PackageReference/App.axaml, since Avalonia has no dedicated SDK), then WinUI/WPF. See
ProjectDetector.cs.
Current limitation: detection recognizes Avalonia/WinUI/WPF projects, but
ComponentRegistry only has MAUI
templates today. shellui-native add prints a warning and installs MAUI code regardless of
detected platform until per-platform templates exist (see "Component Templates" in Core
Concepts above, and Template System v2 in PLAN.md).
Unlike ShellUI Blazor which uses CSS variables, ShellUI Native uses platform-native theming:
- MAUI - ResourceDictionary with Colors, Styles
- Avalonia -
Styles/ResourceDictionarywithDynamicResource, plus Avalonia's built-in Fluent/Simple theme variants for light/dark switching - WinUI - XAML Resources and ThemeResources
- WPF - ResourceDictionary with DynamicResource (existing apps only, not an active target)
Design tokens are mapped from the ShellUI CSS variables to native equivalents:
| CSS Variable | Native Resource |
|---|---|
--background |
ShellUIBackground |
--foreground |
ShellUIForeground |
--primary |
ShellUIPrimary |
--secondary |
ShellUISecondary |
MAUI components follow the ContentView pattern with BindableProperties:
public partial class Button : ContentView
{
public static readonly BindableProperty VariantProperty =
BindableProperty.Create(nameof(Variant), typeof(ButtonVariant),
typeof(Button), ButtonVariant.Default, propertyChanged: OnVisualPropertyChanged);
public ButtonVariant Variant
{
get => (ButtonVariant)GetValue(VariantProperty);
set => SetValue(VariantProperty, value);
}
private static void OnVisualPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is Button button)
button.UpdateVisualState();
}
}Same model as MAUI: one C# file per component, built in code from Avalonia primitives
(Border, Panel, TextBlock, a custom Render for icons), with no .axaml. The CLI keeps
installing one file per component, and nothing has to be registered in App.axaml.
- Properties are
StyledPropertys; class handlers orAffectsRenderreact to changes. - Colors bind to the same tokens with
control.Token(property, ShellToken.X), a resource-observable binding.ShellThemepublishes the light and dark palettes as theme dictionaries, so Avalonia swaps them whenRequestedThemeVariantchanges. - Names match MAUI where Avalonia allows it. The one difference so far:
IconusesKindfor the icon, because every Avalonia control already has aName. - Usings are explicit.
dotnet new avalonia.apphas implicit usings off, so each component carriesusing System;and the rest; the demo turns them off too, so it catches a missing one.
public class Badge : Border
{
public static readonly StyledProperty<string?> TextProperty =
AvaloniaProperty.Register<Badge, string?>(nameof(Text));
private readonly TextBlock _label = new() { FontSize = 12 };
public Badge()
{
this.Token(BackgroundProperty, ShellToken.Primary);
Child = _label.Token(TextBlock.ForegroundProperty, ShellToken.PrimaryForeground);
}
}Components are developed in examples/Avalonia.Demo/Components/UI/ and copied into the
templates' [NativePlatform.Avalonia] entries by scripts/sync-templates.py. A component gets
an Avalonia entry only after its MAUI one exists, and only if every dependency has one too
(checked by TemplateContentTests).
All packages share a single version defined in Directory.Build.props:
<ShellUINativeVersion>0.1.0</ShellUINativeVersion>
<ShellUINativeVersionSuffix>alpha.1</ShellUINativeVersionSuffix>The build stamps it into the assemblies, and component metadata reads it from there at runtime,
so shellui-native.json records the CLI version each component was installed with.
Releasing: set the version in Directory.Build.props, add a # ShellUI Native v<version>
section to RELEASE_NOTES.md, merge to main, then push a v<version> tag.
.github/workflows/release.yml checks the tag, runs the tests, publishes ShellUI.Native.CLI
through NuGet Trusted Publishing and creates the GitHub release. Setup and steps are in
RELEASING.md.