Skip to content
Shiny.NET
Commercial SupportGuaranteed response times on Shiny bug fixes, straight from the maintainers. Plans from US$1,500 a year.See the plans

.NET MAUI on the Web - and Most of Shiny Controls Just Works

Platform.Maui.Web is a .NET MAUI backend for the browser. Your MAUI app runs on WebAssembly and renders real DOM elements: <button>, <input>, <dialog>, SVG for shapes and borders, a 2D canvas for GraphicsView. There is no Blazor, no Razor components and no canvas-drawn UI. MAUI’s own cross-platform layout engine positions everything, so Grid, FlexLayout, AbsoluteLayout and your custom layouts behave the way they do on every other platform.

NuGet package Platform.Maui.Web NuGet package Platform.Maui.Web.Essentials

It lives in shinyorg/mauiplatforms next to the Apple TV backend, and it follows the structure and lessons of the backends in dotnet/maui-labs (Linux GTK4, macOS AppKit). It’s experimental: the current release is 0.4.0, and it stays below 1.0 while the API settles.

The MAUI Web gallery sample running in the browser: a FlyoutPage menu with emoji icon tiles and a live GraphicsView hero

The real test of a new MAUI backend isn’t its own sample, though. It’s somebody else’s app. So we took the biggest MAUI app we have, the Shiny Controls gallery, and pointed it at the browser.

The gallery has a Linux GTK4 head that compiles the shared pages on plain net10.0. The web head is the same idea: a Microsoft.NET.Sdk.WebAssembly project that links the gallery’s XAML and C# and swaps UseMauiAppLinuxGtk4<App>() for UseMauiAppWeb<App>(). No page was changed for the browser.

The result: 68 demo pages, published as a trimmed Release build, and every one of them opens without an exception. We drove each page in Chromium with Playwright and checked the console as well as the screen.

The Shiny Controls gallery home page running in the browser on Platform.Maui.Web

What renders and responds:

Area Controls
Data & layout DataGrid and TreeDataGrid, TreeView, TableView (all 14 cell types, dynamic sections, drag-to-sort), Expander, Docking, CoverFlow, Parallax CollectionView, ShinyFlexLayout, YogaLayout
Scheduler Calendar, Agenda, Calendar Picker, Event List
Input Text entry (M3 floating labels), AutoComplete, TagEntry and ChipGroup, Color Picker, Country & Address, Duration Picker, Font Picker, Slider, Range Slider, Range Pickers, Security Pin, Password Strength, Signature Pad, On-Screen Keyboard, Keyboard Shortcuts
Actions & navigation ShinyButton, ButtonGroup, FAB and FabMenu, StateView, Wizard, Walkthrough, Tooltip
Status & feedback Pills, Badge, Avatar, Notch, Toast, Dialogs, Feedback, Progress Bar, Gauge, Skeleton
Animation Confetti, Border Animations, Motion Icons
Content Markdown Viewer and Editor, Mermaid diagrams (flowcharts, directions, themes, subgraphs, the interactive editor)
Overlays & media Overlay, Frosted Glass, Image Viewer, Image Gallery, Media Picker
Shiny DataGrid in the browser Shiny Scheduler calendar in the browser
Shiny Docking panels in the browser Shiny Gauge dials in the browser
A Mermaid flowchart rendered by Shiny MermaidDiagrams in the browser Shiny CoverFlow in the browser

Why does so much of it work? Most Shiny Controls are composed from MAUI primitives: Grid, Border, Label, GraphicsView, shapes, gestures and animations. The web backend implements those primitives with real DOM elements, so a control built on them comes along for free. Desktop-only features degrade the way they do on any platform without them: the System Tray page, for example, says tray icons aren’t available on this platform.

Controls that bring their own native view need a browser handler before they can run, and some of those don’t have one yet:

  • MediaElement. Shiny’s MediaElement has native handlers (Media3/ExoPlayer, AVPlayer, GStreamer on Linux), but none for the browser yet. An HTML <video> element is the obvious fit.
  • SkiaSharp views. SKCanvasView has no web handler, so everything that paints with Skia waits on it: the Office editors (spreadsheet, documents, slides, notebooks) and FloorPlan.
  • CarouselGallery, StaggeredGrid and VirtualizedGrid. Their handlers wrap RecyclerView, UICollectionView and ScrollViewer, with nothing for the browser yet, so the web head leaves them out of its menu.
  • Images inside GraphicsView. The browser decodes and draws them, but MAUI’s net10.0 PlatformImage can’t, so it reports the wrong size. The ImageEditor canvas shows up black for this reason.
  • Remote ShinyImage downloads. On the MAUI host, ShinyImage downloads through HttpClient, which in the browser is fetch and subject to CORS. Servers that don’t send CORS headers fail, and the progress ring doesn’t finish yet.
  • Not tried yet. The web head leaves out the same pages as the Linux head: Camera and Barcodes, Chat and the speech add-ins, FloatingPanel, Keyframe, Gamepad and the Diagram add-on. Some of these have browser APIs to build on (getUserMedia, BarcodeDetector, the Gamepad API), and Shiny’s Blazor controls already use them.

For the backend itself, the web guide lists the rest. The two you’re most likely to hit are that CollectionView isn’t virtualized yet and that pinch gestures and drag & drop aren’t implemented.

You need the .NET 10 SDK and nothing else (no workload). Create a Microsoft.NET.Sdk.WebAssembly project:

<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationTitle>My App</ApplicationTitle>
<ApplicationId>com.example.myapp</ApplicationId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Platform.Maui.Web" Version="0.4.0" />
<PackageReference Include="Platform.Maui.Web.Essentials" Version="0.4.0" />
<PackageReference Include="Shiny.Maui.Controls" Version="*-*" />
</ItemGroup>
</Project>

Then register the backend instead of UseMauiApp<App>(), and add Shiny Controls exactly as you would on a phone:

public class MyWebApp : WebMauiApplication
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
public static class Program
{
public static Task Main() => new MyWebApp().RunAsync();
}
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiAppWeb<App>() // instead of UseMauiApp<App>()
.UseWebEssentials()
.UseShinyControls()
.ConfigureFonts(f => f.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"));
return builder.Build();
}
}

Add a wwwroot/index.html that loads a main.js (dotnet.create() then runMain()), and run dotnet run, or dotnet publish -c Release for static files you can host anywhere. The setup guide has every file.

XAML needs nothing web-specific. Keep your app project as it is and add a web project beside it that links the shared XAML and C#, leaving the Platforms folder and the app’s MauiProgram.cs behind. That’s how the Shiny Controls web head is built, and the web guide has a complete project file. Some things we learned doing it:

  • OnPlatform matches Web, and #if ANDROID/#if IOS blocks compile with neither defined (WEB and BROWSER are).
  • Trimming is on in Release. Prefer compiled bindings (x:DataType), and use IQueryAttributable rather than [QueryProperty], which MAUI turns off in trimmed builds.
  • The browser serves images and raw assets from the same root. If a MauiImage and a MauiAsset share a file name, as the Shiny gallery’s two copies of dotnet_bot.png did, the build stops with a static web asset conflict. Rename or exclude one of them.
  • Remote images and HTTP calls go through fetch, so the server you call needs to send CORS headers.

MediaElement on <video>, a SkiaSharp view, browser handlers for the three native collection controls, and virtualization in CollectionView are the gaps that matter most to Shiny Controls. If there’s a control you need in the browser, open an issue on shinyorg/mauiplatforms.

6 min read