Lay out content with CSS Flexbox rules (direction, wrapping, grow/shrink distribution, gaps, per-item alignment)
UnoFeatures: Toolkit (add to <UnoFeatures> in your .csproj)
Add FlexPanel to a page
Why: Use a single container that follows the CSS Flexbox specification rather than an approximation of it.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI">
<utu:FlexPanel>
<!-- children -->
</utu:FlexPanel>
</Page>
- A bare
<utu:FlexPanel>is a single-line row:Directiondefaults toRowandWraptoNoWrap. - Layout is computed by a vendored port of Meta's Yoga engine, so results match CSS.
Tip
If you are reproducing a Figma auto-layout frame rather than a CSS layout, use AutoLayout instead. See FlexPanel vs AutoLayout for the full comparison.
Size a child with Basis, not Width
Outcome: The child is arranged from the flex resolution, not from its Width.
<utu:FlexPanel Width="400">
<!-- Arranges 400 wide, not 200. -->
<Border Width="200"
utu:FlexPanel.Basis="100"
utu:FlexPanel.Grow="1" />
</utu:FlexPanel>
- This is the most common surprise when coming from WinUI. On the main axis,
Basisis the flex base size andWidthis not. Growresolves the final slot;Widthonly seeds the measure.Basisaccepts points only — percentages are not supported.
Make columns exactly equal width
Outcome: Every column is the same width regardless of its content.
<utu:FlexPanel Width="300" ColumnGap="8">
<Border utu:FlexPanel.Grow="1" utu:FlexPanel.Basis="0" />
<Border utu:FlexPanel.Grow="1" utu:FlexPanel.Basis="0" />
</utu:FlexPanel>
Grow="1"alone distributes only the leftover space, so items with different content stay different sizes.- Equal columns are the CSS
flex: 1 1 0shorthand — you need bothGrowandBasis="0". SettingBasis="0"takes the content size out of the equation entirely.
Split leftover space by ratio
Outcome: Two children share the free space 1:2.
<utu:FlexPanel Width="300">
<!-- 100 wide -->
<Border utu:FlexPanel.Grow="1" utu:FlexPanel.Basis="0" />
<!-- 200 wide -->
<Border utu:FlexPanel.Grow="2" utu:FlexPanel.Basis="0" />
</utu:FlexPanel>
Growis a ratio, not a length in pixels.Shrinkdistributes a deficit by the same ratio rule: a child withShrink="1"beside a child withShrink="0"absorbs the whole overflow. Nothing stops a child at its content width — see Keep a child from shrinking too far.
Stop a child from shrinking
Outcome: The children keep their natural size and the row overflows instead of compressing.
<utu:FlexPanel Width="200">
<Border Width="150" utu:FlexPanel.Shrink="0" />
<Border Width="150" utu:FlexPanel.Shrink="0" />
</utu:FlexPanel>
Shrinkdefaults to1, so children compress to fit by default.Shrink="0"opts out.- With every child at
Shrink="0", flexbox overflows rather than fitting the content to the panel. This is a deliberate difference fromAutoLayout, which would fit them.
Keep a child from shrinking too far
Outcome: A child compresses with its siblings, but never below a floor you choose.
<utu:FlexPanel Width="100">
<!-- Arranges 80 wide; the sibling takes the remaining 20. -->
<Border Width="100" utu:FlexPanel.FlexMinWidth="80" />
<Border Width="100" />
</utu:FlexPanel>
- By default a child can shrink all the way to
0, as in Yoga and React Native. Unlike CSS (Flexbox 4.5), there is no automatic min-content floor, because WinUI cannot report an element's min-content size. FlexMinWidthclamps the width andFlexMinHeightthe height, whatever theDirection.- To keep a child at its natural size instead, use
Shrink="0".
Note
FlexMinWidth / FlexMinHeight are not FrameworkElement.MinWidth / MinHeight. The framework properties force Measure to return at least X; these clamp the flex-resolved slot to at least X.
Wrap onto multiple lines
Outcome: Children that do not fit start a new line.
<utu:FlexPanel Width="250"
Wrap="Wrap"
RowGap="20"
AlignContent="FlexStart">
<Border Width="100" Height="40" />
<Border Width="100" Height="40" />
<!-- Wraps to a second line, 40 + 20 below the first. -->
<Border Width="100" Height="40" />
</utu:FlexPanel>
Wrapis the capabilityAutoLayoutdoes not have at all.RowGapseparates the lines in a row layout;ColumnGapseparates them in a column layout.AlignContentpositions the lines as a group on the cross axis, and only does anything when the content actually wraps.
Switch between a row and a column
Outcome: The same children stack horizontally or vertically.
<utu:FlexPanel Direction="Column" RowGap="8">
<TextBlock Text="Title" />
<TextBlock Text="Subtitle" />
</utu:FlexPanel>
Direction:Row(default),Column,RowReverse,ColumnReverse.- Changing
Directionswaps which axis is the main axis —JustifyContentalways applies to the main axis andAlignItemsto the cross axis.
Note
Direction defaults to Row, which is not the zero value of the FlexDirection enum — the enum preserves the underlying engine numbering, in which Column = 0.
Space children with gaps, and hide one without leaving a hole
Outcome: Uniform separation that collapses cleanly when a child is hidden.
<utu:FlexPanel Width="400" ColumnGap="10">
<Border Width="50" Height="50" />
<Border Width="50" Height="50" Visibility="Collapsed" />
<!-- Starts at x=60: 50 wide plus a single 10 gap. -->
<Border Width="50" Height="50" />
</utu:FlexPanel>
Visibility="Collapsed"maps to CSSdisplay: none: the child contributes no size and no gap slot.- A negative gap is clamped to
0. Use a negative childMarginif you need items to overlap.
Distribute children along the main axis
Outcome: Items pack, center, or spread along the main axis.
<utu:FlexPanel Width="400"
ColumnGap="10"
JustifyContent="SpaceBetween">
<Border Width="50" Height="50" />
<!-- Pushed to x=350. -->
<Border Width="50" Height="50" />
</utu:FlexPanel>
JustifyContent:FlexStart(default),Center,FlexEnd,SpaceBetween,SpaceAround,SpaceEvenly.- A gap is a floor, not the final separation —
SpaceBetweendistributes whatever is left over on top of it.
Align children on the cross axis
Outcome: Items align across the axis opposite Direction, with per-item overrides.
<utu:FlexPanel Height="200" AlignItems="Center" ColumnGap="8">
<Border Width="64" Height="64" />
<Border Width="64" Height="64" utu:FlexPanel.AlignSelf="FlexEnd" />
</utu:FlexPanel>
AlignItems:Stretch(default),FlexStart,Center,FlexEnd,Baseline.AlignSelfon a child overrides the container for that child;Auto(its default) means inheritAlignItems.AlignItems="Baseline"aligns text baselines rather than boxes — with mixed font sizes, the smaller text is offset down so both sit on the same baseline.
Take a child out of the flow
Outcome: One child is positioned by insets and does not displace its siblings.
<utu:FlexPanel Width="400" Height="200">
<!-- Still arranged at x=0. -->
<Border Width="100" Height="50" />
<Border Width="60" Height="30"
utu:FlexPanel.Position="Absolute"
utu:FlexPanel.Left="200"
utu:FlexPanel.Top="100" />
</utu:FlexPanel>
Position="Absolute"removes the child from the flex line;Left,Top,RightandBottomplace it against the panel.- Flowed siblings are laid out as if the absolute child were not there.
- Insets are measured from the panel's own edges, so the panel needs a resolved size — either set
Width/Heighton it as above, or let it fill a host that has one. In an auto-sized panel the insets resolve against a box that the flowed children determine.
Mirror the layout for right-to-left
Outcome: The main axis runs right-to-left.
<utu:FlexPanel FlowDirection="RightToLeft" ColumnGap="8">
<Border Width="60" Height="40" />
<Border Width="60" Height="40" />
</utu:FlexPanel>
FlowDirectionworks as it does on any other panel: the platform mirrors the whole panel, and the engine itself always lays out left-to-right.- The mirroring is visual, so the
Left/Rightinsets of an absolute child swap sides too, asCanvas.Leftdoes.
Quick reference: key properties & attachments
- Container:
Direction(Row|Column|RowReverse|ColumnReverse),Wrap(NoWrap|Wrap|WrapReverse),JustifyContent,AlignItems,AlignContent,ColumnGap,RowGap,Padding. Right-to-left comes from the inheritedFlowDirection. - Per-child (attached):
Grow,Shrink,Basis,FlexMinWidth,FlexMinHeight,AlignSelf,Position,Left,Top,Right,Bottom. - Read directly off the child:
Margin,Width,Height,MinWidth,MaxWidth,MinHeight,MaxHeight,Visibility.
See FlexPanel for types, defaults and the CSS equivalent of each.
Notes & gotchas
BasisbeatsWidthon the main axis. If a child arranges at an unexpected size, check whetherGroworBasisis resolving the slot.Grow="1"is notflex: 1 1 0. AddBasis="0"whenever you want equal shares rather than equal leftovers.- No min-content floor. Children shrink to
0by default; setFlexMinWidth/FlexMinHeight, orShrink="0", to protect a label. FlexAlignis shared. As in CSS,AlignItems,AlignContentandAlignSelfuse one keyword set, soAutoonly means something onAlignSelfand theSpace*members only onAlignContent.- No border or corner radius.
FlexPanelderives fromPanel, which exposes onlyBackground. Wrap it in aBorderif you need one. - No CSS
order. Reorder the children instead. - Pixel snapping follows
UseLayoutRounding. Set it tofalseif you see sub-pixel drift from double-rounding on a particular target.