UI kit
Plugin dialogs and pages are Avalonia views with view models (CommunityToolkit.Mvvm). They use the host’s controls and theme, so a plugin looks and behaves like the rest of OADM in dark and light. Two rules cover most of it:
- Reuse, never copy. Every element that exists in OADM is a shared control. Use it.
- No local styles. No colors, font sizes, weights or paddings in your views. Use the theme’s classes and
Oadm.*Brushkeys; both appearances define every key.
Add the namespace xmlns:ui="using:Oadm.Sdk.Client.Controls" to your XAML.
Shared controls
Section titled “Shared controls”| Control | Use for |
|---|---|
ui:DialogTitleBar |
The first row of every dialog: the title and an X that closes it |
ui:DialogFooter |
The last row: Cancel on the left, your buttons on the right |
ui:CardHeader |
The heading of a card, with optional controls right of the title |
ui:FormField |
Every labeled input: label, input, and the error or hint below it. Classes wide, inline; Info adds an info icon |
ui:PasswordBox |
Every password field, with the eye button |
ui:SearchBox |
Every search field |
ui:ToolbarButton, ui:ToolbarSeparator |
Toolbar buttons (icon and text) and the line between groups |
ui:IconLabel, ui:OadmIcon |
An icon with text (button content), a single icon |
ui:StatusChip |
Every status: a colored icon (IsOk, IsWarning, IsError, IsAccent, IsInfo) and plain text |
ui:StatCard |
A count card of a monitoring page |
ui:ProgressRow |
A progress bar with its status text |
ui:LoadingOverlay |
A dialog that waits for a read: a ring and text in the middle, Cancel stays usable |
ui:FileRow |
A chosen file with its “Choose file” button |
ui:CodeView, ui:CodeEditor |
Read-only code (JSON, XML, param.cgi answers, colored) and an editable code field |
ui:InfoIcon |
An explanation that shows on hover and click |
ui:WizardSteps |
The step row of a wizard |
ui:DeviceIcon, ui:DeviceTree, ui:PageTab |
A device’s icon by category and status, devices grouped by tag, page tabs |
MessageWindow |
Every message and confirmation: MessageWindow.ConfirmAsync(owner, title, message, confirmText) |
DevicePickerWindow |
Every “choose devices” dialog: search, tags, Select all, 5,000 devices |
ComboBox Classes="grouped" + GroupedChoice |
A select field with group headers |
For lists that a grid shows, use RangeObservableCollection<T> (Oadm.Sdk.Client.Collections): ReplaceAll,
AddRange and RemoveAll raise one change for the whole batch.
Layouts
Section titled “Layouts”A dialog with one form has no card: title bar, the form in Border.dialogBody, footer. Fixed width,
SizeToContent="Height":
<Window ... Width="500" SizeToContent="Height" CanResize="False" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" WindowDecorations="BorderOnly"> <Grid RowDefinitions="Auto,Auto,Auto"> <ui:DialogTitleBar Grid.Row="0" Text="Set UPnP name" /> <Border Grid.Row="1" Classes="dialogBody"> <StackPanel Classes="form"> <ui:FormField Label="Name"> <TextBox Text="{Binding Name}" /> </ui:FormField> </StackPanel> </Border> <ui:DialogFooter Grid.Row="2" CancelCommand="{Binding CancelCommand}"> <Button Classes="primary" Content="OK" Command="{Binding ApplyCommand}" ToolTip.Tip="{Binding FormError}" ToolTip.ShowOnDisabled="True" /> </ui:DialogFooter> </Grid></Window>A dialog with several sections or a list uses cards (Border.card, Margin="16,0") between title bar and footer,
with a fixed width and height.
A page starts with its content. The description goes into the page header with ui:PageHeader.Subtitle, never
into a card heading. A status sits directly left of the Save button it reports on.
Validation
Section titled “Validation”Every validation error appears directly below its input: small red text and a red border. Never an error list at
the bottom of a dialog. Form view models derive from ValidatingViewModel and register one rule per property:
public sealed partial class UserViewModel : ValidatingViewModel{ public UserViewModel() { Validation .Rule(nameof(UserName), () => UserName.Trim().Length == 0 ? "Enter a user name." : null) .Rule(nameof(Confirm), () => Confirm != Password ? "The passwords do not match." : null); Validation.Validate(); }
[ObservableProperty] public partial string UserName { get; set; } = "";
// ...}- An error shows once the field was edited or left, or the user tried to submit (
Validation.ShowAll()); never on an untouched form. After loading values,Validation.Reset(...). - Submit buttons stay disabled while any error exists (
IsFormValid), with the reason as tooltip (FormError). - An answer of the server that belongs to a field:
Validation.SetServerError(property, message). - An error of a whole table goes directly below the table; an error of one row stays in its status column.
Buttons
Section titled “Buttons”- One accent button per view (
Button.primary): the main action. Everything else isButton.secondary. - Destructive actions are red:
Button.danger,Button.toolbar.dangerfor flat buttons,Button.link.dangerfor row links.MessageWindowturns its confirm button red when the text starts with Delete, Remove or Release. Destructive entries sit apart from everyday ones (a separator, last in a menu). - Button and menu labels never end with “…”, also when they open a dialog or a file picker.
- Tables are
DataGrids withRangeObservableCollectionsources: virtualized, one change per batch. - Statuses are
ui:StatusChipin a template column. Grids whose status explains a problem getClasses="wrapRows"so the text can take two lines. - Numbers are right-aligned:
CellStyleClasses="number"and a headerTextBlock.numberHeader. - An empty list says in one line what to do first.
Icons are resources of the client. Use them with DynamicResource in XAML (Icon="{DynamicResource Icon.refresh}"),
and by key without Icon. in IconKey properties (IconKey="refresh"). Some keys:
add, remove, refresh, restart, search, settings, edit, copy, export, upload, download, file,
folder, start, stop, check, close, info, key, lock, lockOpen, eye, eyeOff, shield, users,
user, network, firmware, app, video, snapshot, clock, activity, toggle, zap, bell, flame,
globe, grid, hardDrive, wrench, eraser, tag, plugin, clipboardCheck.
The full list is in plugins/README.md. A new
icon goes into src/Oadm.Client/Themes/Icons.axaml (a 24 x 24 Lucide stroke geometry).
Wording
Section titled “Wording”Texts are for installers who know Axis devices. Short and plain:
- Intros and subtitles: one short sentence, or none. Never explain what a visible control already shows.
- No protocol, API or internal names in UI text. Say what the user can do.
- Tooltips only when they add something, at most about eight words.
- Step names are imperatives with the article: “Wait for the device to come back”.
- Errors say what happened and, when nothing was changed, “Nothing was changed”.
- No filler: no “simply”, “easily”, “please note”.
Reference
Section titled “Reference”Every member with its signature in the SDK reference: Oadm.Sdk.Client.Controls, ValidatingViewModel, FormValidator, RangeObservableCollection<T>, MessageWindow, DevicePickerWindow.