Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

Shiny Controls 1.0 — The Ultra Control Suite for .NET MAUI & Blazor

Shiny Controls 1.0 is here. One control suite, two renderers — native .NET MAUI and real Blazor components — sharing a Material 3 style token contract so a ShinyButton on iOS and a ShinyButton in the browser are the same control with the same API, not two lookalikes maintained in parallel.

  • NuGet package Shiny.Maui.Controls
  • NuGet package Shiny.Blazor.Controls

The whole Blazor gallery is deployed and live. Every control below has a page in it, with the knobs wired up so you can drive the thing rather than read about it:

It is the same Sample.Blazor project that lives in the repo, published to WebAssembly. The theme selector in the header is not a demo affordance — it swaps the real theme pack stylesheet, and every control on every page restyles itself from the tokens.

There are too many controls to introduce one at a time, so here is the whole suite as a table. Each one links to its own docs, which is where the properties, templates and platform notes live.

Category Controls
Flagship TableView · Scheduler · ChatView · ImageEditor
Collections & grids DataGrid · VirtualizedGrid · StaggeredGrid · ParallaxCollectionView · CarouselGallery · Carousel
Layout & overlays Stacks & Grid · AppLayout · FloatingPanel · SheetView · Overlay · Fab & FabMenu · TreeView · FrostedGlassView · Toolbar & TabBar · StateView · Wizard · Walkthrough · Tooltip
Input ShinyButton · TextEntry · AutoCompleteEntry · AddressEntry · CountryPicker · ColorPicker · FontPicker · Slider · RangeSlider · SecurityPin · SignaturePad · MediaPickerButton · DurationPicker · Speech Add-ins
Display & media CameraView · MediaElement · ShinyImage · ImageViewer · Markdown · Mermaid Diagrams · Barcodes & QR · Keyframe Animation · Motion Icons
Status & feedback Toast · Dialogs · ProgressBar · SkeletonView · Splash Screen · PillView · BadgeView · Feedback Service
Desktop Tray Icon · Docking
System Theming — the token contract and the Basic / Ocean / Material / Terminal / Aurora packs

Below are the three worth stopping on.


A full chat surface — bubbles, grouping, avatars, reactions, read receipts, typing indicators, attachments, optimistic send with retry, cursor-based paging that stays stable while messages arrive.

The thing that makes it different from a CollectionView with a bubble template is that it does not bind a Messages collection. You implement an IChatSessionProvider that hands the control a session-scoped IChatSession, and the control subscribes to that session’s live events on attach and disposes it on detach. Paging, live inserts, send verdicts and typing all flow through that one seam, so the control owns the hard part instead of leaving it in your view model.

<shiny:ChatView Provider="{Binding Provider}"
SessionId="{Binding SessionId}"
MyBubbleColor="#DCF8C6"
OtherBubbleColor="White" />
public partial class ChatViewModel(IChatSessionProvider provider) : ObservableObject
{
public IChatSessionProvider Provider { get; } = provider;
public string SessionId { get; } = "demo";
}

That is the whole integration. Permissions drive what the UI offers — PermittedEmojis decides whether the reaction row appears at all, BodyPermissions gates the markdown toolbar — so the control never shows an affordance your backend will reject.

MAUI MAUI — custom templates Blazor
ChatView on MAUI with bubbles, avatars and the composer ChatView with custom message templates Bubbles, reactions and composer on Blazor

ChatView docs


Dim the page, cut an animated spotlight around one control at a time, say what it does. Onboarding, feature announcements, and the workflow someone only does once a quarter.

The design decision worth calling out: the steps live together on the walkthrough, in order — they are not attached properties on the controls they describe. On a real screen, with nested layouts and templated cells and a panel that is only sometimes there, attached ordering scatters the sequence across the markup where nothing can see it as a whole. Reordering becomes a hunt, and a step whose control is conditionally hidden silently derails everything after it. Here, reordering is moving a line, and IsVisible="False" drops a step out of the run and re-numbers the counter.

<shiny:Walkthrough RememberRunKey="home-v1" AutoStart="True" OverlayOpacity="0.8">
<!-- No target: a centred welcome card, no cut-out. -->
<shiny:WalkthroughStep Title="Welcome"
Text="Here is what is new in this release."
AnimationIn="Pop" />
<shiny:WalkthroughStep Target="{x:Reference SearchBox}"
Title="Find anything"
Text="Search across every project you can see."
Placement="Bottom" />
<!-- No card at all; the cut-out does the pointing. -->
<shiny:WalkthroughStep Target="{x:Reference Avatar}"
Title="Your profile"
Text="Settings and sign-out live here."
Display="Spotlight"
Highlight="Circle" />
<!-- Live control: the tap reaches it through the hole, and using it advances. -->
<shiny:WalkthroughStep Target="{x:Reference SaveButton}"
Text="Press Save to finish."
AllowTargetInteraction="True"
AdvanceOnTargetTap="True" />
</shiny:Walkthrough>

RememberRunKey is what makes onboarding run once — it is backed by a replaceable IWalkthroughStore (Preferences on MAUI, localStorage on Blazor), and Restart() clears it. The tour paints into a layer above the page content, so a target inside a scroll view or a card gets highlighted where it actually is instead of being clipped by its container.

Welcome Popover on a target Circular spotlight Live target
A centred welcome card over the dimmed page on MAUI The spotlight around the search box with a popover below it on MAUI A circular cut-out around the avatar on MAUI The Save button live through the cut-out on MAUI
The same welcome card on Blazor The same search-box spotlight and popover on Blazor The same circular avatar cut-out on Blazor The same live Save button on Blazor

Top row MAUI (iOS), bottom row Blazor — same four steps, same XAML-shaped markup.

Walkthrough docs


Three views — a monthly calendar grid, a day/multi-day agenda timeline, and a vertically scrolling event list — over one data interface. You write the data layer once and pick the view per screen.

public class MyEventProvider : ISchedulerEventProvider
{
public async Task<IReadOnlyList<SchedulerEvent>> GetEvents(
DateTimeOffset start, DateTimeOffset end)
=> await myService.GetEventsAsync(start, end);
public void OnEventSelected(SchedulerEvent selectedEvent) { /* navigate, show a sheet… */ }
public bool CanCalendarSelect(DateOnly date) => true;
public void OnCalendarDateSelected(DateOnly date) { }
public void OnAgendaTimeSelected(DateTimeOffset time) { }
public bool CanSelectAgendaTime(DateTimeOffset time) => true;
}
<scheduler:SchedulerCalendarView Provider="{Binding Provider}"
SelectedDate="{Binding SelectedDate}" />

Multi-day events span correctly across all three views, the agenda draws a live current-time marker and supports extra timezone columns with sticky headers, the event list scrolls infinitely in both directions, and every visual element — events, headers, loaders, day pickers — is replaceable with a DataTemplate. Bindings use the static lambda overloads throughout, so it is AOT-safe with no string-based reflection.

Calendar Agenda Event list Agenda + picker
Monthly calendar grid Agenda timeline Event list Agenda with calendar picker

Scheduler docs


Screenshots of a camera control are famously useless — a picture of a preview is just a picture — so here is what it actually does instead.

CameraView is a cross-platform camera for MAUI (AVFoundation on iOS / Mac Catalyst / macOS, CameraX on Android, Media Capture on Windows) with a matching Blazor WebAssembly control over getUserMedia. Live preview, lens and device selection, pinch-to-zoom, torch, flash, photo capture and video recording with quality/bitrate/frame-rate control are the table stakes.

The two things that set it apart are pluggable pipelines, and they compose with each other:

The frame-analysis pipeline. Assign a single IFrameAnalyzer — declared right in XAML, since the analyzer is the content property of CameraView — and frames stream to it off the UI thread with drop-on-busy back-pressure. Bounding boxes draw continuously via CameraOverlayView, but results are delivered on a gated scan trigger: arm with Scan() and the next confirmed detection fires once. An optional ScanWindow restricts detection to a region and draws an aim reticle. Built-in analyzers cover barcode/QR (native Vision and MLKit, restrictable by symbology), face detection with landmarks, motion clustered into debounced regions, OCR with scan-window crop and upscale for small text, and structured documents — invoices with order lines, receipts with line items and per-tax breakdowns, business cards, AAMVA driver’s licences, province-aware Canadian health cards, credit cards and passport MRZ — each a strong record with nullable fields. When the document is free-form, AiDocumentAnalyzer<T> detects presence cheaply on every frame and sends exactly one frame to a Microsoft.Extensions.AI IChatClient for structured extraction.

The effects pipeline. Effects is an ordered, live collection applied to the preview, captured stills and — on Apple — recorded video. Mutate it while the camera runs and the change lands on the next frame. Eleven colour grades, five spatial GPU looks (comic, sketch, posterize, pixelate, blur), compositing draw effects for watermarks and face masks anchored to tracked facial landmarks, and slow post-capture transforms such as AI photo stylization through an MEAI IImageGenerator. Four extension points let you add your own at the right layer. And because per-platform coverage is genuinely uneven, GetEffectSupport(effect) reports Full / ColorOnly / StillOnly / Unsupported so your UI can grey out what would otherwise silently do nothing.

Two more that are easy to miss: you can record and analyse at the same time on every platform — a dash-cam app reading signs off its own live feed while recording — and VideoRecordingOptions.Overlay burns a watermark, timestamp or telemetry into every encoded frame, drawn with Microsoft.Maui.Graphics so one implementation covers every platform.

  • NuGet package Shiny.Maui.Controls.Camera
  • NuGet package Shiny.Blazor.Controls.Camera

CameraView docs


None of the above is styled by hand. Colour roles, surfaces, shape, elevation, typography, density, borders, state and spacing are a token contract the controls read — SetDynamicResource on MAUI, var(--shiny-*) on Blazor. The core packages define the contract and a built-in Basic theme; Ocean, Material, Terminal and Aurora install as separate NuGet packs and swap the whole app’s look without touching a page.

Basic Ocean Material Terminal Aurora
The button gallery under the Basic theme Under the Ocean pack Under the Material pack Under the Terminal pack Under the Aurora pack

Want your own? The Theme Creator takes a few seed colours and exports the theme JSON, the Blazor CSS, or the MAUI C#.

Terminal window
dotnet add package Shiny.Maui.Controls # .NET MAUI
dotnet add package Shiny.Blazor.Controls # Blazor

Then go press things in the playground, and see the controls documentation for the rest.

7 min read