Skip to main content
Avalonia’s layout system is a two-pass process that determines the size and position of every element in the visual tree. Understanding this system is crucial for creating responsive, well-behaved UIs.

Layout Process Overview

The layout system executes in two phases:
┌────────────────────────────────┐
│      1. MEASURE PASS             │
│  (Top-down: size constraints)   │
│  (Bottom-up: desired sizes)     │
└────────────────────────────────┘


┌────────────────────────────────┐
│      2. ARRANGE PASS             │
│  (Top-down: final positions)    │
│  (Children positioned)          │
└────────────────────────────────┘

Measure Pass

Determines how much space each element needs:
  • Parent passes available size to children
  • Children calculate their desired size
  • Results stored in DesiredSize property

Arrange Pass

Positions elements:
  • Parent allocates final space to children
  • Children position themselves within allocated space
  • Final bounds stored in Bounds property

Layoutable Class

The Layoutable base class implements layout functionality:
Layoutable Properties (from source)
using Avalonia.Layout;

public class Layoutable : Visual
{
    // Size constraints
    public double Width { get; set; }              // Explicit width
    public double Height { get; set; }             // Explicit height
    public double MinWidth { get; set; }           // Minimum width
    public double MaxWidth { get; set; }           // Maximum width  
    public double MinHeight { get; set; }          // Minimum height
    public double MaxHeight { get; set; }          // Maximum height
    
    // Alignment
    public HorizontalAlignment HorizontalAlignment { get; set; }
    public VerticalAlignment VerticalAlignment { get; set; }
    
    // Spacing
    public Thickness Margin { get; set; }          // External spacing
    
    // Layout result
    public Size DesiredSize { get; }               // Calculated desired size
    
    // Layout rounding
    public bool UseLayoutRounding { get; set; }    // Pixel-perfect rendering
}

Size Properties

<!-- Explicit size -->
<Button Width="100" Height="40" Content="Fixed Size" />

<!-- NaN means auto-size -->
<Button Width="NaN" Height="NaN" Content="Auto Size" />

<!-- In code -->
button.Width = 100;
button.Height = double.NaN; // Auto height

Alignment

Controls how elements position within their allocated space:
<StackPanel>
    <Button HorizontalAlignment="Left" 
            Content="Left" />
    
    <Button HorizontalAlignment="Center" 
            Content="Center" />
    
    <Button HorizontalAlignment="Right" 
            Content="Right" />
    
    <Button HorizontalAlignment="Stretch" 
            Content="Stretch" />
</StackPanel>
Alignment Enums (from source)
public enum HorizontalAlignment
{
    Stretch,  // Fill available width (default)
    Left,     // Align to left edge
    Center,   // Center horizontally
    Right     // Align to right edge
}

public enum VerticalAlignment
{
    Stretch,  // Fill available height (default)
    Top,      // Align to top edge
    Center,   // Center vertically
    Bottom    // Align to bottom edge
}

Margin

Creates space around an element:
Margin Examples
<!-- Uniform margin -->
<Button Margin="10" Content="All sides" />

<!-- Horizontal, Vertical -->
<Button Margin="20,10" Content="H: 20, V: 10" />

<!-- Left, Top, Right, Bottom -->
<Button Margin="10,5,10,5" Content="Specific sides" />

<!-- Named properties -->
<Button Content="Individual">
    <Button.Margin>
        <Thickness Left="10" Top="5" Right="10" Bottom="15" />
    </Button.Margin>
</Button>
Margin creates space outside the element, while Padding (on controls like Border) creates space inside.

Layout Panels

Panels are containers that implement specific layout strategies:

Panel Base Class

Panel.cs (Simplified from source)
using Avalonia.Controls;

public class Panel : Control
{
    // Children collection
    public Controls Children { get; }
    
    // Background brush
    public IBrush? Background { get; set; }
    
    // Override to implement custom layout
    protected override Size MeasureOverride(Size availableSize)
    {
        // Measure children
        // Return desired size
    }
    
    protected override Size ArrangeOverride(Size finalSize)
    {
        // Position children
        // Return used size
    }
}

StackPanel

Arranges children in a vertical or horizontal stack:
<StackPanel Spacing="10">
    <TextBlock Text="Item 1" />
    <TextBlock Text="Item 2" />
    <TextBlock Text="Item 3" />
</StackPanel>
Characteristics:
  • Children sized to content in stack direction
  • Children stretched in perpendicular direction (unless alignment set)
  • No wrapping

Grid

Arranges children in rows and columns:
Grid Layout
<Grid RowDefinitions="Auto,*,Auto" 
      ColumnDefinitions="200,*">
    
    <!-- Header spans both columns -->
    <TextBlock Grid.Row="0" 
               Grid.Column="0" 
               Grid.ColumnSpan="2"
               Text="Header" />
    
    <!-- Sidebar -->
    <Border Grid.Row="1" Grid.Column="0" 
            Background="LightGray">
        <TextBlock Text="Sidebar" />
    </Border>
    
    <!-- Main content -->
    <ScrollViewer Grid.Row="1" Grid.Column="1">
        <TextBlock Text="Content area" />
    </ScrollViewer>
    
    <!-- Footer spans both columns -->
    <TextBlock Grid.Row="2" 
               Grid.Column="0"
               Grid.ColumnSpan="2"
               Text="Footer" />
</Grid>
Grid Sizing:
Sizes to content
<Grid RowDefinitions="Auto,Auto">
    <TextBlock Grid.Row="0" Text="Sized to content" />
    <Button Grid.Row="1" Content="Also sized to content" />
</Grid>
Proportional sizing
<Grid ColumnDefinitions="*,2*,*">
    <!-- Ratio: 1:2:1 -->
    <Border Grid.Column="0" />  <!-- 25% -->
    <Border Grid.Column="1" />  <!-- 50% -->
    <Border Grid.Column="2" />  <!-- 25% -->
</Grid>
Fixed pixel size
<Grid ColumnDefinitions="200,*">
    <Border Grid.Column="0" />  <!-- 200px -->
    <Border Grid.Column="1" />  <!-- Remaining space -->
</Grid>

DockPanel

Docks children to edges:
DockPanel
<DockPanel LastChildFill="True">
    <!-- Dock to edges -->
    <Menu DockPanel.Dock="Top" />
    <StatusBar DockPanel.Dock="Bottom" />
    <TreeView DockPanel.Dock="Left" Width="200" />
    
    <!-- Last child fills remaining space -->
    <ContentControl Content="{Binding MainContent}" />
</DockPanel>
Dock Values: Left, Top, Right, Bottom

Canvas

Absolute positioning:
Canvas Layout
<Canvas Width="400" Height="300">
    <Rectangle Canvas.Left="50" 
               Canvas.Top="50"
               Width="100" 
               Height="100"
               Fill="Blue" />
    
    <Ellipse Canvas.Left="200" 
             Canvas.Top="100"
             Width="80" 
             Height="80"
             Fill="Red" />
</Canvas>
Canvas doesn’t adapt to different screen sizes. Use sparingly for custom graphics or fixed layouts.

WrapPanel

Wraps children to next line when space runs out:
WrapPanel
<WrapPanel Orientation="Horizontal">
    <Button Content="Button 1" />
    <Button Content="Button 2" />
    <Button Content="Button 3" />
    <Button Content="Button 4" />
    <Button Content="Button 5" />
    <!-- Wraps to next line when needed -->
</WrapPanel>

UniformGrid

Equal-sized cells:
UniformGrid
<UniformGrid Rows="2" Columns="3">
    <Button Content="1" />
    <Button Content="2" />
    <Button Content="3" />
    <Button Content="4" />
    <Button Content="5" />
    <Button Content="6" />
</UniformGrid>

Layout Invalidation

Avalonia automatically re-layouts when properties change:
Layout Invalidation
// These properties trigger layout updates:
button.Width = 200;           // Invalidates measure
button.Margin = new Thickness(10);  // Invalidates measure
stackPanel.Children.Add(newChild);  // Invalidates measure

// Manual invalidation (rarely needed)
control.InvalidateMeasure();
control.InvalidateArrange();

Performance Considerations

1

Minimize layout passes

Batch property changes to avoid multiple layout updates.
2

Use virtualization

For long lists, use VirtualizingStackPanel or VirtualizingPanel.
3

Avoid nested measure-dependent layouts

Deep nesting of Auto-sized elements can be slow.
4

Use layout rounding sparingly

UseLayoutRounding="True" has a performance cost.

Custom Layout

Create custom panels by overriding measure and arrange:
Custom Panel
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;

public class CircularPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        // Measure all children
        foreach (var child in Children)
        {
            child.Measure(availableSize);
        }
        
        // Return desired size
        return new Size(200, 200);
    }
    
    protected override Size ArrangeOverride(Size finalSize)
    {
        var count = Children.Count;
        if (count == 0) return finalSize;
        
        var radius = Math.Min(finalSize.Width, finalSize.Height) / 2 - 20;
        var center = new Point(finalSize.Width / 2, finalSize.Height / 2);
        var angleStep = 2 * Math.PI / count;
        
        // Arrange children in circle
        for (int i = 0; i < count; i++)
        {
            var child = Children[i];
            var angle = i * angleStep;
            
            var x = center.X + radius * Math.Cos(angle) - child.DesiredSize.Width / 2;
            var y = center.Y + radius * Math.Sin(angle) - child.DesiredSize.Height / 2;
            
            child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
        }
        
        return finalSize;
    }
}

Layout Debugging

Enable visual layout debugging:
Enable Layout Debugging
// In your Window constructor
public MainWindow()
{
    InitializeComponent();
    
#if DEBUG
    this.AttachDevTools();
#endif
}
Press F12 to open DevTools and inspect layout bounds.

Best Practices

1

Use appropriate panels

Choose the right panel for your layout needs. Grid for complex layouts, StackPanel for simple stacks.
2

Avoid explicit sizes

Let controls size to content when possible. Use Min/Max constraints instead of fixed sizes.
3

Leverage alignment

Use HorizontalAlignment and VerticalAlignment instead of margins for positioning.
4

Use Grid star sizing

For responsive layouts, prefer star sizing (*) over absolute sizes.
5

Consider layout performance

Deep nesting and complex layouts can impact performance. Profile and optimize as needed.

Build docs developers (and LLMs) love