Skip to content
Mobile
Skill

/maui-custom-handlers

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

BOOST
From plugin
maui-skills
17541 skills
Install
$ npx -y skills add davidortinau/maui-skills --skill maui-custom-handlers --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/maui-custom-handlers

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

maui-custom-handlers.SKILL.md
name: maui-custom-handlers
description: >
  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".
  DO NOT USE FOR: platform API calls without custom controls (use maui-platform-invoke),
  data binding (use maui-data-binding), or gesture handling (use maui-gestures).

.NET MAUI Custom Handlers

Decision: Customize Existing vs. Create New

| Scenario | Approach | |---|---| | Change how a built-in control looks/behaves on one platform | **Customize** — use `AppendToMapping` / `PrependToMapping` | | Need the change on only some instances of a control | **Customize** — subclass the control + type-check in mapper | | Need a completely new cross-platform control with native backing | **Create 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:

// ❌ 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.

// ❌ 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.

// ✅ 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

| Method | Risk | Use when | |---|---|---| | `AppendToMapping` | Low — runs after default | Adding behaviour without breaking defaults | | `PrependToMapping` | Low — runs before default | Setting initial state that the default can override | | `ModifyMapping` | ⚠️ High — replaces default | You intentionally want to suppress the framework's mapper logic |

---

PropertyMapper vs. CommandMapper

| Mapper | Purpose | Pattern | |---|---|---| | `PropertyMapper` | Sync a bindable property to the native view | Runs whenever the property value changes | | `CommandMapper` | Fire-and-forget action from control → handler | Runs 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`
Read more
Ships withmaui-skills

A collection of 41 skills for .NET MAUI development, UI/UX design reasoning, and Xamarin migration, designed for use with GitHub Copilot CLI and Claude Code.

Get the whole plugin

Other skills on maui-skills.