Skip to content

Data display ​

Controls for presenting structured information: lists, tables, grids, trees, tabs, timelines, carousels, paginators, and step wizards. All controls live in the Loam.Controls namespace; enums (LoamColor, LoamSize, Variant, Typo) live in the Loam namespace. (Column alignment uses Avalonia's own HorizontalAlignment from Avalonia.Layout.)

Where the button family is about committing to an action, this page is about showing data — turning a model into something a user can scan, expand, sort, and step through. Most of these controls follow one of two shapes: a container with an ObservableCollection of plain item objects (Tabs.Items of TabItem, Timeline.Items of TimelineItem, Stepper.Steps of Step), or a panel you fill with real controls (List is a StackPanel; SimpleTable.Rows hold strings or any Control). Once you know which shape a control uses, populating it is the same gesture every time.

csharp
using Loam;          // LoamColor, LoamSize, Variant, Typo, Icons
using Loam.Controls; // List, DataGrid<T>, Tabs, Stepper, …

Mental model

Pick by how much the data does. Static rows you control by hand → SimpleTable. Data-shaped rows the user sorts/pages/filters/selects → DataGrid<T>. A vertical menu of tappable rows → List. Hierarchy → TreeView. Switching between views of the same region → Tabs. Optional detail you collapse → ExpansionPanels. A sequence (history or a wizard) → Timeline / Stepper. Rotating featured content → Carousel. Splitting a long result set across pages → Pagination.

Package (since 3.1). DataGrid<T>, SimpleTable, TreeView/TreeViewItem, and Pagination ship in the Loam.Data satellite package — add the package reference and register its themes with Styles.Add(new LoamData()) after LoamTheme. The remaining controls on this page (List, Tabs, Stepper, Timeline, Carousel, ExpansionPanel) stay in the core Loam package and need no extra reference. Namespaces are unchanged (Loam.Controls). See the v3 → v3.1 migration guide.

Name collision note. TreeView, TreeViewItem, and Carousel exist in both Loam and Avalonia.Controls. Qualify Loam types explicitly — Loam.Controls.TreeView, Loam.Controls.TreeViewItem, Loam.Controls.Carousel — when both namespaces are in scope.

Choosing a table. Reach for DataGrid<T> by default — it's the recommended table for any data-shaped content (sorting, paging, filtering, selection, editing). Use SimpleTable only for a handful of static, non-interactive rows you'd otherwise hand-build with a Grid. See ADR-0013.

Choosing a control ​

UseWhenReach for
Vertical menu of rowsTappable items, optional icon/secondary line, grouped by subheadersList
Static tableA few non-interactive rows you'd otherwise hand-build with a GridSimpleTable
Interactive tableSorting, paging, filtering, selection, editing over typed rowsDataGrid<T>
HierarchyNested, expandable nodes (file tree, org chart)TreeView
Switch views in placeOne region, several panels; only one visible at a timeTabs
Progressive disclosureOptional sections the user expands (FAQ, settings groups)ExpansionPanels
Chronological historyOrdered events down (or across) a connector lineTimeline
Rotating contentOne featured slide at a time, with optional auto-playCarousel
Page a long result setJump between pages of an external/manual data sourcePagination
Guide through stepsA linear, ordered wizard with Back/NextStepper

Color, Size, and Variant mean the same thing here as everywhere else — see Components overview → common parameters and Theming.


List / ListItem / ListSubheader ​

List is a vertical container (StackPanel subclass) that holds ListItem rows, mirroring the reference API's List / ListItem. Each ListItem is a ContentControl that optionally shows a leading icon and highlights on hover. ListSubheader (a Text subclass) provides a muted, semibold section label with list-aligned padding.

Use it when you need a vertical menu of tappable rows — a navigation rail, a settings list, an inbox. For tabular data with columns, use SimpleTable or DataGrid<T>; for nested rows, use TreeView.

ListItem properties ​

PropertyTypeDefaultDescription
Iconstring?nullSVG path for the leading icon. Set to null or empty to hide the icon. Mirrors the reference API's Icon.
Contentobject?nullRow content (inherited from ContentControl).
SecondaryTextstring?nullOptional supporting line rendered below the main content. Folded into the row's automation name.
Actionobject?nullOptional trailing visual, usually an IconButton or status Chip.
IsSelectedboolfalseWhether the row is shown in the selected state.
Activated (event)EventHandler<RoutedEventArgs>—Raised (bubbling) when the row is activated by pointer or by Enter/Space while focused.

ListSubheader ​

ListSubheader has no additional properties beyond Text. Typography (Typo.Caption), weight (SemiBold), padding, and secondary foreground are applied in the constructor and cannot be overridden per-instance via properties.

csharp
var list = new List
{
    Children =
    {
        new ListSubheader { Text = "Tasks" },
        new ListItem { Icon = Icons.Material.Filled.Check, Content = new Text { Text = "Ready for review" } },
        new ListItem { Icon = Icons.Material.Filled.Star,  Content = new Text { Text = "Pinned milestone" } },
        new ListSubheader { Text = "Archive" },
        new ListItem { Content = new Text { Text = "Older releases" } },
    },
};

List itself is a plain StackPanel, so rows are not auto-selected as a group — wire Activated on each ListItem (and set IsSelected yourself) when you want single-select menu behavior:

csharp
foreach (var item in list.Children.OfType<ListItem>())
{
    item.Activated += (sender, _) =>
    {
        foreach (var row in list.Children.OfType<ListItem>())
        {
            row.IsSelected = ReferenceEquals(row, sender);
        }
    };
}

SimpleTable ​

A lightweight data table hosted on an elevated Paper surface, mirroring the reference API's SimpleTable. Populate Headers with column labels and Rows with TableRow instances. Cell values may be plain strings or any Control.

Use it when you have a small, fixed set of rows that never sort, page, or filter — a spec sheet, a summary, a comparison you assemble by hand. The moment the data is dynamic or the user wants to sort it, switch to DataGrid<T>.

TableRow ​

MemberTypeDescription
CellsIList<object?>Left-to-right cell values. A value may be a string (rendered as Text) or any Control.
TableRow(params object?[] cells)constructorConvenience constructor.

SimpleTable properties ​

PropertyTypeDefaultDescription
HeadersObservableCollection<string>emptyColumn header labels.
RowsObservableCollection<TableRow>emptyData rows.
StripedboolfalseAlternating row shading. Mirrors the reference API's Striped.
HoverboolfalseRow highlight on pointer-over. Mirrors the reference API's Hover.
BorderedboolfalseCell grid lines. Mirrors the reference API's Bordered.
DenseboolfalseCompact cell padding. Mirrors the reference API's Dense.
Elevationint1Paper surface elevation. Mirrors the reference API's Elevation.
csharp
var table = new SimpleTable
{
    Striped   = true,
    Hover     = true,
    Bordered  = false,
    Dense     = false,
    Elevation = 2,
};

table.Headers.Add("Name");
table.Headers.Add("Role");
table.Headers.Add("Status");

table.Rows.Add(new TableRow("Alice", "Admin",  "Active"));
table.Rows.Add(new TableRow("Bob",   "Viewer", "Inactive"));
// Mix strings and controls in the same row:
table.Rows.Add(new TableRow("Carol", "Editor", new Chip { Text = "Pending", Color = LoamColor.Warning }));

DataGrid<T> ​

A typed data grid that renders Items across strongly typed DataGridColumn<T> definitions with clickable sort headers, optional paging, row striping/hover, and single-row selection. Mirrors the reference API's DataGrid. Because DataGrid<T> is generic it is implemented as a Decorator; sort/paging statics live on the companion DataGrids class.

Use it when the table is the workhorse of a screen: many rows, a typed model, and users who expect to sort, filter, page, select, edit cells, group, freeze columns, or export. It is the recommended default for any data-shaped content (see the note above). Bind Items to an ObservableCollection<T> and the grid tracks add/remove/reset for you.

DataGrid<T> properties ​

Property / MemberTypeDefaultDescription
ColumnsObservableCollection<DataGridColumn<T>>emptyColumn definitions.
ItemsIEnumerable<T>?nullSource rows. When the source implements INotifyCollectionChanged (e.g. ObservableCollection<T>), the grid observes it and refreshes on add/remove/reset — no need to reassign Items.
ObserveItemChangesboolfalseWhen true and rows implement INotifyPropertyChanged, the grid also refreshes when a row raises a property change.
Refresh()void—Forces a refresh; call after mutating a non-observable source in place.
ExportCsv() / ExportTsv()string—The current view (filtered + sorted, all pages) as CSV / TSV, using each column's display text with RFC-4180 quoting. Backed by the static DataGrids.ToDelimited<T>(rows, columns, separator) helper.
CopyToClipboardAsync()Task<string?>—Copies the selected rows — or the whole current view when nothing is selected — to the system clipboard as TSV and returns the copied text; returns null when no clipboard is available. Also wired to Ctrl+C / Cmd+C when focus is within the grid.
SelectionModeDataGridSelectionModeSingleRow selection: None (disabled), Single, or Multiple. A plain click (or Space/Enter on the focused row) selects, replacing any existing selection. In Multiple, Ctrl-click / Ctrl+Space toggles a row and Shift-click / Shift+Space selects a range from the anchor. A disabled grid is not selectable.
SelectedItemT?nullThe selected row (the primary/last-affected one in Multiple). Two-way friendly; assigning replaces any existing selection and raises SelectionChanged when the value changes.
SelectedItemsIReadOnlyList<T>emptyA snapshot of the selected rows in view order.
SelectionChangedevent Action<T?>—Raised when the selection changes; the argument is the primary selected item (or default when empty).
IsLoadingboolfalseShows a skeleton loading body (state precedence: Error > Loading > Empty > data).
ErrorText / ErrorContentstring? / Control?nullShows an error body instead of rows; ErrorContent overrides ErrorText.
OnRetryAction?nullWhen set, the error body shows a Retry button that invokes this.
SkeletonRowCountint6Number of skeleton rows in the loading state.
ShowFooterboolfalseRenders a footer row of per-column aggregates (over the current filtered rows, all pages), aligned to the column layout.
PageSizeint0Rows per page; 0 disables paging. Mirrors the reference API's RowsPerPage.
Pageint1Current 1-based page.
FilterTextstring?nullText passed to the filter pipeline before sorting/paging.
FilterFunc<T, string, bool>?nullCustom row predicate for FilterText; defaults to searching rendered cell values.
VirtualizeboolfalseLimits unpaged rendering to MaxRenderedRows.
MaxRenderedRowsint200Maximum rows rendered when Virtualize is enabled and paging is off.
StripedbooltrueAlternating row shading. Mirrors the reference API's Striped.
HoverbooltrueRow hover highlight. Mirrors the reference API's Hover.
DenseboolfalseCompact cell padding. Mirrors the reference API's Dense.
Elevationint1Host paper elevation. Mirrors the reference API's Elevation.
GroupByFunc<T, object?>?nullGroups rows by key with a group-header row (key + count) above each group, in first-appearance order (follows the current sort). Applies within the rendered page.
CollapsibleGroupsbooltrueWhen grouped, lets the user click (or keyboard-activate) a group header to collapse/expand its rows. Collapsed state is keyed by group key and survives re-renders.
GroupAggregateFunc<IReadOnlyList<T>, string>?nullOptional text appended to each group header, computed from the group's items (e.g. a sum or average).
EmptyTextstring"No data"Text shown below the header when there are no rows to display after filtering.
EmptyContentControl?nullCustom empty-state content; overrides EmptyText when set.
FrozenColumnsint0Number of leading columns to pin while the rest scroll horizontally. Ignored while grouped, or if not less than the column count. Frozen layouts size every column by pixel width.
RowHeightdouble0Fixed body-row height in px (0 = auto). Guarantees row alignment across the frozen/scrollable panes for custom-height cells.

DataGridColumn<T> properties ​

PropertyTypeDefaultDescription
Headerstring(required)Column header text.
ValueFunc<T, object?>(required)Projects a row to its cell value (used for display and sorting).
Formatstring?nullOptional .NET format string applied to the cell value (e.g. "N2").
SortablebooltrueWhether clicking the header sorts by this column.
AlignHorizontalAlignmentLeftCell content alignment.
Widthdouble?nullFixed pixel width; null sizes with star (shares remaining space). In a frozen-column layout, columns without a width get a default pixel width.
CellTemplateFunc<T, Control>?nullCustom cell content.
EditableboolfalseRenders a text editor for this column when SetText is provided.
SetTextAction<T, string?>?nullApplies edited text back to the row.
SummaryFunc<IReadOnlyList<T>, string>?nullCustom footer text for this column (shown when the grid's ShowFooter is on).
SummaryKindDataGridSummary?nullBuilt-in footer aggregate when Summary is null: Sum/Average/Min/Max over the column's numeric values, or Count. Honors Format.

The two-argument constructor — new DataGridColumn<T>(header, value) — is the only way to set the required Header and Value; everything else is an init property set in the object initializer.

DataGrids static helpers ​

MethodSignatureDescription
Sort<T>(IReadOnlyList<T> items, DataGridColumn<T>? column, bool descending) → IReadOnlyList<T>Sorts items by column.Value; returns original order when column is null.
PageCount(int count, int pageSize) → intTotal page count for count rows at pageSize (0 = 1 page).
Filter<T>(IReadOnlyList<T> items, string? text, Func<T, string, bool> predicate) → IReadOnlyList<T>Returns matching rows when text has content; otherwise returns the original rows.
Group<T>(IReadOnlyList<T> items, Func<T, object?> selector) → IReadOnlyList<DataGridGroup<T>>Groups rows by key in first-appearance order; a null key forms its own group.

Keyboard ​

Rows are focusable. When a row has focus:

KeyAction
↑ / ↓Move focus to the previous / next rendered row.
Home / EndMove focus to the first / last rendered row.
Space / EnterSelect the focused row (toggles it in Multiple).
Shift + ↑/↓/Home/EndExtend the selection to the focused row (Multiple).
Ctrl + ASelect every rendered row — the current page, expanded groups only (Multiple).
EscClear the selection.
Ctrl + C / Cmd + CCopy the selection (or the whole view) as TSV.

In Single mode the selection follows focus as you move; navigation stays within the current page and never wraps.

csharp
class Employee
{
    public string Name { get; set; } = "";
    public string Department { get; set; } = "";
    public decimal Salary { get; set; }
}

var grid = new DataGrid<Employee>
{
    Striped   = true,
    Hover     = true,
    PageSize  = 20,
    FilterText = searchText,
    Filter = (employee, text) =>
        employee.Name.Contains(text, StringComparison.OrdinalIgnoreCase) ||
        employee.Department.Contains(text, StringComparison.OrdinalIgnoreCase),
    Elevation = 1,
};

grid.Columns.Add(new DataGridColumn<Employee>("Name", e => e.Name)
{
    Editable = true,
    SetText = (employee, text) => employee.Name = text ?? "",
});
grid.Columns.Add(new DataGridColumn<Employee>("Department", e => e.Department));
grid.Columns.Add(new DataGridColumn<Employee>("Salary",     e => e.Salary)
{
    Format  = "C2",
    Align   = HorizontalAlignment.Right,
});

grid.SelectionChanged += emp => Console.WriteLine($"Selected: {emp?.Name}");

grid.Items = employees; // IEnumerable<Employee>
Loading, empty, and error states

The grid renders a single body state at a time, in the precedence Error > Loading > Empty > data. Drive them from your view model rather than swapping the grid out:

csharp
grid.IsLoading = true;                 // skeleton rows while fetching
// …on failure:
grid.IsLoading = false;
grid.ErrorText = "Couldn't load employees.";
grid.OnRetry   = () => ViewModel.Reload();   // shows a Retry button
// …on success with no rows:
grid.ErrorText = null;
grid.EmptyText = "No employees match your filter.";

Loam.Controls.TreeView / Loam.Controls.TreeViewItem ​

A hierarchical tree that mirrors the reference API's TreeView / TreeViewItem. Root nodes are added to TreeView.Items; each TreeViewItem may have its own Items collection for nested children. Clicking a node selects it and updates TreeView.SelectedItem. Nodes with children show a chevron that toggles Expanded. Tree rows are focusable; Enter selects a row and Space toggles expandable rows.

Use it when the data is genuinely nested — a file system, a category hierarchy, an org chart. For a flat menu, prefer List; for flat-but-groupable rows, prefer DataGrid<T> with GroupBy.

Loam.Controls.TreeView properties ​

PropertyTypeDefaultDescription
ItemsObservableCollection<Loam.Controls.TreeViewItem>emptyRoot nodes.
SelectedItemLoam.Controls.TreeViewItem?nullThe selected node (two-way).

Loam.Controls.TreeViewItem properties ​

Property / MemberTypeDefaultDescription
Textstring?nullNode label. Mirrors the reference API's Text.
Iconstring?nullLeading icon path. Mirrors the reference API's Icon.
ExpandedboolfalseWhether children are shown (two-way).
IsSelectedboolfalseWhether this node is the selected node.
ItemsObservableCollection<Loam.Controls.TreeViewItem>emptyChild nodes.
ItemSelectedevent EventHandler<RoutedEventArgs>—Raised (bubbling) when this node's row is clicked.
csharp
var tree = new Loam.Controls.TreeView();

var parent = new Loam.Controls.TreeViewItem
{
    Text     = "Documents",
    Icon     = Icons.Material.Filled.Article,
    Expanded = true,
};
parent.Items.Add(new Loam.Controls.TreeViewItem { Text = "Report.pdf",  Icon = Icons.Material.Filled.Article });
parent.Items.Add(new Loam.Controls.TreeViewItem { Text = "Budget.xlsx", Icon = Icons.Material.Filled.Table });

tree.Items.Add(parent);
tree.Items.Add(new Loam.Controls.TreeViewItem { Text = "Downloads", Icon = Icons.Material.Filled.CloudUpload });

Tabs ​

A tab strip with switchable content, mirroring the reference API's Tabs. Add TabItem instances to Items; the active header is underlined in the accent Color.

Use it when several views share one screen region and the user looks at one at a time — Overview / Analytics / Settings. Tabs switch content in place; they are not navigation between top-level destinations (use NavMenu for that) and not a linear sequence (use Stepper).

TabItem properties ​

PropertyTypeDefaultDescription
Headerstring?nullTab header text. Mirrors the reference API's TabPanel label.
ContentControl?nullContent shown when the tab is selected.

Tabs properties ​

PropertyTypeDefaultDescription
ItemsObservableCollection<TabItem>emptyThe tabs.
SelectedIndexint0Index of the active tab.
ColorLoamColorLoamColor.PrimaryUnderline accent color for the active tab. Mirrors the reference API's Color.
csharp
var tabs = new Tabs
{
    Color         = LoamColor.Primary,
    SelectedIndex = 0,
};

tabs.Items.Add(new TabItem("Overview",  new Text { Text = "Overview content" }));
tabs.Items.Add(new TabItem("Analytics", new Text { Text = "Analytics content" }));
tabs.Items.Add(new TabItem("Settings",  new Text { Text = "Settings content" }));

ExpansionPanels / ExpansionPanel ​

A stacked accordion of collapsible sections, mirroring the reference API's ExpansionPanels / ExpansionPanel. By default, expanding one panel collapses the others (accordion mode); set MultiExpansion to allow several open at once. Headers are focusable, expose an automation name from Header, and toggle with Enter or Space.

Use it when the page has more sections than fit comfortably and each is optional — a FAQ, grouped settings, an order's collapsible detail blocks. If switching is exclusive and the content is peer views (not optional detail), prefer Tabs.

ExpansionPanel properties ​

ExpansionPanel extends HeaderedContentControl.

PropertyTypeDefaultDescription
Headerobject?nullPanel header (inherited from HeaderedContentControl).
Contentobject?nullRevealed body content (inherited from ContentControl).
IsExpandedboolfalseWhether the panel is open (two-way). Mirrors the reference API's IsExpanded.

ExpansionPanels properties ​

PropertyTypeDefaultDescription
PanelsObservableCollection<ExpansionPanel>emptyThe contained panels.
MultiExpansionboolfalseAllow multiple panels open simultaneously. Mirrors the reference API's MultiExpansion.
csharp
var accordion = new ExpansionPanels { MultiExpansion = false };

accordion.Panels.Add(new ExpansionPanel
{
    Header    = "Shipping",
    Content   = new Text { Text = "Ships within 2–3 business days." },
    IsExpanded = true,
});
accordion.Panels.Add(new ExpansionPanel
{
    Header  = "Returns",
    Content = new Text { Text = "Free returns within 30 days." },
});
accordion.Panels.Add(new ExpansionPanel
{
    Header  = "Warranty",
    Content = new Text { Text = "12-month manufacturer warranty." },
});

Accordion vs. multi-expand

With MultiExpansion = false (the default), setting IsExpanded = true on more than one panel up front is contradictory — opening any panel will close the others as soon as the user interacts. Set MultiExpansion = true when you genuinely want several sections open at once.


Timeline ​

A timeline that renders TimelineItem entries down (or across) a connector line, each with a colored dot beside a Paper content card. Mirrors the reference API's Timeline / TimelineItem. Implemented as a Decorator (no ControlTheme required).

Use it when the data is an ordered sequence of events you want to show — order history, an audit trail, a changelog. It is read-only chronology; for a sequence the user advances through, use Stepper.

TimelineItem properties ​

Property / MemberTypeDefaultDescription
Contentobject?nullEntry content (string or any Control). When set, it wins over the generated layout below.
ColorLoamColorLoamColor.PrimaryDot color. Mirrors the reference API's Color.
Titlestring?nullUsed by the generated card layout when Content is empty.
Subtitlestring?nullSupporting text in the generated layout.
TimeTextstring?nullOptional time/metadata line rendered above the title in the generated layout.
TimelineItem(object? content, LoamColor color = Primary)constructor—Content + dot color.
TimelineItem(string title, string? subtitle, string? timeText = null, LoamColor color = Primary)constructor—Generated title/subtitle/time layout.

Timeline properties ​

PropertyTypeDefaultDescription
ItemsObservableCollection<TimelineItem>emptyEntries displayed in order.
OrientationOrientationVerticalLay entries top-to-bottom (Vertical) or left-to-right (Horizontal).
csharp
var timeline = new Timeline();

timeline.Items.Add(new TimelineItem("Order placed",    LoamColor.Primary));
timeline.Items.Add(new TimelineItem("Payment confirmed", LoamColor.Success));
timeline.Items.Add(new TimelineItem("Dispatched",      LoamColor.Info));
timeline.Items.Add(new TimelineItem("Out for delivery", LoamColor.Warning));

Use the title/subtitle/time constructor for a richer entry without building a control yourself:

csharp
using Avalonia.Layout;

var history = new Timeline { Orientation = Orientation.Vertical };
history.Items.Add(new TimelineItem("Order placed", "Confirmation emailed", "09:14", LoamColor.Primary));
history.Items.Add(new TimelineItem("Dispatched",   "Left the warehouse",   "13:02", LoamColor.Info));

A slideshow that displays one CarouselItem at a time with optional prev/next arrows and clickable bullet indicators. Mirrors the reference API's Carousel / CarouselItem. Navigation wraps around.

Use it when you have a small set of equally important, rotating items — a featured-content banner, an onboarding tour, an image gallery. For long lists the user scans linearly, a List is clearer; for paging through records, use Pagination.

CarouselItem properties ​

Property / MemberTypeDefaultDescription
Contentobject?nullSlide content (string or any Control). When set, it wins over the generated layout.
Titlestring?nullTitle used by the generated slide layout when Content is empty.
Subtitlestring?nullSupporting text in the generated slide layout.
ColorLoamColorLoamColor.PrimarySemantic color for the generated slide surface.
CarouselItem(object? content)constructor—Slide from any content.
CarouselItem(string title, string? subtitle, LoamColor color = Primary)constructor—Generated title/subtitle slide.
Property / MemberTypeDefaultDescription
ItemsObservableCollection<CarouselItem>emptyThe slides.
SelectedIndexint0Visible slide index (two-way).
ShowArrowsbooltrueWhether prev/next arrow buttons are shown. Mirrors the reference API's ShowArrows.
ShowBulletsbooltrueWhether bullet indicators are shown. Mirrors the reference API's ShowBullets.
AutoPlayboolfalseAdvances automatically while attached, enabled, and showing at least two slides.
AutoPlayIntervalTimeSpan4sHow often AutoPlay advances.
SelectedIndexChangedevent EventHandler<int>—Raised after the visible slide changes.
Next() / Previous()void—Advance / go back one slide, wrapping around.
GoTo(int index)void—Jump to a slide, clamped to the available range.
csharp
var carousel = new Loam.Controls.Carousel
{
    ShowArrows  = true,
    ShowBullets = true,
};

carousel.Items.Add(new CarouselItem(new Image { Source = new Bitmap("slide1.png") }));
carousel.Items.Add(new CarouselItem(new Image { Source = new Bitmap("slide2.png") }));
carousel.Items.Add(new CarouselItem(new Text  { Text = "Coming soon" }));

Auto-play, accessibly

AutoPlay is off by default — and that's usually right. Motion that the user can't pause is a real accessibility problem. If you enable it, keep ShowArrows/ShowBullets on so there's always a manual control, and lean on a calm AutoPlayInterval (the 4-second default) rather than a snappy one.


Pagination ​

A page navigator that renders boundary pages, a configurable window of pages around the selection, ellipsis gaps, and prev/next arrows. Mirrors the reference API's Pagination. Also used internally by DataGrid<T> when PageSize > 0.

Use it when you page through an external or manual data source (server-side paging, a non-grid layout) and want a standalone pager. Inside a DataGrid<T>, set PageSize instead — the grid builds and wires its own pager for you.

Pagination properties ​

PropertyTypeDefaultDescription
Countint1Total number of pages. Mirrors the reference API's Count.
Selectedint1Current 1-based page (two-way). Mirrors the reference API's Selected.
ColorLoamColorLoamColor.PrimarySelected page button color. Mirrors the reference API's Color.
BoundaryCountint1Pages shown at each end. Mirrors the reference API's BoundaryCount.
MiddleCountint3Pages shown around the selection. Mirrors the reference API's MiddleCount.
ShowFirstLastboolfalseAdds first-page and last-page boundary buttons flanking the arrows.
ShowRangeboolfalseWith PageSize/TotalItems, shows a "Showing X–Y of N" summary.
PageSize / TotalItemsint0Rows per page and total item count, used for the range summary.

DataGrid<T>'s built-in pager enables ShowFirstLast and ShowRange automatically.

Static helper ​

MethodSignatureDescription
BuildPages(int count, int selected, int boundary, int middle) → IReadOnlyList<int>Returns the page layout as a list of 1-based page numbers; 0 marks an ellipsis gap.
csharp
var pager = new Pagination
{
    Count         = 20,
    Selected      = 1,
    Color         = LoamColor.Primary,
    BoundaryCount = 1,
    MiddleCount   = 3,
};

pager.GetObservable(Pagination.SelectedProperty).Subscribe(page =>
{
    Console.WriteLine($"Navigated to page {page}");
});

Stepper ​

A linear step wizard that displays numbered Step entries with connector lines, the active step's content, and Back / Next (Finish) navigation. Mirrors the reference API's Stepper / Step. Advancing through the last step marks it complete and fires OnCompleted.

Use it when a task is genuinely sequential and the order matters — checkout, account setup, a multi-page form. The Next button reads "Finish" on the last step. For peer views the user can visit in any order, use Tabs; for showing (not advancing) a sequence, use Timeline.

Step properties ​

PropertyTypeDefaultDescription
Titlestring?nullStep header title.
Contentobject?nullStep body content (string or any Control).
CompletedboolfalseWhether the step is marked complete (set automatically by Next()).

Stepper properties ​

Property / MemberTypeDefaultDescription
StepsObservableCollection<Step>emptyThe wizard steps.
ActiveIndexint0The active step index (two-way).
OnCompletedAction?nullInvoked when the final step is finished.
Next()void—Advances to the next step, marking the current one complete; calls OnCompleted on the last step.
Previous()void—Returns to the previous step.
csharp
var stepper = new Stepper
{
    OnCompleted = () => Console.WriteLine("Wizard finished"),
};

stepper.Steps.Add(new Step("Account",  new Text { Text = "Enter your email and password." }));
stepper.Steps.Add(new Step("Profile",  new Text { Text = "Tell us about yourself." }));
stepper.Steps.Add(new Step("Confirm",  new Text { Text = "Review and submit." }));

Recipe: a paged list with a header tab ​

A small dashboard slice — Tabs switching views, a List of rows, and a standalone Pagination below it driving which page of rows is shown. Everything is plain C#; lay it out with a StackPanel (see Surfaces & layout).

csharp
using Avalonia.Controls;
using Avalonia.Layout;
using Loam;
using Loam.Controls;

string[] allItems = Enumerable.Range(1, 42).Select(i => $"Message {i}").ToArray();
const int pageSize = 10;

var list = new List();
var pager = new Pagination
{
    Count      = DataGrids.PageCount(allItems.Length, pageSize),
    Selected   = 1,
    ShowRange  = true,
    PageSize   = pageSize,
    TotalItems = allItems.Length,
};

void ShowPage(int page)
{
    list.Children.Clear();
    foreach (var text in allItems.Skip((page - 1) * pageSize).Take(pageSize))
    {
        list.Children.Add(new ListItem
        {
            Icon    = Icons.Material.Filled.Article,
            Content = new Text { Text = text },
        });
    }
}

pager.GetObservable(Pagination.SelectedProperty).Subscribe(ShowPage);
ShowPage(1);

var inbox = new StackPanel { Spacing = 8, Children = { list, pager } };

var tabs = new Tabs { Color = LoamColor.Primary };
tabs.Items.Add(new TabItem("Inbox",   inbox));
tabs.Items.Add(new TabItem("Archive", new Text { Text = "Nothing archived yet." }));

Accessibility & keyboard ​

Every interactive control on this page is keyboard-operable and carries an automation name out of the box. Across the family, Enter and Space are the activation keys, disabled controls drop out of the tab order, and arrow keys navigate within a control once it has focus.

  • List / ListItem — rows are focusable; Tab moves between them and Enter/Space raise Activated. SecondaryText is folded into the row's announced name.
  • Tabs — headers are focusable; Enter/Space select the focused header, ←/↓ move to the previous tab and →/↑ to the next. The strip announces "Tab N of M".
  • ExpansionPanel — headers are focusable and announce "Expanded"/"Collapsed"; Enter/Space toggle the panel.
  • TreeView — nodes are focusable; Enter selects, Space toggles an expandable node (or selects a leaf), → expands or steps into children, ← collapses or steps to the parent, and ↑/↓ move through the visible enabled nodes.
  • Carousel — when the carousel has focus, ←/→ move to the previous/next slide; the arrows and bullets are individually focusable and activate with Enter/Space. Each bullet announces "Slide N" and "Selected"/"Not selected".
  • Pagination — the arrows and page numbers are buttons: Tab to a page, Enter/Space to go there. The selected page announces "Page N, selected".
  • DataGrid<T> — see the dedicated Keyboard table above.
  • Stepper — drive the wizard with the focusable Back / Next (named "Previous step" / "Next step", "Finish steps" on the last step) buttons; the numbered markers are decorative and not focusable.

Name your icon-only rows

A ListItem whose Content is just an icon, or a TreeViewItem with only an Icon, has no text for assistive technology to read. Give it readable text — set the node's Text, the row's Content/SecondaryText, or an explicit automation name:

csharp
using Avalonia.Automation;

var row = new ListItem { Icon = Icons.Material.Filled.Star };
AutomationProperties.SetName(row, "Pinned");

See also ​

  • Display primitives — Text, Icon, Chip, and the glyph set behind every Icon property here.
  • Buttons & menus — the Button/IconButton family that powers Pagination, Stepper, and Carousel chrome.
  • Form inputs — pair a TextField with DataGrid<T>.FilterText, or host a Form inside a Stepper step.
  • Navigation — for moving between top-level destinations rather than switching views with Tabs.
  • Surfaces & layout — Paper, StackPanel, and the layout pieces these recipes compose.
  • Theming — how Color, Size, and Variant resolve to tokens.
  • v3 → v3.1 migration guide — moving the Loam.Data controls into the satellite package.

MIT Licensed · Independent Avalonia controls.