Skip to content

Commit 502958f

Browse files
Improving docs for responsive layouts
1 parent 43ebbdb commit 502958f

File tree

6 files changed

+392
-3
lines changed

6 files changed

+392
-3
lines changed

docs/layout/layout.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,5 @@ For details and code examples, see [Overlay Layers](/docs/fundamentals/visual-an
7979
## See also
8080

8181
- [Positioning Controls](/docs/layout/positioning-controls): Alignment, margins, and positioning.
82+
- [Responsive Layouts](/docs/layout/responsive-layouts): Adapting layout to different sizes using container queries and reflowing panels.
8283
- [Overlay Layers](/docs/fundamentals/visual-and-logical-trees#overlay-layers): Adding custom overlay content above normal controls.

docs/layout/responsive-layouts.md

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
---
2+
id: responsive-layouts
3+
title: Responsive layouts
4+
description: Create layouts that adapt to different sizes using container queries, form-factor extensions, and reflowing panels.
5+
doc-type: explanation
6+
---
7+
8+
Avalonia provides techniques for building layouts that adapt when the available space changes. You can respond to the size of a container, the type of device, or the dimensions of a reflowing panel. This page explains each approach and when to choose it.
9+
10+
## Approaches at a glance
11+
12+
| Technique | Responds to | Resolves | Best for |
13+
|-----------|-------------|----------|----------|
14+
| [Container queries](#container-queries) | Size of an ancestor control | Live, as the control resizes | Reusable components that appear in panels of varying width |
15+
| [`OnFormFactor`](#onformfactor) | Device type (desktop, mobile) | Once, at startup | Platform-specific layout differences |
16+
| [Reflowing panels](#reflowing-panels) | Available width | Live, as the panel resizes | Card grids and flowing content |
17+
| [Breakpoint view models](#breakpoint-view-models) | Window width (or any measured value) | Live, via property change | Complex multi-property transitions driven by code |
18+
19+
## Container queries
20+
21+
Container queries let you activate styles when an ancestor control reaches a specific size. Because the query targets a control rather than the window, the same component adapts correctly whether it appears in a full-width page, a narrow sidebar, or a dialog.
22+
23+
### Declaring a container
24+
25+
Mark any ancestor as a container by setting the `Container.Name` and `Container.Sizing` attached properties:
26+
27+
```xml
28+
<Border Container.Name="main"
29+
Container.Sizing="Width">
30+
<!-- child content here -->
31+
</Border>
32+
```
33+
34+
`Container.Sizing` determines which dimensions are tracked:
35+
36+
| Value | Tracked dimensions |
37+
|-------|--------------------|
38+
| `Normal` | None (default) |
39+
| `Width` | Width only |
40+
| `Height` | Height only |
41+
| `WidthAndHeight` | Both width and height |
42+
43+
### Writing a container query
44+
45+
A `ContainerQuery` element lives inside the `Styles` collection of a control that is an ancestor of the container. It activates its child styles when the query condition is met:
46+
47+
```xml
48+
<Window>
49+
<Window.Styles>
50+
<ContainerQuery Name="main" Query="max-width:600">
51+
<Style Selector="StackPanel#sidebar">
52+
<Setter Property="IsVisible" Value="False" />
53+
</Style>
54+
</ContainerQuery>
55+
</Window.Styles>
56+
57+
<Grid ColumnDefinitions="200,*">
58+
<StackPanel x:Name="sidebar" Grid.Column="0">
59+
<!-- sidebar content -->
60+
</StackPanel>
61+
<ContentControl Grid.Column="1"
62+
Content="{Binding CurrentPage}" />
63+
</Grid>
64+
</Window>
65+
```
66+
67+
In this example, the sidebar hides when the container named `main` is 600 pixels wide or narrower.
68+
69+
### Adjusting layout structure with breakpoints
70+
71+
You can use multiple container queries on the same container to define breakpoint tiers. The following example changes the number of columns in a `UniformGrid` as the container grows:
72+
73+
```xml
74+
<Panel Container.Name="content" Container.Sizing="Width">
75+
<Panel.Styles>
76+
<ContainerQuery Name="content" Query="max-width:400">
77+
<Style Selector="UniformGrid#cards">
78+
<Setter Property="Columns" Value="1" />
79+
</Style>
80+
</ContainerQuery>
81+
<ContainerQuery Name="content" Query="min-width:400">
82+
<Style Selector="UniformGrid#cards">
83+
<Setter Property="Columns" Value="2" />
84+
</Style>
85+
</ContainerQuery>
86+
<ContainerQuery Name="content" Query="min-width:800">
87+
<Style Selector="UniformGrid#cards">
88+
<Setter Property="Columns" Value="3" />
89+
</Style>
90+
</ContainerQuery>
91+
</Panel.Styles>
92+
93+
<UniformGrid x:Name="cards">
94+
<!-- card items -->
95+
</UniformGrid>
96+
</Panel>
97+
```
98+
99+
### Customising non-layout properties
100+
101+
Container queries are not limited to layout properties. You can adjust any property that a `Style` can set, including font sizes, spacing, visibility, and colours:
102+
103+
```xml
104+
<Panel Container.Name="content" Container.Sizing="Width">
105+
<Panel.Styles>
106+
<!-- Default heading size -->
107+
<Style Selector="TextBlock.heading">
108+
<Setter Property="FontSize" Value="24" />
109+
</Style>
110+
111+
<!-- Smaller heading when the container is narrow -->
112+
<ContainerQuery Name="content" Query="max-width:500">
113+
<Style Selector="TextBlock.heading">
114+
<Setter Property="FontSize" Value="18" />
115+
</Style>
116+
<Style Selector="StackPanel.toolbar">
117+
<Setter Property="Orientation" Value="Vertical" />
118+
</Style>
119+
</ContainerQuery>
120+
</Panel.Styles>
121+
122+
<StackPanel>
123+
<TextBlock Classes="heading" Text="Dashboard" />
124+
<StackPanel Classes="toolbar" Orientation="Horizontal" Spacing="8">
125+
<Button Content="New" />
126+
<Button Content="Refresh" />
127+
</StackPanel>
128+
</StackPanel>
129+
</Panel>
130+
```
131+
132+
### Combining queries
133+
134+
Combine multiple conditions in a single query using `and` (all conditions must match) or `,` (any condition can match):
135+
136+
```xml
137+
<!-- Both conditions must be true -->
138+
<ContainerQuery Name="main" Query="min-width:400 and max-width:800">
139+
<!-- styles for medium widths -->
140+
</ContainerQuery>
141+
142+
<!-- Either condition can be true -->
143+
<ContainerQuery Name="main" Query="max-width:300,min-height:600">
144+
<!-- styles for narrow OR tall containers -->
145+
</ContainerQuery>
146+
```
147+
148+
For the full query syntax, available query types, and restrictions, see [Container queries](/docs/styling/container-queries).
149+
150+
:::tip
151+
When the `TopLevel` (your window or main view) is set as a container, container queries behave like CSS media queries, responding to the window size itself.
152+
:::
153+
154+
## OnFormFactor
155+
156+
The `OnFormFactor` markup extension selects a value based on the device type. It resolves once at startup, so it does not respond to window resizing at runtime:
157+
158+
```xml
159+
<Grid ColumnDefinitions="{OnFormFactor Desktop='250,*', Mobile='*'}">
160+
<Border Grid.Column="0"
161+
IsVisible="{OnFormFactor Desktop=True, Mobile=False}">
162+
<ListBox ItemsSource="{Binding MenuItems}" />
163+
</Border>
164+
<ContentControl Grid.Column="{OnFormFactor Desktop=1, Mobile=0}"
165+
Content="{Binding CurrentPage}" />
166+
</Grid>
167+
```
168+
169+
Use `OnFormFactor` when your desktop and mobile layouts are structurally different and you do not need to respond to live resizing. For layouts that must adapt as the user resizes the window, use container queries instead.
170+
171+
## Reflowing panels
172+
173+
Some panels automatically reflow their children based on available space without requiring queries or code.
174+
175+
**`WrapPanel`** arranges children in a row and wraps to the next line when the edge of the panel is reached:
176+
177+
```xml
178+
<WrapPanel Orientation="Horizontal">
179+
<Button Content="One" Margin="4" />
180+
<Button Content="Two" Margin="4" />
181+
<Button Content="Three" Margin="4" />
182+
<!-- wraps to the next row when the panel is too narrow -->
183+
</WrapPanel>
184+
```
185+
186+
**`UniformGridLayout`** (used with `ItemsRepeater`) calculates column count from the available width and a minimum item size:
187+
188+
```xml
189+
<ItemsRepeater ItemsSource="{Binding Cards}">
190+
<ItemsRepeater.Layout>
191+
<UniformGridLayout MinItemWidth="280"
192+
MinItemHeight="200"
193+
MinColumnSpacing="12"
194+
MinRowSpacing="12" />
195+
</ItemsRepeater.Layout>
196+
<ItemsRepeater.ItemTemplate>
197+
<DataTemplate>
198+
<Border Padding="16" CornerRadius="8"
199+
Background="White"
200+
BorderBrush="#E5E7EB" BorderThickness="1">
201+
<TextBlock Text="{Binding Title}" />
202+
</Border>
203+
</DataTemplate>
204+
</ItemsRepeater.ItemTemplate>
205+
</ItemsRepeater>
206+
```
207+
208+
These panels are a good choice when you need flowing content without explicit breakpoints.
209+
210+
## Breakpoint view models
211+
212+
When your responsive logic involves multiple coordinated property changes or conditions beyond size (such as combining orientation and platform checks), you can observe the window width in your view model and expose boolean properties for each tier:
213+
214+
```csharp
215+
public partial class MainViewModel : ObservableObject
216+
{
217+
[ObservableProperty]
218+
private bool _isCompact;
219+
220+
[ObservableProperty]
221+
private bool _isWide;
222+
223+
public void UpdateLayout(double windowWidth)
224+
{
225+
IsCompact = windowWidth < 640;
226+
IsWide = windowWidth >= 1024;
227+
}
228+
}
229+
```
230+
231+
Call `UpdateLayout` from the window's size-changed handler:
232+
233+
```csharp
234+
protected override void OnSizeChanged(SizeChangedEventArgs e)
235+
{
236+
base.OnSizeChanged(e);
237+
if (DataContext is MainViewModel vm)
238+
vm.UpdateLayout(e.NewSize.Width);
239+
}
240+
```
241+
242+
Then bind layout properties to the breakpoint flags:
243+
244+
```xml
245+
<StackPanel IsVisible="{Binding IsCompact}" Spacing="8">
246+
<views:SidebarView />
247+
<views:ContentView />
248+
</StackPanel>
249+
250+
<Grid IsVisible="{Binding !IsCompact}" ColumnDefinitions="280,*">
251+
<views:SidebarView Grid.Column="0" />
252+
<views:ContentView Grid.Column="1" />
253+
</Grid>
254+
```
255+
256+
This approach provides full programmatic control but requires code-behind or view model wiring. Prefer container queries when your transitions are purely size-based and can be expressed in XAML.
257+
258+
## Choosing an approach
259+
260+
Use the following decision process to select the right technique:
261+
262+
1. **Does your component need to adapt based on its own size (not the window)?** Use container queries. This keeps the component self-contained and reusable.
263+
2. **Are desktop and mobile layouts structurally different, with no need for live resizing?** Use `OnFormFactor`.
264+
3. **Do you have a collection of items that should reflow into rows?** Use `WrapPanel` or `UniformGridLayout`.
265+
4. **Does your transition logic involve multiple conditions, platform checks, or non-size triggers?** Use breakpoint view models.
266+
267+
You can combine these techniques. For example, use `OnFormFactor` for a top-level structural difference (sidebar vs. bottom tabs), then use container queries within individual panels so they adapt to their actual rendered size.
268+
269+
## See also
270+
271+
- [Container queries](/docs/styling/container-queries): Full query syntax, container sizing modes, and restrictions.
272+
- [How to: Build responsive layouts](/docs/how-to/responsive-layout-how-to): Step-by-step recipes for common responsive patterns.
273+
- [Layout](/docs/layout/layout): How the Avalonia measure and arrange system works.
274+
- [Choosing a layout panel](/docs/layout/choosing-a-layout-panel): Picking the right panel for your scenario.

docs/migration/winui/index.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,67 @@ WinUI uses `VisualStateManager` with visual states and storyboards to handle con
4242

4343
Avalonia's approach is more concise and composable. For a full guide on how styling works, see [Styles](/docs/styling/styles).
4444

45+
#### AdaptiveTrigger to container queries
46+
47+
WinUI uses `AdaptiveTrigger` inside `VisualStateManager` to adapt layout based on window size. Avalonia replaces this with container queries, which can respond to the size of any ancestor control (not only the window).
48+
49+
**WinUI (AdaptiveTrigger):**
50+
51+
```xml
52+
<VisualStateManager.VisualStateGroups>
53+
<VisualStateGroup>
54+
<VisualState x:Name="Narrow">
55+
<VisualState.StateTriggers>
56+
<AdaptiveTrigger MinWindowWidth="0" />
57+
</VisualState.StateTriggers>
58+
<VisualState.Setters>
59+
<Setter Target="ContentGrid.Columns" Value="1" />
60+
</VisualState.Setters>
61+
</VisualState>
62+
<VisualState x:Name="Wide">
63+
<VisualState.StateTriggers>
64+
<AdaptiveTrigger MinWindowWidth="800" />
65+
</VisualState.StateTriggers>
66+
<VisualState.Setters>
67+
<Setter Target="ContentGrid.Columns" Value="3" />
68+
</VisualState.Setters>
69+
</VisualState>
70+
</VisualStateGroup>
71+
</VisualStateManager.VisualStateGroups>
72+
```
73+
74+
**Avalonia (container queries):**
75+
76+
```xml
77+
<Panel Container.Name="root" Container.Sizing="Width">
78+
<Panel.Styles>
79+
<ContainerQuery Name="root" Query="max-width:800">
80+
<Style Selector="UniformGrid#ContentGrid">
81+
<Setter Property="Columns" Value="1" />
82+
</Style>
83+
</ContainerQuery>
84+
<ContainerQuery Name="root" Query="min-width:800">
85+
<Style Selector="UniformGrid#ContentGrid">
86+
<Setter Property="Columns" Value="3" />
87+
</Style>
88+
</ContainerQuery>
89+
</Panel.Styles>
90+
91+
<UniformGrid x:Name="ContentGrid">
92+
<!-- content -->
93+
</UniformGrid>
94+
</Panel>
95+
```
96+
97+
Container queries offer two advantages over WinUI's `AdaptiveTrigger`:
98+
99+
- **Component-level responsiveness.** `AdaptiveTrigger` always measures the window. Container queries measure any ancestor, so a component adapts correctly whether it appears full-width, in a sidebar, or in a dialog.
100+
- **No visual state boilerplate.** You define the query and the style in one place without declaring state groups, state names, or trigger objects.
101+
102+
You can also combine width and height conditions in a single query using `and` or `,` operators, and target any styleable property (font size, spacing, visibility, colours) alongside layout properties.
103+
104+
For the full query syntax, see [Container queries](/docs/styling/container-queries). For guidance on choosing between container queries, `OnFormFactor`, reflowing panels, and code-driven breakpoints, see [Responsive layouts](/docs/layout/responsive-layouts).
105+
45106
### XAML namespace
46107

47108
| WinUI / UWP | Avalonia |
@@ -163,5 +224,7 @@ Moving from WinUI to Avalonia is not only about cross-platform. There are a few
163224

164225
- [Get Started with Avalonia](/docs/get-started/first-app): Create your first Avalonia application.
165226
- [Styles](/docs/styling/styles): How Avalonia's CSS-like styling works.
227+
- [Container queries](/docs/styling/container-queries): Size-based styling, replacing WinUI's AdaptiveTrigger.
228+
- [Responsive layouts](/docs/layout/responsive-layouts): Building adaptive layouts with container queries and reflowing panels.
166229
- [Data Binding Syntax](/docs/data-binding/data-binding-syntax): Avalonia binding syntax reference.
167230
- [Controls Reference](/controls): Full Avalonia controls documentation.

0 commit comments

Comments
 (0)