Overview
DataTemplates define how a data object is rendered as UI. They apply to:
ItemsControland subclasses (ListBox,TreeView) viaItemTemplateTableViewColumn.CellTemplate(12.1+) and DataGrid template columns (see Column Templates below)ContentControland subclasses viaContentTemplate- Any
Contentproperty — Avalonia walks up the template tree to find a matchingDataTemplate
Template matching is by the data object's runtime type. First matching template in scope wins.
Inline DataTemplate
<ListBox ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:PersonViewModel">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
x:DataType enables compiled bindings inside the template.
12.0+: compiled bindings are the default (
AvaloniaUseCompiledBindingsByDefaultis on in new templates), so every{Binding}needs a knownx:DataTypeor the build fails (AVLN2000). For genuinely dynamic data use{ReflectionBinding Path}. 11.x: compiled bindings are opt-in (x:CompileBindings="True"/ the MSBuild property); without it{Binding}is reflection-based. Seeavalonia-versions.
ContentControl with DataTemplate
<ContentControl Content="{Binding CurrentPage}">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="vm:HomeViewModel">
<views:HomeView/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
For multi-page navigation, app-level templates (see below) are more practical than per-ContentControl templates.
App-Level Templates (Global Auto-Selection)
In App.axaml, templates registered here apply everywhere in the application. Avalonia automatically uses the matching template when a ViewModel type appears as Content or as an item.
<Application.DataTemplates>
<DataTemplate x:DataType="vm:HomeViewModel">
<views:HomeView/>
</DataTemplate>
<DataTemplate x:DataType="vm:SettingsViewModel">
<views:SettingsView/>
</DataTemplate>
<DataTemplate x:DataType="vm:PersonViewModel">
<StackPanel>
<TextBlock Text="{Binding FullName}" FontWeight="Bold"/>
<TextBlock Text="{Binding Email}"/>
</StackPanel>
</DataTemplate>
</Application.DataTemplates>
With this setup, a ContentControl binding to a ViewModel just works:
<!-- Automatically renders the right view -->
<ContentControl Content="{Binding ActivePage}"/>
Template Scope Resolution Order
Avalonia searches for a matching DataTemplate in this order:
ContentControl.ContentTemplateorItemsControl.ItemTemplate(explicit, highest priority)Control.DataTemplateson the control itselfDataTemplatesin parent controls, walking up the logical treeApplication.DataTemplates(global fallback)
First match wins. Order within a collection matters — put more specific types before base types.
DataTemplate Type Selector (IDataTemplate)
For conditional template selection based on runtime type or property values, implement IDataTemplate:
public class AnimalTemplateSelector : IDataTemplate
{
// Set these from XAML
public IDataTemplate? DogTemplate { get; set; }
public IDataTemplate? CatTemplate { get; set; }
public Control? Build(object? param) => param switch
{
Dog d => DogTemplate?.Build(d),
Cat c => CatTemplate?.Build(c),
_ => new TextBlock { Text = $"Unknown: {param}" }
};
public bool Match(object? data) => data is Dog or Cat;
}
<Window.Resources>
<local:AnimalTemplateSelector x:Key="AnimalSelector">
<local:AnimalTemplateSelector.DogTemplate>
<DataTemplate x:DataType="vm:Dog">
<TextBlock Text="{Binding Breed}"/>
</DataTemplate>
</local:AnimalTemplateSelector.DogTemplate>
<local:AnimalTemplateSelector.CatTemplate>
<DataTemplate x:DataType="vm:Cat">
<TextBlock Text="{Binding Color}"/>
</DataTemplate>
</local:AnimalTemplateSelector.CatTemplate>
</local:AnimalTemplateSelector>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Animals}"
ItemTemplate="{StaticResource AnimalSelector}"/>
FuncDataTemplate (Code-Only)
Useful in code-behind, custom controls, or when XAML is impractical:
// Simple
var template = new FuncDataTemplate<PersonViewModel>((person, _) =>
new TextBlock { Text = person.FullName });
// With bindings
var template = new FuncDataTemplate<PersonViewModel>((person, scope) =>
{
var tb = new TextBlock();
// 12.0+: compiled binding from a lambda (no reflection)
tb.Bind(TextBlock.TextProperty,
CompiledBinding.Create((PersonViewModel p) => p.FullName));
// 11.x idiom / reflection fallback: new Binding(nameof(PersonViewModel.FullName))
// 12: new ReflectionBinding(nameof(PersonViewModel.FullName))
return tb;
});
myListBox.ItemTemplate = template;
Column Templates (TableView 12.1+, DataGrid)
<!-- TableView (12.1+) -->
<TableView ItemsSource="{Binding People}">
<TableView.Columns>
<TableViewColumn Header="Name" Binding="{Binding Name}"/>
<TableViewColumn Header="Status">
<TableViewColumn.CellTemplate>
<DataTemplate x:DataType="vm:PersonViewModel">
<TextBlock Text="{Binding Status}"/>
</DataTemplate>
</TableViewColumn.CellTemplate>
</TableViewColumn>
</TableView.Columns>
</TableView>
CellTemplate takes priority over Binding. DataGrid (Avalonia.Controls.DataGrid, maintenance-only) uses DataGridTemplateColumn.CellTemplate the same way. Column Bindings are compiled in 12: the row type is inferred from ItemsSource when possible; otherwise set it explicitly with Binding="{Binding Name, DataType={x:Type vm:PersonViewModel}}" or fall back to {ReflectionBinding Name}. See avalonia-controls/data-display.
Reusable Templates as Resources
Define once, reference many times:
<Window.Resources>
<DataTemplate x:Key="PersonCard" x:DataType="vm:PersonViewModel">
<Border BorderThickness="1" CornerRadius="4" Padding="8">
<StackPanel>
<TextBlock Text="{Binding FullName}" FontWeight="Bold"/>
<TextBlock Text="{Binding Email}" Foreground="Gray"/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<!-- Use by key -->
<ListBox ItemsSource="{Binding People}"
ItemTemplate="{StaticResource PersonCard}"/>
<ContentControl Content="{Binding SelectedPerson}"
ContentTemplate="{StaticResource PersonCard}"/>
TreeDataTemplate (Hierarchical / TreeView)
TreeDataTemplate extends DataTemplate with an ItemsSource binding for child nodes:
<TreeView ItemsSource="{Binding RootNodes}">
<TreeView.ItemTemplate>
<TreeDataTemplate x:DataType="vm:TreeNode"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Spacing="4">
<Image Source="{Binding Icon}" Width="16" Height="16"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
ViewModel:
public class TreeNode
{
public string Name { get; set; } = "";
public IImage? Icon { get; set; }
public ObservableCollection<TreeNode> Children { get; } = new();
}
Mixed types in a tree (files and folders):
public class NodeTemplateSelector : IDataTemplate
{
public Control? Build(object? param) => param switch
{
FolderNode => /* build folder template */,
FileNode => /* build file template */,
_ => null
};
public bool Match(object? data) => data is FolderNode or FileNode;
}
ItemsControl Panel Customization
Change the layout panel used to arrange items:
<!-- Wrap panel for wrapping items -->
<ItemsControl ItemsSource="{Binding Tags}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:TagViewModel">
<Border Background="#E0E0E0" CornerRadius="12" Padding="8,4">
<TextBlock Text="{Binding Label}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Uniform grid -->
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Design-Time Data in Templates
<ListBox ItemsSource="{Binding People}">
<Design.DataContext>
<vm:MainViewModelDesign/>
</Design.DataContext>
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:PersonViewModel">
<TextBlock Text="{Binding FullName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Common Mistakes
- Missing
x:DataTypeon DataTemplate — in 12.0+ (compiled bindings default) the build fails (AVLN2000) without a type context; in 11.x with compiled bindings enabled it fails the same way. Every DataTemplate using{Binding}needsx:DataType, or use{ReflectionBinding}. new Binding(...)in C# templates (12.0+) — the 11.x idiom is gone from the compiled-by-default model; useCompiledBinding.CreateorReflectionBinding.- Binding plugins like
{Binding ^Stream}(12.0+) — plugins were removed; seeavalonia-versionsfor replacements. - Wrong resource scope — a DataTemplate in
Window.Resourcesis not visible to templates inside a nestedUserControl. EachUserControlhas its own resource scope. Put shared templates inApplication.ResourcesorApplication.DataTemplates. - Using
DataTemplateinstead ofTreeDataTemplatefor tree data —DataTemplaterenders the node content but does not provide anItemsSourcebinding for children;TreeViewwill not recurse into children. - Matching on interface type — Avalonia's automatic type-based template selection matches on the concrete runtime type, not interfaces.
IDataTemplate.Match()must explicitly check for the concrete type, or register templates against the concrete type. - Multiple templates for same type in same scope — if two
DataTemplates inApplication.DataTemplateshavex:DataType="vm:PersonViewModel", the first one always wins. Remove duplicates or use keyed resources for multiple representations. - Circular template reference — a
DataTemplatethat creates aContentControlwhoseContentbinds back to the same ViewModel type causes infinite recursion. Use a keyed template or break the cycle with a wrapper ViewModel. - Setting both
ItemTemplateand using auto-selection — an explicitItemTemplatealways takes precedence overApplication.DataTemplates. RemoveItemTemplateto rely on global auto-selection.