Me.Toolkit.Maui.Sizing
26.9.28
dotnet add package Me.Toolkit.Maui.Sizing --version 26.9.28
NuGet\Install-Package Me.Toolkit.Maui.Sizing -Version 26.9.28
<PackageReference Include="Me.Toolkit.Maui.Sizing" Version="26.9.28" />
<PackageVersion Include="Me.Toolkit.Maui.Sizing" Version="26.9.28" />
<PackageReference Include="Me.Toolkit.Maui.Sizing" />
paket add Me.Toolkit.Maui.Sizing --version 26.9.28
#r "nuget: Me.Toolkit.Maui.Sizing, 26.9.28"
#:package Me.Toolkit.Maui.Sizing@26.9.28
#addin nuget:?package=Me.Toolkit.Maui.Sizing&version=26.9.28
#tool nuget:?package=Me.Toolkit.Maui.Sizing&version=26.9.28
Me.Toolkit.Maui.Sizing
Relative sizes for .NET MAUI: a percentage of the parent, the window, the display or another
element, recomputed as it changes — the way CSS %, vw and vh work.
MAUI sizes are absolute numbers. WidthRequest="100" is the same 100 on a phone and on a 4K
monitor, and OnIdiom only lets you pick a different absolute number per device. This adds a
relative one.
Use
<ContentPage xmlns:me="https://github.com/melihercan/Me.Toolkit.Maui" ...>
<Image WidthRequest="{me:Relative 30}" />
<Label FontSize="{me:Relative 3, To=Window, Min=12, Max=40}" />
<Image WidthRequest="{me:Relative 15, To=Display, Axis=Shorter}"
HeightRequest="{me:Relative 15, To=Display, Axis=Shorter}" />
<BoxView WidthRequest="{me:Relative 50, Source={x:Reference Card}}" />
<BoxView WidthRequest="{me:Relative 50, AncestorType={x:Type Border}}" />
No registration is needed. It works on any double property — WidthRequest, HeightRequest,
FontSize, Spacing, MaximumWidthRequest and so on — and on Margin and Padding:
Padding="{me:Relative 2, To=Window, Min=8, Max=24}" puts the value on every side, and
Sides=Horizontal — or Vertical, Left, Top Left and so on — on only those, the rest 0.
| Option | Values | Default |
|---|---|---|
Percent (first argument) |
30 is 30% |
— |
Portrait, Landscape |
a different percentage per window orientation | Percent |
Breakpoints |
percentages by window width: '600:50 1200:33' |
none |
To |
Parent, Window, Display, Self |
Parent |
Offset |
added after the percentage, may be negative | 0 |
Axis |
Width, Height, Shorter, Longer |
Width |
Min, Max |
clamp the result | 0, no limit |
BreakpointsBy |
Window, or Reference for a container query |
Window |
Round |
Units, Pixels or None, before the clamp |
Units |
Sides |
which sides of a margin or padding | All |
SafeArea |
with To=Window, leave out the notch and system bars |
False |
Source |
an element, usually {x:Reference} |
— |
AncestorType |
the nearest ancestor of this type | — |
From code:
label.SetRelativeSize(Label.FontSizeProperty,
new RelativeSize(3) { To = SizeReference.Window, Min = 12, Max = 40 });
Aspect ratios and gaps
To=Self measures the element itself, padding included, so a height can follow the element's own
width:
<Image WidthRequest="{me:Relative 50}" HeightRequest="{me:Relative 56.25, To=Self}" />
It must read the other dimension than the one it sets — HeightRequest from Width,
WidthRequest from Height — and anything else is refused, since it would chase its own value.
Offset is added after the percentage and before Min/Max, which is CSS's calc():
<HorizontalStackLayout Spacing="8">
<BoxView WidthRequest="{me:Relative 50, Offset=-4, AncestorType={x:Type VerticalStackLayout}}" />
<BoxView WidthRequest="{me:Relative 50, Offset=-4, AncestorType={x:Type VerticalStackLayout}}" />
</HorizontalStackLayout>
Breakpoints
Like CSS media queries, Breakpoints choose the percentage by the window's width:
<Border WidthRequest="{me:Relative 100, Breakpoints='600:50 1200:33', Offset=-8}" />
Pairs are width:percent, separated by spaces, in any order. Below the smallest width, Percent
applies; widths are the window's, in layout units, so they mean the same on every platform. They
follow the window as it is resized. Breakpoints cannot be combined with Portrait/Landscape —
both choose the percentage, and rather than pick a winner it is refused.
BreakpointsBy=Reference compares the reference's own width instead — the parent, a named element,
whatever the size is measured against — like a CSS container query. A card can then lay itself out
one way when it is narrow and another when it is wide, wherever it is placed, and it needs no window.
<Image WidthRequest="{me:Relative 100, Breakpoints='600:50', BreakpointsBy=Reference}" />
The safe area
SafeArea=True measures the window less its safe area — the notch or display cutout, the status bar,
the home indicator — so full-width content on a phone in landscape is not cut by the notch:
<Image WidthRequest="{me:Relative 100, To=Window, SafeArea=True}" />
It applies to To=Window only; a parent or element is already inside whatever safe area the page
keeps. The insets come from the platform — iOS and Mac Catalyst's UIWindow.SafeAreaInsets,
Android's system-bar and display-cutout insets — because MAUI 10 reports them cross-platform nowhere.
A Windows window has none. Platforms can report new insets just after the rotation that brings them,
so the size is recomputed once more shortly after the window resizes.
Rounding
Sizes are whole numbers by default — a 15.4 font is 15, a 300.444 width is 300 — because a
fraction of a unit is below anything anyone can see, and only makes values harder to read. Rounding
happens before Min/Max, so it never escapes them.
Round=Units, the default, rounds to the nearest whole device-independent unit, halves up. Not whole pixels on a display scaled by a fraction — at 150% an odd value lands between two, and the platform nudges it by less than half a pixel: 469 rendered as 469.333 on Windows.Round=Noneleaves the value as calculated.Round=Pixelsrounds to whole physical pixels of the element's window, so what is requested is what is drawn: requested 591.3333, rendered 591.3333 — 887 pixels at 150%. It uses the density of the monitor the window is on and re-rounds when the window moves to one scaled differently. Outside a window it rounds to whole units.
Rounding a value close to 100% of its parent can round up past the space available, by up to half a unit or pixel, which layout then trims.
In styles
{me:Relative} cannot go in a Style setter — a setter is shared, and a relative size is tracked
per element. RelativeSizing's attached properties bridge the two:
<Style TargetType="Label">
<Setter Property="me:RelativeSizing.FontSize" Value="3, To=Window, Min=12, Max=40" />
</Style>
The value is text with the same option names as {me:Relative}: an optional leading percentage,
then Name=Value pairs. Numbers are read the same way in every culture. Source and AncestorType
cannot be written as text, since text cannot name an element or a type.
RelativeSizing.WidthRequest, HeightRequest, FontSize, Margin and Padding are available,
and work directly on an element too: <BoxView me:RelativeSizing.WidthRequest="25, Offset=10" />.
FontSize and Padding find the property of whichever element they are on, including third-party
controls with a FontSizeProperty or PaddingProperty. Breakpoints=600:50 1200:33,
Round=Pixels and Sides=Left Top are written the same way.
Removing the style removes the size, and the property goes back to its default.
From code, element.ClearRelativeSize(property) does the same. Prefer it to RemoveBinding: MAUI
keeps the last value a removed binding set, for any binding, and ClearValue does not take it back.
Portrait and landscape
Rotation needs nothing special: it changes the window's size, so every relative size recomputes.
Axis=Shorter or Longer gives a size rotation does not change at all.
For a different percentage per orientation, name one or both:
<Image WidthRequest="{me:Relative Portrait=90, Landscape=45}" />
<Image WidthRequest="{me:Relative 50, Landscape=25}" />
Orientation is the shape of the element's window — landscape when it is wider than tall,
portrait otherwise, square included — whatever the size is measured against. A card is wider than
it is tall on a portrait phone too, so the reference's own shape would say "landscape" almost
everywhere. On a phone the window's shape is the screen's orientation; on a desktop, a tall narrow
window counts as portrait. Until the element is in a window, Percent applies.
With OnIdiom and OnPlatform
Put them inside, as a named argument:
<Image WidthRequest="{me:Relative Percent={OnIdiom Phone=80, Tablet=50, Desktop=30}}" />
Two other spellings look reasonable and do not work, both because of how MAUI compiles XAML:
{me:Relative {OnIdiom ...}}— as the unnamed first argument,OnIdiomis not told what type it is producing and throws "Cannot determine property to provide the value for". Naming itPercent=fixes that.{OnIdiom Desktop={me:Relative 30}, Default=100}— nested the other way,{me:Relative}cannot see the element it is sizing, and the compiled XAML applies the result withSetValue, which cannot carry a binding. This throws a message saying which way round to write it.
What it measures
- Parent is the parent's size less its padding — the space the element is actually laid
out in, as a CSS percentage refers to the content box.
SourceandAncestorTypeexclude padding the same way. ABorder's stroke is not subtracted. - Window is the window the element is in, and follows it as it is resized.
- Display is the display the window is on, in device-independent units, and changes on rotation. On Windows it is the monitor the window is actually on, followed as the window is dragged between monitors — 5% of the width read 128 on a 3840-pixel landscape monitor and 72 on a 2160-pixel portrait one beside it. Elsewhere it is the main display: a phone has one, and Mac Catalyst offers no per-window screen to ask.
Both are measured in the units pages are laid out in. That matters on Mac Catalyst, where an
app with the iPad idiom — MAUI's template default — is shown scaled to 77%: layout uses iPad points,
1.3 times as many as the Mac points the display is reported in, and MAUI's own Window.Width is
reported in one or the other depending on whether the window was just restored or has been resized.
So on iOS and Mac Catalyst the window is measured from the native window's bounds, which are always
in layout units, and the display is converted with Apple's 0.77.
Until the reference has a size — before the first layout, or before the element is in a window — the property keeps its default. If the element moves to another parent or window, it follows. Setting the property directly replaces the relative size, as it would any binding.
Build-time checks
The package brings an analyzer that reads the app's XAML during the build and warns, at the line, about what would otherwise fail only when the page loads:
| ID | What |
|---|---|
MTKS001 |
{me:Relative} in a Style setter — use me:RelativeSizing |
MTKS002 |
a markup extension as the unnamed argument, {me:Relative {OnIdiom ...}} — it throws at startup; name it Percent= |
MTKS003 |
{me:Relative} nested inside another markup extension |
MTKS004 |
To=Self measuring the dimension it sets |
MTKS005 |
an invalid option: a malformed Breakpoints, Sides on a property that is not a margin or padding, SafeArea without To=Window, conflicting options, bad me:RelativeSizing text |
All are warnings, so a false positive never breaks a build that does not ask for
-warnaserror. An unknown option name inside {me:Relative} is already a build error from MAUI's
own XAML compiler.
Things to know
{me:Relative}is not for aStylesetter — useRelativeSizing, above. It throws saying so.- Watch for layout loops. A percentage of a parent that sizes itself to its content —
Autorows, aVerticalStackLayoutin its stacking direction — makes the child depend on the parent and the parent on the child. Size against something with a size of its own: a*row, the window, or a named element. - No leaks through the display. The display's change event is static; an element subscribes only while it is in a window and unsubscribes when it leaves.
- Trim-safe. The binding is a
TypedBinding, not a string path, so iOS and Android Release builds do not trim away what it reads. - Accessibility text size multiplies a relative font. MAUI scales
FontSizeby the user's system text-size setting (FontAutoScalingEnabled, on by default), after this library has set it. SoMinandMaxbound theFontSizeproperty, not the rendered text: withMax=40and the text size at 130%, text renders at 52. That is usually right — the user asked for bigger text. To cap what renders, setFontAutoScalingEnabled="False", and then honour the setting some other way. This is how MAUI documents the setting; it has not been measured on a device here.
Where it has run
Windows, resized and dragged between two monitors; an Android 16 phone, rotated; Mac Catalyst on
macOS 15, resized; an iPhone XR on iOS 18, rotated — every card of the repository's demo checked
against its expected value, in both orientations, including Breakpoints, rounding, SafeArea —
against the Android status and navigation bars and the iPhone's notch and home indicator — and a
relative, rounded margin.
Documentation
Full documentation is in the wiki — Design Notes for why each library is shaped the way it is, Building and Testing for the multi-targeting rules, and Publishing for how these packages are released.
Source: github.com/melihercan/Me.Toolkit.Maui
Licence
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-android36.0 is compatible. net10.0-browser was computed. net10.0-ios was computed. net10.0-ios26.0 is compatible. net10.0-maccatalyst was computed. net10.0-maccatalyst26.0 is compatible. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net10.0-windows10.0.19041 is compatible. |
-
net10.0
- Microsoft.Maui.Controls (>= 10.0.101)
-
net10.0-android36.0
- Microsoft.Maui.Controls (>= 10.0.101)
-
net10.0-ios26.0
- Microsoft.Maui.Controls (>= 10.0.101)
-
net10.0-maccatalyst26.0
- Microsoft.Maui.Controls (>= 10.0.101)
-
net10.0-windows10.0.19041
- Microsoft.Maui.Controls (>= 10.0.101)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
26.9.28 fixes Mac Catalyst and adds to what a relative size can do.
Fixed: on Mac Catalyst with the iPad idiom - MAUI's default - window- and display-relative sizes were wrong: 23% small against the display, and against the window 30% large at launch and 23% small after a resize, because MAUI reports the window in different units at those two times. The window is now measured from the native UIWindow, and the display converted with Apple's 0.77.
Changed: sizes are whole numbers by default (Round=Units). Round=Pixels rounds to whole physical pixels of the window's display; Round=None leaves the value as calculated.
Added: Breakpoints='600:50 1200:33' choose the percentage by window width, like CSS media queries, or by the reference's own width with BreakpointsBy=Reference, like container queries. Relative Margin and Padding, on every side or the Sides chosen. SafeArea=True with To=Window leaves out the notch, cutout and system bars. On Windows, To=Display measures the monitor the window is on. RelativeSizing attached properties let a Style carry a relative size, and ClearRelativeSize removes one.
Added: build-time checks. An analyzer in the package reads the app's XAML and warns, at the line, about MTKS001-005: {me:Relative} in a Style setter, a markup extension as its unnamed argument - which throws at startup - nesting inside another extension, To=Self measuring the dimension it sets, and invalid options.
Verified on Windows, an Android 16 phone, Mac Catalyst on macOS 15 and an iPhone XR on iOS 18.
26.9.27 stamps the net10.0 assembly with the package version. 26.9.26 shipped it as 1.0.0.0 - the publish workflow's test step rebuilt that slice without the version before packing - while the Android, iOS, Mac Catalyst and Windows assemblies were right. Nothing else changed.
First release, 26.9.26.
{me:Relative} and SetRelativeSize size a double property as a percentage of the parent (less its padding), the window, the main display, an element named with x:Reference, or the nearest ancestor of a type. Width, Height, Shorter and Longer pick the dimension; Min and Max clamp the result; Portrait and Landscape give a different percentage for each orientation of the window. To=Self keeps an aspect ratio, Offset gives calc(50% - 8), and RelativeSizing's attached properties let a Style carry a relative size. The value is recomputed whenever the reference changes size or the element moves in the tree.