:has() State to Avalonia Style Architecture:has()-Style Parent State MappingPrimary APIs:
Styles, StyleInclude, selector orderingClasses.Set(...), control pseudo-classes and classesContainerQuery, Container.Name, Container.SizingReference docs:
02-html-css-selectors-cascade-variables-and-theming.md07-html-css-design-system-utilities-and-component-variants.md16-property-system-attached-properties-behaviors-and-style-properties.md| Advanced CSS idiom | Avalonia mapping |
|---|---|
@layer reset, components, overrides |
style ordering in Styles / merged dictionaries |
@scope (.shell) { ... } |
parent-qualified selectors (Grid.shell ...) |
:where(...) low-specificity grouping |
shared class selectors + explicit style ordering |
:is(...) selector grouping |
multiple explicit selectors or class normalization |
:has(...) parent dependent style |
set parent class from state (has-*) and style by class |
@container (min-width: 900px) |
ContainerQuery Query="min-width:900" |
HTML/CSS baseline:
<div class="panel card warning">...</div>
@layer reset, components, overrides;
@layer components {
.card { border-radius: 12px; background: #151b28; }
}
@layer overrides {
.warning { border: 1px solid #d18a00; }
}
Avalonia equivalent (ordered styles):
<Styles>
<!-- Components layer -->
<Style Selector="Border.card">
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Background" Value="#151B28" />
</Style>
<!-- Overrides layer (later wins) -->
<Style Selector="Border.warning">
<Setter Property="BorderBrush" Value="#D18A00" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Styles>
HTML/CSS scoped rule:
<section class="shell">
<button class="cta">Save</button>
</section>
@scope (.shell) {
.cta { min-inline-size: 8rem; }
}
Avalonia scope pattern:
<Grid Classes="shell">
<Grid.Styles>
<Style Selector="Grid.shell Button.cta">
<Setter Property="MinWidth" Value="128" />
</Style>
</Grid.Styles>
<Button Classes="cta" Content="Save" />
</Grid>
:has()-Style Parent State MappingHTML/CSS baseline:
<div class="toolbar">
<button class="chip danger">Delete</button>
</div>
.toolbar:has(.danger) {
outline: 1px solid #c7353f;
}
Avalonia does not use CSS :has(). The practical pattern is to set a class on parent when child state changes.
<Border Classes="toolbar has-danger">
<Border.Styles>
<Style Selector="Border.toolbar.has-danger">
<Setter Property="BorderBrush" Value="#C7353F" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Border.Styles>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Classes="chip danger" Content="Delete" />
</StackPanel>
</Border>
<section class="dashboard" data-density="compact">
<div class="cards">
<article class="card warning">Service latency high</article>
</div>
</section>
@layer base, components, state;
.dashboard { container-type: inline-size; }
@container (min-width: 900px) {
.cards { grid-template-columns: repeat(3, 1fr); }
}
.dashboard[data-density="compact"] .card { padding: .45rem .65rem; }
<Grid Classes="dashboard compact"
Container.Name="Host"
Container.Sizing="Width">
<Grid.Styles>
<Style Selector="Grid.dashboard Border.card">
<Setter Property="Padding" Value="12" />
</Style>
<Style Selector="Grid.dashboard.compact Border.card">
<Setter Property="Padding" Value="8" />
</Style>
<ContainerQuery Name="Host" Query="min-width:900">
<Style Selector="UniformGrid#CardsGrid">
<Setter Property="Columns" Value="3" />
</Style>
</ContainerQuery>
</Grid.Styles>
<UniformGrid x:Name="CardsGrid" Columns="1">
<Border Classes="card warning">
<TextBlock Text="Service latency high" />
</Border>
</UniformGrid>
</Grid>
using Avalonia;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Styling;
var toolbar = new Border
{
Child = new StackPanel
{
Orientation = Avalonia.Layout.Orientation.Horizontal,
Spacing = 8,
Children =
{
new Button { Content = "Delete", Classes = { "chip", "danger" } },
new Button { Content = "Duplicate", Classes = { "chip" } }
}
}
};
void UpdateToolbarState()
{
var panel = (StackPanel)toolbar.Child!;
var hasDanger = panel.Children
.OfType<StyledElement>()
.Any(c => c.Classes.Contains("danger"));
toolbar.Classes.Set("toolbar", true);
toolbar.Classes.Set("has-danger", hasDanger);
}
UpdateToolbarState();
var host = new Grid();
Container.SetName(host, "Host");
Container.SetSizing(host, ContainerSizing.Width);
Dispatcher.UIThread if driven from async background signals.:has() migration seems hard to maintain.
Container.Name and Container.Sizing are set on the intended container.