👉 How To Troubleshoot Styles
Much of the Avalonia UI styling system corresponds to CSS styling approaches. So without knowledge of this technology, you may find the hints here helpful.
Selector has no Targets
An Avalonia UI selector, like a CSS selector, does not raise an error or warning when there are no controls which can be matched. The style will silently fail to show.
Check whether you have used a name or class that does not exist.
Check whether you have used a child selector where there are no children to match.
Include File Sequence
Styles are applied in order of declaration. If there are multiple style files included that target the same control property, the last style included will override the previous ones. For example:
<Style Selector="TextBlock.header">
<Style Property="Foreground" Value="Green" />
</Style>
<Style Selector="TextBlock.header">
<Style Property="Foreground" Value="Blue" />
<Style Property="FontSize" Value="16" />
</Style>
<StyleInclude Source="Style1.axaml" />
<StyleInclude Source="Style2.axaml" />
Here styles from file Styles1.axaml were applied first, so setters in styles of file Styles2.axaml take priority. The resulting TextBlock will have FontSize="16" and Foreground="Green". The same order prioritization happens within style files also.
Locally Set Properties Have Priority
A local value defined directly on a control often has higher priority than any style value. So in this example the text block will have a red foreground:
<Style Selector="TextBlock.header">
<Setter Property="Foreground" Value="Green" />
</Style>
...
<TextBlock Classes="header" Foreground="Red" />
You can see the full list of value priorities in the BindingPriority
enum, where lower enum values have the higher priority.
BindingPriority | Value | Comment |
---|---|---|
Animation | -1 | The highest priority - even overrides a local value |
LocalValue | 0 | A local value is set on the property of the control. |
StyleTrigger | 1 | This is triggered when a pseudo class becomes active. |
TemplatedParent | 2 | |
Style | 3 | |
Unset | 2147483647 |
The exception is that Animation
values have the highest priority and can even override local values.
Some default Avalonia UI styles use local values in their templates instead of template bindings or style setters. This makes it impossible to update the template property without replacing the whole template.
Missing style pseudo class (trigger) selector
Let's imagine a situation in which you might expect a second style to override previous one, but it doesn't:
<Style Selector="Border:pointerover">
<Setter Property="Background" Value="Blue" />
</Style>
<Style Selector="Border">
<Setter Property="Background" Value="Red" />
</Style>
...
<Border Width="100" Height="100" Margin="100" />
With this code example the Border
has a Red background normally and Blue when the pointer is over it. This is because as with CSS more specific selectors have precedence. It is an issue, when you want to override default styles of any state (pointerover, pressed or others) with a single style. To achieve it you will need to have new styles for these states as well.
Visit the Avalonia source code to find the original templates when this happens and copy and paste the styles with pseudoclasses into your code.
Selector with a pseudoclass doesn't override the default
The following code example of styles that can be expected to work on top of default styles:
<Style Selector="Button">
<Setter Property="Background" Value="Red" />
</Style>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="Blue" />
</Style>
You might expect the Button
to be red by default and blue when pointer is over it. In fact, only setter of first style will be applied, and second one will be ignored.
The reason is hidden in the Button's template. You can find the default templates in the Avalonia source code (old Default theme and new Fluent theme), but for convenience here we have simplified one from the Fluent theme:
<Style Selector="Button">
<Setter Property="Background" Value="{DynamicResource ButtonBackground}"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
</Style>
The actual background is rendered by a ContentPresenter
, which in the default is bound to the Buttons Background
property. However in the pointer-over state the selector is directly applying the background to the ContentPresenter (Button:pointerover /template/ ContentPresenter#PART_ContentPresenter
) That's why when our setter was ignored in the previous code example. The corrected code should target content presenter directly as well:
<!-- Here #PART_ContentPresenter name selector is not necessary, but was added to have more specific style -->
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Blue" />
</Style>
You can see this behavior for all controls in the default themes (both old Default and the new Fluent), not just Button. And not just for Background, but also other state-dependent properties.
Why default styles change the ContentPresenter Background
property directly instead of changing the Button.Background
property?
This is because if the user were to set a local value on the button, it would override all styles, and make button always the same color. For more details see this reverted PR.
Previous value of specific properties is not restored when style is not applied anymore
In Avalonia we have multiple types of properties, and one of them, Direct Property, doesn't support styling at all. These properties work in simplified way to achieve lower overhead and higher performance, and do not store multiple values depending on priority. Instead only latest value is saved and cannot be restored. You can find more details about properties here.
Typical example is CommandProperty. It is defined as a DirectProperty, and it will never work properly. In the future attempt to style direct property will be resulted in compile time error, see #6837.