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

Feedback Service

Every interactive control in the library fires feedback events through IFeedbackService — a single, injectable service that lets you customize how your app responds to user interactions. By default, it triggers haptic feedback. Replace it with text-to-speech, sound effects, analytics, or any combination.

  • NuGet downloads for Shiny.Maui.Controls
Frameworks
.NET MAUI
Feedback service demo

All Shiny controls that support feedback have a UseFeedback property (default: true). When an interaction occurs — a button tap, a pin digit entered, a toast shown, a message received — the control calls IFeedbackService.OnRequested() with:

Parameter Description
control The actual control instance (e.g. the ChatView, SecurityPin, Button) — use pattern matching like control is ChatView to identify the source
eventName The interaction that occurred (e.g. "MessageSent", "Completed", "Clicked")
args Optional context — for ChatView, this is the ChatMessage object; for standard MAUI controls, the native EventArgs; "LongPress" string for SecurityPin completion

UseShinyControls() registers HapticFeedbackService as the default implementation. It performs a click haptic for most events and a long press haptic when eventName is "LongPress".

builder.UseShinyControls(); // registers HapticFeedbackService by default

Replace the default by calling SetCustomFeedback<T>() during setup:

builder.UseShinyControls(cfg =>
{
cfg.SetCustomFeedback<MyFeedbackService>();
});
public class ChatTtsFeedbackService(ITextToSpeech tts) : HapticFeedbackService
{
public override async void OnRequested(object control, string eventName, object? args)
{
// Let haptic do its thing first
base.OnRequested(control, eventName, args);
// Speak incoming chat messages aloud
if (control is ChatView && eventName == "MessageReceived" && args is ChatMessage { IsFromMe: false } msg)
{
var say = $"Message from {msg.SenderId}. {msg.Text}";
await tts.SpeakAsync(say);
}
}
}
public class SoundFeedbackService : IFeedbackService
{
public void OnRequested(object control, string eventName, object? args)
{
var sound = (control, eventName) switch
{
(SecurityPin, "Completed") => "success.wav",
(SecurityPin, "DigitEntered") => "click.wav",
(ChatView, "MessageSent") => "swoosh.wav",
(ChatView, "MessageReceived") => "chime.wav",
_ => null
};
if (sound is not null)
PlaySound(sound);
}
}

Beyond Shiny’s own controls, you can opt in to automatic feedback for standard MAUI controls — Button, Entry, Slider, Switch, and more. The hook system is fully pluggable and AOT-compatible. It uses Application.DescendantAdded/DescendantRemoved to hook into the visual tree lifecycle, binding and unbinding events automatically with no modifications to the controls themselves.

builder.UseShinyControls(cfg =>
{
cfg.AddDefaultMauiControlFeedback();
});

Add your own control hooks on top of the built-in defaults:

cfg.AddDefaultMauiControlFeedback(x =>
{
x.Hook<MyCustomControl>(nameof(MyCustomControl.Tapped),
(c, h) => c.Tapped += h,
(c, h) => c.Tapped -= h);
});

Use AddMauiControlFeedback to register only the hooks you want:

cfg.AddMauiControlFeedback(x =>
{
x.Hook<Button>(nameof(Button.Clicked),
(btn, h) => btn.Clicked += h,
(btn, h) => btn.Clicked -= h);
x.Hook<Slider, ValueChangedEventArgs>(nameof(Slider.ValueChanged),
(s, h) => s.ValueChanged += h,
(s, h) => s.ValueChanged -= h);
});

Two Hook overloads are available:

  • Hook<TControl>(eventName, subscribe, unsubscribe) — for EventHandler events (args passed as EventArgs)
  • Hook<TControl, TEventArgs>(eventName, subscribe, unsubscribe) — for EventHandler<TEventArgs> events (typed args passed through)

The control parameter is the actual control instance, and args is the native event args object.

Control Event Args Description
Button Clicked EventArgs Button tapped
Entry TextChanged TextChangedEventArgs Text input changed
Slider ValueChanged ValueChangedEventArgs Slider moved
Switch Toggled ToggledEventArgs Switch toggled
CheckBox CheckedChanged CheckedChangedEventArgs Checkbox toggled
DatePicker DateSelected DateChangedEventArgs Date selected
TimePicker TimeChanged PropertyChangedEventArgs Time changed
Picker SelectedIndexChanged EventArgs Selection changed
SearchBar SearchButtonPressed EventArgs Search submitted
Stepper ValueChanged ValueChangedEventArgs Stepper changed
Editor TextChanged TextChangedEventArgs Editor text changed
RadioButton CheckedChanged CheckedChangedEventArgs Radio button toggled

Events are properly unhooked when controls are removed from the visual tree, ensuring no memory leaks.

Control Event Name Args Description
ChatView MessageSent ChatMessage User sent a message
ChatView MessageReceived ChatMessage External message arrived
ChatView MessageTapped ChatMessage User tapped a message bubble
ChatView AttachImage User tapped the attach button
Fab Clicked Fab button tapped
FabMenu Toggled Menu opened or closed
FabMenuItem Clicked Menu item tapped
FloatingPanel Opened Panel finished opening
FloatingPanel Closed Panel finished closing
FloatingPanel DetentChanged Panel snapped to a detent
ImageViewer Opened Viewer overlay opened
ImageViewer Closed Viewer overlay closed
ImageViewer DoubleTapped Double-tap zoom toggle
ImageEditor ToolModeChanged mode name Active tool changed (Move, Crop, Draw, Text, Line, Arrow)
ImageEditor Undo Undo action
ImageEditor Redo Redo action
ImageEditor Rotate Image rotated
ImageEditor Reset Reset to original
ImageEditor CropApplied Crop applied
ImageEditor Saved Image saved
SecurityPin Completed "LongPress" All digits entered
SecurityPin DigitEntered A digit was entered
SchedulerCalendarView DaySelected Calendar day tapped
SchedulerCalendarView EventSelected Calendar event tapped
SchedulerAgendaView EventSelected Agenda event tapped
SchedulerAgendaView TimeSlotSelected Agenda time slot tapped
CellBase (all cells) Tapped Table cell tapped
Toaster Show toast text Toast shown

Set UseFeedback="False" on any control to suppress feedback for that instance:

<shiny:FloatingPanel UseFeedback="False" ... />
<shiny:SecurityPin UseFeedback="False" ... />

For toast:

await toaster.ShowAsync("Silent toast", cfg => cfg.UseFeedback = false);
claude plugin marketplace add shinyorg/skills
claude plugin install shiny@shiny

One plugin installs all 35 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.

copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny@shiny

One plugin installs all 35 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.

View shiny-controls Skill