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.
Click it before you install it
Section titled “Click it before you install it”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.
The catalogue
Section titled “The catalogue”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.
ChatView
Section titled “ChatView”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 |
|---|---|---|
![]() |
![]() |
![]() |
Walkthrough
Section titled “Walkthrough”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 |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Top row MAUI (iOS), bottom row Blazor — same four steps, same XAML-shaped markup.
Scheduler
Section titled “Scheduler”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 |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
CameraView
Section titled “CameraView”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.
Theming ties it together
Section titled “Theming ties it together”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 |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
Want your own? The Theme Creator takes a few seed colours and exports the theme JSON, the Blazor CSS, or the MAUI C#.
Getting started
Section titled “Getting started”dotnet add package Shiny.Maui.Controls # .NET MAUIdotnet add package Shiny.Blazor.Controls # BlazorThen go press things in the playground, and see the controls documentation for the rest.






















