Accessibility
Loam controls are keyboard-operable and screen-reader-aware out of the box, because almost every one of them either subclasses an Avalonia input control (so it inherits focus and key handling) or wires its own key handlers and AutomationProperties in code. This page collects the conventions that hold across the library — how focus moves, which keys do what in each control, how disabled state is expressed, and the one thing you still have to do yourself: name controls that show only an icon.
There is no XAML and no separate accessibility layer to configure. Everything here is plain C#, set the same way you set any other property.
using Avalonia.Automation; // AutomationProperties.SetName / SetHelpText
using Loam; // Icons
using Loam.Controls; // IconButton, Menu, DataGrid<T>, …Mental model
Three things make a Loam control accessible, and Loam handles two of them for you. Focus & keys come from the Avalonia base type or a control-specific key handler — you get them for free. Automation names are derived automatically wherever there's text to derive them from (a label, content, or value). The one gap you own is an icon-only control with no text — there is nothing to derive a name from, so you set one with AutomationProperties.SetName. If a control shows a word, it already announces it; if it shows only a glyph, name it.
Where names come from
A screen reader announces a control by its automation name. Loam fills this in automatically from whatever text the control already carries, falling back through a small chain. You only need to intervene when that chain comes up empty — typically an icon-only control.
| Control | Automation name derived from | You set a name when… |
|---|---|---|
Button / Fab | Content / Label text | never (it has a label) |
IconButton / ToggleIconButton | — (glyph only) | always |
NavLink | Label, then Content, then Href | Content is a custom control, not text |
NavigationRailItem / BottomNavigationItem | Label | always set Label (icon-only rows) |
ListItem | Content + SecondaryText | the row is icon-only |
TreeViewItem | Text | only an Icon is set, no Text |
Field inputs (TextField, Select, pickers) | Label, then placeholder / value | rarely (set a Label) |
Slider / Rating | — (no inherent text) | when there's no adjacent label |
ProgressLinear / ProgressCircular / Skeleton | Label property | set Label so the busy state is announced |
Name your icon-only controls
This is the single highest-value accessibility habit in a Loam app. An IconButton, an icon-only NavLink, or a bare Slider has no text for assistive technology to read, so it announces as nothing. Give it a name:
using Avalonia.Automation;
using Loam;
using Loam.Controls;
var delete = new IconButton { Icon = Icons.Material.Filled.Delete, Color = LoamColor.Error };
AutomationProperties.SetName(delete, "Delete");Several controls expose a dedicated property that is the name — prefer it when it exists: ProgressCircular.Label, Skeleton.Label, NavigationRailItem.Label, NavLink.Label. Setting the property keeps the name and the visible text in sync.
Focus management
Focus behavior is consistent across the library:
- Tab order — every interactive control is focusable and joins the tab order; Tab / Shift+Tab move forward and back. A focused control shows a focus highlight.
- Arrow keys move within a control — once a composite control (a
DataGrid<T>row group, aToggleGroup, aTabsstrip, aMenupopup) has focus, the arrow keys navigate inside it rather than leaving it. Tab is what moves you between controls. - Activation keys are Enter and Space — this is uniform: buttons, list rows, tree nodes, tabs, expansion-panel headers, nav items, and field pickers all activate on these two keys.
Focus restore on overlays
Loam's transient surfaces capture the element that had focus when they opened and return focus to it when they close. This holds for Menu, DialogService, Overlay, and Popover — so dismissing a dialog or closing a menu lands the user back where they were, not at the top of the page.
For dialogs specifically, when DialogOptions.AutoFocus is true (the default) the first enabled, visible, focusable child of the dialog receives focus as it opens. The backdrop scrim and the dialog surface carry their own automation help text describing whether Escape and scrim-click dismissal are enabled.
using Loam.Controls;
// AutoFocus moves focus into the dialog on open; focus is restored to the
// triggering control after ShowAsync resolves.
var result = await DialogService.For(this).ShowAsync(
"Rename",
instance =>
{
var field = new TextField { Label = "Name" }; // first focusable child → gets focus
var ok = new Button { Content = "Save" };
ok.Click += (_, _) => instance.Ok(field.Text);
return new StackPanel { Spacing = 12, Children = { field, ok } };
});Disabled semantics
Setting IsEnabled = false on any Loam control does three things consistently:
- Removes it from the tab order — it can no longer be focused.
- Blocks pointer and keyboard activation — clicks and activation keys are ignored.
- Dims it to the theme's disabled opacity — resolved from the
StateDisabledOpacitytheme token, so a disabled control reads as muted in both light and dark themes (see Theming → tokens).
Composite controls disable their parts together. A disabled FileUpload disables its generated picker button, file chips, and clear action at once; a disabled Form disables its generated submit/reset actions. In both cases the programmatic API stays live — FileUpload.Clear(), Form.Validate(), and Form.ResetFields() still run so you can drive state from your view model while the UI is locked.
The field pickers follow the same rule: a disabled picker suppresses pointer, keyboard, and OpenPicker() from opening the flyout, while still accepting programmatic value updates.
Disabled is not the same as read-only
IsEnabled = false removes a control from the tab order entirely — a keyboard user can't reach it to read its value. When you want a value to stay readable and focusable but not editable, prefer the control's own read-only flag instead: TextField.ReadOnly or Rating.ReadOnly. Reserve IsEnabled = false for actions that genuinely aren't available right now.
Keyboard reference by control
The tables below list the keys each control handles once it has focus. They are grounded in each control's actual key handling; the per-component pages carry the same detail in context.
Buttons & menus
| Control | Key | Action |
|---|---|---|
Button / IconButton / Fab | Space / Enter | Invoke Click / Command. |
ToggleIconButton | Space / Enter | Flip Toggled, then fire Click. |
Menu (trigger) | Space / Enter | Open the flyout and focus the first enabled row. |
Menu (popup) | ↑ / ↓ | Move through enabled rows (wraps); disabled rows are skipped. |
Menu (popup) | Esc | Close the popup; focus returns to the trigger. |
See Buttons & menus → Accessibility.
Form inputs
| Control | Key | Action |
|---|---|---|
TextField / MaskedTextField | (typing) | Edit text; TextField validates on blur when Required/Validation is set. |
NumericField | ↑ / ↓ | Step Value by Step, clamped to [Minimum, Maximum]. |
Select | Enter / Space | Open the flyout. Esc closes it. |
Autocomplete | Esc | Close the suggestion flyout. Enter/Space re-runs the search. |
Slider | ←/↓, →/↑ | Step Value. Home/End jump to Minimum/Maximum. |
Rating | ←/→ (and ↑/↓) | Change the score. Home clears to 0, End sets the max, Space/Enter activate. |
ToggleGroup | ←/→ (and ↑/↓) | Move between segments. Home/End jump to first/last; Space/Enter select. |
CheckBox / Switch / Radio | Space | Toggle (inherited from the Avalonia base control). |
See Form inputs → Accessibility.
Pickers
| Control | Key | Action |
|---|---|---|
| Field picker (closed) | Enter / Space | Open the flyout (non-editable mode). |
Field picker in Editable mode | Alt+↓ | Open the flyout — Enter/Space belong to the text box. |
| Date/Time/Range flyout | Esc | Close without committing; OK commits, Cancel discards. |
Editable field | Enter | Commit typed text (also on focus loss); invalid text stays on screen with the Invalid… error. |
MonthCalendar | Enter / Space | Select the focused day and raise DateSelected; arrow keys move across days and months. |
ColorPicker commits on swatch selection rather than via OK/Cancel. The inline clear button (when Clearable) is named "Clear date" / "Clear time" / "Clear dates" and clears without opening the flyout. See Pickers → Accessibility.
Data display
| Control | Key | Action |
|---|---|---|
DataGrid<T> row | ↑/↓, Home/End | Move focus between rendered rows (no wrap, stays on the current page). |
DataGrid<T> row | Space / Enter | Select the focused row (toggles in Multiple). |
DataGrid<T> (Multiple) | Shift+↑/↓/Home/End, Ctrl+A | Extend / select-all the rendered rows. Esc clears. |
DataGrid<T> | Ctrl/Cmd+C | Copy the selection (or whole view) as TSV. |
Tabs header | ←/↓, →/↑ | Move to the previous / next tab. Enter/Space select. |
TreeView node | → / ← | Expand / collapse (or step into / out of children). ↑/↓ move through visible nodes. |
TreeView node | Enter / Space | Select / toggle the focused node. |
ExpansionPanel header | Enter / Space | Toggle the panel (announces "Expanded"/"Collapsed"). |
ListItem row | Enter / Space | Raise Activated. |
Carousel | ← / → | Previous / next slide; arrows and bullets are individually focusable. |
Pagination | Tab + Enter/Space | Move to and activate a page or arrow button. |
See Data display → Accessibility and the dedicated DataGrid keyboard table.
Overlays & navigation
| Control | Key | Action |
|---|---|---|
DialogService | Esc | Cancel while DismissOnEscape is true. AutoFocus focuses the first focusable child on open. |
SnackbarService toast | Esc | Dismiss the focused toast (even with no dismiss button shown). |
Overlay | Esc | Set Visible = false while AutoClose is enabled. |
Popover | Esc | Close the open surface; a Trigger also opens on Space/Enter. |
CommandPalette | ↓/↑, Enter, Esc | Move the highlight, run the command, close the palette. |
Link / NavLink / NavGroup | Enter / Space | Activate the link, or toggle a NavGroup open/closed. |
NavigationRail / BottomNavigation item | Enter / Space | Select the destination. |
See Overlays → Accessibility and Navigation → Accessibility.
Recipe: an accessible icon toolbar
A row of icon-only actions is the most common place accessibility slips, because nothing in the markup carries text. The fix is mechanical: give every glyph an AutomationProperties name (and, for sighted users, a Tooltip). Everything below is plain C#.
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Layout;
using Loam;
using Loam.Controls;
IconButton Named(string glyph, string name, LoamColor color = LoamColor.Default)
{
var button = new IconButton { Icon = glyph, Color = color, Variant = Variant.Text };
AutomationProperties.SetName(button, name); // screen-reader name
Tooltip.Set(button, name); // visible hint on hover/focus
return button;
}
var favorite = new ToggleIconButton
{
Icon = Icons.Material.Filled.FavoriteBorder,
ToggledIcon = Icons.Material.Filled.Favorite,
Color = LoamColor.Primary,
};
AutomationProperties.SetName(favorite, "Favorite");
Tooltip.Set(favorite, "Favorite");
var toolbar = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = 4,
Children =
{
Named(Icons.Material.Filled.Edit, "Edit"),
Named(Icons.Material.Filled.ContentCopy, "Copy"),
Named(Icons.Material.Filled.Delete, "Delete", LoamColor.Error),
favorite,
},
};The same pattern covers an icon-only NavLink (set Label instead of Content), a NavigationRailItem (always set Label), and a busy ProgressCircular (set Label).
Reduced motion
A few controls animate by default. Where motion could be a problem, each one exposes a switch to turn it off without losing the control:
Collapse— setAnimated = false(orDuration = TimeSpan.Zero) for an instant reveal.Carousel—AutoPlayis off by default; leave it off, or if you enable it keepShowArrows/ShowBulletson so there's always a manual control.
Don't auto-advance content the user can't pause
Motion a user can't stop is a real accessibility barrier. Carousel.AutoPlay defaults to off for exactly this reason — turn it on only with manual controls visible and a calm AutoPlayInterval.
See also
- Buttons & menus → Accessibility — the button family and
Menupopup keys. - Form inputs → Accessibility — field, toggle, and specialized-input keys.
- Pickers → Accessibility — flyout commit/dismiss and editable-mode keys.
- Data display → Accessibility — grid, tabs, tree, and list navigation.
- Overlays → Accessibility — dialog, snackbar, overlay, and palette behavior.
- Navigation → Accessibility — links, nav rows, rail, and bottom-bar keys.
- Theming → tokens — where the disabled-opacity and focus tokens come from.