data-* Attributes, Custom Events, and Behavior Hooks to Avalonia Attached PropertiesPrimary APIs:
AvaloniaProperty.RegisterAttached<TOwner, THost, TValue>AttachedProperty<T>.Changed.AddClassHandler<TTarget>(...)RoutedEvent.Register<TOwner, TEventArgs>(...), RaiseEvent(...), AddHandler(...)Classes.Set(...), AutomationProperties.*Reference docs:
16-property-system-attached-properties-behaviors-and-style-properties.md18-input-system-and-routed-events.md60-automation-properties-and-attached-behavior-patterns.md| HTML/JS idiom | Avalonia mapping |
|---|---|
data-kind="danger" |
attached property (UiData.Kind) |
CSS [data-kind="danger"] selector |
class set by attached property (.kind-danger) |
dispatchEvent(new CustomEvent("remove")) |
routed event (RemoveRequestedEvent) |
| behavior via dataset + JS listeners | attached-property class handlers + routed events/commands |
HTML/CSS baseline:
<button class="chip" data-kind="danger" data-track-id="delete-account">
Delete Account
</button>
.chip[data-kind="danger"] {
border-color: #c7353f;
color: #fff;
background: #9f1f27;
}
Avalonia usage:
<Button Content="Delete Account"
local:UiData.Kind="danger"
local:UiData.TrackId="delete-account"
Classes="chip" />
<Style Selector="Button.chip.kind-danger">
<Setter Property="Background" Value="#9F1F27" />
<Setter Property="Foreground" Value="White" />
</Style>
HTML/JS baseline:
<action-chip id="archive-chip">Archive</action-chip>
action-chip { cursor: pointer; user-select: none; }
const chip = document.querySelector("#archive-chip");
chip?.addEventListener("click", () => {
chip.dispatchEvent(
new CustomEvent("remove-requested", { bubbles: true, detail: { id: "archive-chip" } })
);
});
Avalonia usage:
<local:ActionChip Content="Archive"
RemoveRequested="OnChipRemoveRequested" />
public class ActionChip : Button
{
public static readonly RoutedEvent<RoutedEventArgs> RemoveRequestedEvent =
RoutedEvent.Register<ActionChip, RoutedEventArgs>(nameof(RemoveRequested), RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs>? RemoveRequested
{
add => AddHandler(RemoveRequestedEvent, value);
remove => RemoveHandler(RemoveRequestedEvent, value);
}
public ActionChip()
{
Click += (_, _) => RaiseEvent(new RoutedEventArgs(RemoveRequestedEvent));
}
}
<div class="toolbar">
<button class="chip" data-kind="warning" data-track-id="archive-item">Archive</button>
</div>
.toolbar { display: flex; gap: .5rem; }
.chip[data-kind="warning"] {
background: #6f4f00;
color: #ffe9ad;
}
<StackPanel Orientation="Horizontal" Spacing="8">
<local:ActionChip Content="Archive"
local:UiData.Kind="warning"
local:UiData.TrackId="archive-item"
Classes="chip" />
</StackPanel>
<Style Selector="local|ActionChip.chip.kind-warning">
<Setter Property="Background" Value="#6F4F00" />
<Setter Property="Foreground" Value="#FFE9AD" />
</Style>
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
public static class UiData
{
public static readonly AttachedProperty<string?> KindProperty =
AvaloniaProperty.RegisterAttached<UiData, Control, string?>("Kind");
public static readonly AttachedProperty<string?> TrackIdProperty =
AvaloniaProperty.RegisterAttached<UiData, Control, string?>("TrackId");
static UiData()
{
KindProperty.Changed.AddClassHandler<Control>((control, e) =>
{
var kind = e.GetNewValue<string?>();
control.Classes.Set("kind-danger", kind == "danger");
control.Classes.Set("kind-warning", kind == "warning");
});
}
public static string? GetKind(Control control) => control.GetValue(KindProperty);
public static void SetKind(Control control, string? value) => control.SetValue(KindProperty, value);
public static string? GetTrackId(Control control) => control.GetValue(TrackIdProperty);
public static void SetTrackId(Control control, string? value) => control.SetValue(TrackIdProperty, value);
}
public class ActionChip : Button
{
public static readonly RoutedEvent<RoutedEventArgs> RemoveRequestedEvent =
RoutedEvent.Register<ActionChip, RoutedEventArgs>(nameof(RemoveRequested), RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs>? RemoveRequested
{
add => AddHandler(RemoveRequestedEvent, value);
remove => RemoveHandler(RemoveRequestedEvent, value);
}
public ActionChip()
{
Click += (_, _) => RaiseEvent(new RoutedEventArgs(RemoveRequestedEvent));
}
}
var archiveChip = new ActionChip { Content = "Archive" };
UiData.SetKind(archiveChip, "warning");
UiData.SetTrackId(archiveChip, "archive-item");
RoutingStrategies.Bubble and raised from the expected control.