app-card with Named SlotsAppCard Templated Content ControlPrimary APIs:
ControlTemplate, TemplateBinding, ContentPresenterControlTheme, nested selectors (^, /template/)TemplatePartAttribute, TemplateAppliedEventArgs, INameScopeContentControl with additional styled slot propertiesReference docs:
10-templated-controls-and-control-themes.md28-custom-themes-xaml-and-code-only.md51-template-content-and-func-template-patterns.md| Shadow DOM / CSS idiom | Avalonia mapping |
|---|---|
:host |
style selector for control type (local|AppCard) |
:host(.compact) |
class selector on control (local|AppCard.compact) |
<slot name="header"> |
dedicated styled property + ContentPresenter |
default <slot> |
ContentControl.Content through TemplateBinding Content |
::part(header) |
named template element (x:Name="PART_Header") + /template/ selectors |
| Shadow-root style encapsulation | ControlTheme-scoped selectors anchored with ^ |
HTML/CSS baseline:
<app-card class="compact">
<h3 slot="header">Revenue</h3>
<p>$420,000</p>
<button slot="actions">Details</button>
</app-card>
app-card { display: block; border-radius: 12px; }
app-card.compact { padding: .5rem; }
app-card::part(header) { font-weight: 700; }
class AppCardElement extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: "open" });
root.innerHTML = `
<section part="container">
<header part="header"><slot name="header"></slot></header>
<main part="content"><slot></slot></main>
<footer part="actions"><slot name="actions"></slot></footer>
</section>
`;
}
}
customElements.define("app-card", AppCardElement);
Avalonia pattern:
<local:AppCard Classes="compact"
HeaderContent="Revenue"
ActionsContent="Details">
<TextBlock Text="$420,000" />
</local:AppCard>
app-card with Named Slots<section class="cards">
<app-card>
<span slot="header">Orders</span>
<span>1,280</span>
<button slot="actions">Open</button>
</app-card>
</section>
.cards { display: grid; gap: .75rem; }
app-card::part(container) {
border: 1px solid #2a3348;
background: #111827;
}
app-card.compact::part(container) { padding: .45rem .65rem; }
<ControlTheme x:Key="{x:Type local:AppCard}" TargetType="local:AppCard">
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="PART_Container"
Padding="12"
CornerRadius="12"
BorderBrush="#2A3348"
BorderThickness="1"
Background="#111827">
<Grid RowDefinitions="Auto,*,Auto" RowSpacing="8">
<ContentPresenter x:Name="PART_Header"
Grid.Row="0"
Content="{TemplateBinding HeaderContent}" />
<ContentPresenter x:Name="PART_Content"
Grid.Row="1"
Content="{TemplateBinding Content}" />
<ContentPresenter x:Name="PART_Actions"
Grid.Row="2"
Content="{TemplateBinding ActionsContent}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^ /template/ ContentPresenter#PART_Header">
<Setter Property="TextBlock.FontWeight" Value="Bold" />
</Style>
<Style Selector="^.compact /template/ Border#PART_Container">
<Setter Property="Padding" Value="8" />
</Style>
</ControlTheme>
<local:AppCard HeaderContent="Orders" Classes="compact">
<TextBlock Text="1,280" />
</local:AppCard>
AppCard Templated Content Controlusing Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
[TemplatePart("PART_Header", typeof(ContentPresenter), IsRequired = true)]
[TemplatePart("PART_Content", typeof(ContentPresenter), IsRequired = true)]
[TemplatePart("PART_Actions", typeof(ContentPresenter), IsRequired = false)]
public class AppCard : ContentControl
{
public static readonly StyledProperty<object?> HeaderContentProperty =
AvaloniaProperty.Register<AppCard, object?>(nameof(HeaderContent));
public static readonly StyledProperty<object?> ActionsContentProperty =
AvaloniaProperty.Register<AppCard, object?>(nameof(ActionsContent));
public object? HeaderContent
{
get => GetValue(HeaderContentProperty);
set => SetValue(HeaderContentProperty, value);
}
public object? ActionsContent
{
get => GetValue(ActionsContentProperty);
set => SetValue(ActionsContentProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_ = e.NameScope.Find<ContentPresenter>("PART_Header");
_ = e.NameScope.Find<ContentPresenter>("PART_Content");
_ = e.NameScope.Find<ContentPresenter>("PART_Actions");
}
}
var card = new AppCard
{
HeaderContent = "Orders",
Content = new TextBlock { Text = "1,280" },
ActionsContent = new Button { Content = "Open" }
};
card.Classes.Set("compact", true);
HeaderContent, ActionsContent) instead of late string lookups.ControlTheme; avoid runtime template parsing in hot paths.ContentPresenter binds to correct template-bound property.::part-style rules seem missing after migration.
^ ... /template/ ...).compact) is set on the control instance, not only on internal template elements.