Skip to content

OnPlatform/OnIdiom XAML markup extensions hardcode a closed platform/idiom set — custom backends can't express platform-conditional values #35695

Description

@Redth

Part of #34099.

The XAML markup-extension forms {OnPlatform ...} and {OnIdiom ...} hardcode a closed, sealed set of platforms/idioms, so a third-party platform backend (e.g. Linux/GTK, or a Web/WASM backend) cannot express platform-conditional values in XAML the way first-party platforms can. The element forms (OnPlatform<T> / On) are already data-driven and work for custom platforms — so the inconsistency is purely in the markup extensions.

Problem

OnPlatformExtension (Microsoft.Maui.Controls.Xaml)

The platform values are fixed, named properties and several aren't even public:

public object Android { get; set; }
internal object GTK { get; set; }   // internal!
public object iOS { get; set; }
internal object macOS { get; set; } // internal!
public object MacCatalyst { get; set; }
public object Tizen { get; set; }
public object UWP { get; set; }     // [Obsolete]
internal object WPF { get; set; }   // internal!
public object WinUI { get; set; }

Matching is a hardcoded if-chain rather than a data-driven lookup:

private bool TryGetValueForPlatform(out object value)
{
    if (DeviceInfo.Platform == DevicePlatform.Android && Android != s_notset) { ... }
    if (DeviceInfo.Platform == DevicePlatform.Create("GTK") && GTK != s_notset) { ... }
    if (DeviceInfo.Platform == DevicePlatform.iOS && iOS != s_notset) { ... }
    // ... macOS, MacCatalyst, Tizen, WinUI, UWP — fixed set, no extensibility
}

There is no way to add a value for a platform string the framework doesn't already know about (e.g. Web, Browser, Linux). Because OnPlatformExtension is a sealed-by-convention framework type with a fixed property set, it also can't be subclassed to add one.

OnIdiomExtension (Microsoft.Maui.Controls.Xaml)

Same shape — fixed properties (Phone, Tablet, Desktop, TV, Watch) and a hardcoded if-chain over DeviceIdiom. DeviceIdiom.Create(...) allows custom idioms at runtime, but the markup extension can't express them. (Lower priority — most backends can map to one of the existing five idioms — but it's the same class of problem.)

Contrast: the element form already works

OnPlatform<T> / On are fully data-driven and already support arbitrary platform strings:

public class On
{
    public IList<string> Platform { get; set; }  // arbitrary strings
}

// OnPlatform<T> implicit conversion:
else if (platform.Platform.Contains(DeviceInfo.Platform.ToString())) { ... }

So a custom backend can already do this today:

<View.Margin>
    <OnPlatform x:TypeArguments="Thickness">
        <On Platform="Default" Value="0" />
        <On Platform="Web" Value="0,0,0,20" />
    </OnPlatform>
</View.Margin>

…as long as DeviceInfo.Platform.ToString() returns the custom string (see the DeviceInfo dependency below). This proves the matching can be data-driven without breaking first-party platforms — the markup extensions just haven't been generalized.

Proposed solutions (any one would close the gap)

  1. Make matching data-driven in the markup extensions. Resolve the value by comparing DeviceInfo.Platform.ToString() (and DeviceInfo.Idiom.ToString()) against a case-insensitive key, the same way the element form does. Keep the existing named properties as sugar that populate the same dictionary, so first-party usage is unchanged.
  2. Allow arbitrary keys via an indexer / content collection on OnPlatformExtension (e.g. accept <On Platform="Web">-style children, or a string-keyed dictionary), so unknown platforms round-trip.
  3. At minimum: make GTK/macOS/WPF public and document the element form as the supported route for non-first-party platforms, and ensure the docs/analyzers don't steer people exclusively to the markup-extension form.

DeviceInfo dependency

Both forms ultimately read the static DeviceInfo.Platform / DeviceInfo.Idiom. For a custom backend these resolve to DevicePlatform.Unknown unless the backend installs its own IDeviceInfo via the internal DeviceInfo.SetCurrent(IDeviceInfo) (reflection workaround today). That registration gap is tracked by #34100 (which covers Microsoft.Maui.Devices); this issue is specifically about the XAML markup extensions' closed value set, which remains a gap even after DeviceInfo is registrable.

What we're NOT asking for

  • Not asking MAUI to officially support any new platform.
  • Not asking to change the first-party named properties' behavior.

Just asking the markup extensions to stop hardcoding a closed set, so community backends get the same XAML ergonomics first-party platforms already enjoy (and that the element form already provides).

Environment

  • .NET 10
  • Custom backends: Maui.Gtk (Linux), and a Web/WASM GDUI backend
  • Assemblies inspected: Microsoft.Maui.Controls.Xaml 10.0.60 (OnPlatformExtension, OnIdiomExtension), Microsoft.Maui.Controls.Core 10.0.60 (OnPlatform<T>, On)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-xamlXAML, CSS, Triggers, Behaviors

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions