davidortinau/maui-skills/maui-custom-handlers

Claude Code and agent skill by davidortinau/maui-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Guide for creating custom .NET MAUI handlers, customizing existing handlers with property mappers, and implementing platform-specific native views. Covers PrependToMapping/ModifyMapping/AppendToMapping, PropertyMapper, CommandMapper, partial handler classes, and handler registration. USE FOR: "custom handler", "PropertyMapper", "AppendToMapping", "PrependToMapping", "ModifyMapping", "CommandMapper", "platform-specific rendering", "native view", "handler registration", "custom control renderer".…

Install

npx vskill@latest install davidortinau/maui-skills/maui-custom-handlers

SKILL.md

.NET MAUI Custom Handlers

Decision: Customize Existing vs. Create New

ScenarioApproach
Change how a built-in control looks/behaves on one platformCustomize — use AppendToMapping / PrependToMapping
Need the change on only some instances of a controlCustomize — subclass the control + type-check in mapper
Need a completely new cross-platform control with native backingCreate new handler with partial classes
⚠️ Prefer `AppendToMapping` over ModifyMapping. ModifyMapping replaces
the default mapper action entirely — if the framework adds behaviour in a
future release, your override silently drops it.

Gotchas & Common Mistakes

Mapper customizations are global

Every instance of the control is affected. Guard with a subclass check for instance-specific behaviour:

csharp
// ❌ Removes borders from EVERY Entry in the app
EntryHandler.Mapper.AppendToMapping("NoBorder", (handler, view) =>
{
#if ANDROID
handler.PlatformView.Background = null;
#endif
});
// ✅ Only affects BorderlessEntry instances
EntryHandler.Mapper.AppendToMapping("NoBorder", (handler, view) =>
{
if (view is not BorderlessEntry) return;
#if ANDROID
handler.PlatformView.Background = null;
#endif
});
Unsubscribe native events in HandlerChanging

Failing to remove native event handlers causes memory leaks because the native view may outlive the managed wrapper.

csharp
// ❌ Subscribes but never unsubscribes — leaks
entry.HandlerChanged += (s, e) =>
{
#if ANDROID
((Entry)s!).Handler!.PlatformView.As<Android.Widget.EditText>()!
.FocusChange += OnNativeFocusChange;
#endif
};
// ✅ Pair subscribe in HandlerChanged with unsubscribe in HandlerChanging
entry.HandlerChanged += OnHandlerChanged;
entry.HandlerChanging += OnHandlerChanging;
Partial class name/namespace mismatch

Namespace and class name must match exactly across the shared handler file and every platform file. A mismatch silently creates separate classes — no compiler error, just a handler that does nothing on that platform.

Conditional using placement

The using PlatformView = ... aliases must be at the top of the shared handler file (not the platform files) so the ViewHandler<TControl, TPlatformView> base-class generic resolves correctly per platform.

csharp
// ✅ Top of Handlers/VideoPlayerHandler.cs
#if ANDROID
using PlatformView = Android.Widget.VideoView;
#elif IOS || MACCATALYST
using PlatformView = AVKit.AVPlayerViewController;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.Controls.MediaPlayerElement;
#endif
Missing CreatePlatformView()

Each platform partial must override CreatePlatformView(). Omitting it produces a compile error — but the error message points at the base class, not your handler, making it confusing to debug.


Mapper Method Selection

MethodRiskUse when
AppendToMappingLow — runs after defaultAdding behaviour without breaking defaults
PrependToMappingLow — runs before defaultSetting initial state that the default can override
ModifyMapping⚠️ High — replaces defaultYou intentionally want to suppress the framework's mapper logic

PropertyMapper vs. CommandMapper

MapperPurposePattern
PropertyMapperSync a bindable property to the native viewRuns whenever the property value changes
CommandMapperFire-and-forget action from control → handlerRuns once per invocation, no return value
⚠️ Don't put property sync logic in CommandMapper — it won't re-run when
the property changes, leading to stale native views.

Checklist — New Handler

  • [ ] Cross-platform control inherits View (or appropriate base)
  • [ ] Shared handler file has conditional using PlatformView = ... aliases
  • [ ] Handler inherits ViewHandler<TControl, PlatformView>
  • [ ] PropertyMapper maps every bindable property
  • [ ] Each platform partial overrides CreatePlatformView()
  • [ ] Namespace + class name identical across all partial files
  • [ ] Handler registered in MauiProgram.cs via ConfigureMauiHandlers
  • [ ] Native event subscriptions cleaned up in HandlerChanging

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
176
Repository forks
21
7-day trend score
23

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 22 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/davidortinau/maui-skills/maui-custom-handlers/badge)](https://verified-skill.com/skills/davidortinau/maui-skills/maui-custom-handlers)

Something wrong with this skill or its page? Tell us.