Views
A view is a UIViewBase subclass that builds a component tree — the framework compiles it once and renders
it to the platform. Every view also implements IUIViewDefinition, whose static ViewKey is the stable
name route registration and view lookup use.
internal sealed class CounterView : UIViewBase, IUIViewDefinition
{
public static string ViewKey => "counter";
protected override IVisualComponent CreateContent()
=> new ContainerComponent()
.AddChild(new TextComponent().BindTitle(nameof(CounterController.Count)));
}
Regions
A view builds up to five regions by overriding the corresponding Create* method — all protected virtual
except CreateContent, which is abstract and required:
CreateHeader()CreateFooter()CreateLeftSide()CreateRightSide()CreateContent()— the only region every view must supply.
Each returns an IVisualComponent? (null for a region the view does not use) except CreateContent,
which returns one. Regions are built lazily, once, and cached.
Title and options
Title (defaults to the type name) and Options (UIViewOptions.Default unless overridden) are virtual
properties a view overrides directly:
public override string Title => "Dashboard";
public override UIViewOptions Options => new()
{
StickyHeader = true,
ScrollContentOnly = true,
NotificationPlacement = UINotificationPlacement.Top
};
UIViewOptions says what the view wants from its own shell, not from any component in it:
| Property | Effect |
|---|---|
StickyHeader |
The header region stays at the top of the viewport while the content scrolls under it. |
ScrollContentOnly |
The page keeps the viewport's height; header and sides stand still and only the content region scrolls. |
NotificationPlacement |
Which corner this view's toasts stack in — Bottom (default) or Top. |
Dialogs
CreateDialogs() declares the view's dialogs, rendered closed by the shell and opened by key from a
command (see interactions and effects for the effect that opens one). Each
UIDialog names a key, its content, and how it presents:
protected override IReadOnlyList<UIDialog> CreateDialogs()
=> [
new UIDialog
{
Key = "confirm-delete",
CloseOnBackdrop = false,
CloseOnEscape = false,
Content = new ContainerComponent()
.AddChild(new TextComponent().SetTitle("Delete this build?"))
}
];
UIDialog properties: Key and Content are required; Surface (UISurfaceStyle, default Raised) is
what the panel is made of; Placement (UIDialogPlacement: Center, Left, Right, Top, Bottom,
default Center) is where it stands — centred or as a sheet against one edge; Modal (default true)
blocks interaction with the view underneath; CloseOnBackdrop and CloseOnEscape (both default true)
are the two dismissals a destructive confirmation usually turns off.
The placement grid
Every visual component has a Placement — its cell(s) in a 24-column grid (UIGridPlacement.GridColumns).
SetPlacement is the fluent entry point:
new TextComponent()
.SetPlacement(1, 1, 24, 1)
public TComponent SetPlacement(int column, int row, int columnSpan = 1, int rowSpan = 1,
UIGridPlacement? sm = null, UIGridPlacement? md = null, UIGridPlacement? xl = null, UIGridPlacement? xxl = null)
column/row are the starting cell, columnSpan/rowSpan how many cells the component occupies. The four
optional arguments after them override the placement per breakpoint — sm, md, xl, xxl, built with
UIGridPlacement.At(column, row, columnSpan, rowSpan).
A child of a container with no placement of its own spans all twenty-four columns — one to a row. The two
wrapping layouts read the span differently: in a WrapPanel, and in an ItemsView whose layout is Wrap,
a child with no placement takes its content's width and the line wraps where the room ends, and a placement
makes it that share of the width instead — a template root with .SetPlacement(1, 1, 6, 1) is four items to
a line.
This is Placement's own type, UIResponsive<UIGridPlacement>, and the same shape backs every responsive
property on a component (Width, Margin, and so on): a required Base value plus optional per-breakpoint
overrides (Sm, Md, Xl, Xxl). An unset breakpoint falls back to the next narrower one that is set,
down to Base — mobile-first. UIResponsive<T>.Create(value, sm, md, xl, xxl) builds one explicitly, and a
bare T converts to it implicitly (FromValue).
Component ids
A component's Id is either the string passed to its constructor or a generated u<N> from a process-wide
counter. HasAuthoredId says which:
new StackPanelComponent("sidebar")
An authored id is what the client's small per-viewer preferences (a collapsed menu, a split panel's tracks)
key off in localStorage; a generated id means nothing between two runs, so a component with no authored id
stores nothing. Give one to anything the reader would expect to remember its own state across a reload.
The fluent style
Every component method returns the component itself (TComponent, the generic self-type), so calls chain:
new ContainerComponent()
.SetPadding(new UIThickness(8))
.AddChild(new TextComponent()
.SetTitle("Hi")
.SetPlacement(1, 1, 24, 1)
)
.SetPlacement(1, 2, 24, 1)
The naming is consistent across every component:
Set*assigns an authored value — a plain CLR value the view controls once, at compile time.Bind*(generated per bindable property) or the rawBind(property, path, scope, mode)ties a property to a controller path instead — see binding.Configure(Action<TComponent>)runs arbitrary code against the component inline, for the rare case a fluent method does not cover.AddChild(IVisualComponent)/AddChildren(...)append children to a container component (ContainerComponentBase<T>and its descendants).
Context menu
SetContextMenu(IVisualComponent contextMenu) sets what opens on a right-click, normally a
MenuComponent; ShowContextMenu (bindable, default true) switches the mechanic off without removing the
authored menu.
Visibility
Visibility is UIResponsive<UIVisibility>?, and UIVisibility has three values:
| Value | Effect |
|---|---|
Visible |
Drawn, and taking part in layout, hit-testing, focus and assistive technology. |
Hidden |
Not drawn, but still holding its place — the layout around it does not move. |
Collapsed |
Gone from the layout as well: everything after it closes the gap. |
Theme
Theme (UIThemeMode?) forces a component's subtree into Light or Dark regardless of the session's own
theme; null (the default) inherits. This is a per-component override — the session-wide setting is a
separate mechanic (SetThemeEffect, see interactions and effects).
Inline text markup
A description or a tooltip may carry a small inline markup, parsed by UIInlineMarkup (parity-tested
against the client's own parser):
| Syntax | Produces |
|---|---|
**bold** |
Bold |
*italic* |
Italic |
__underline__ |
Underline |
~~strikethrough~~ |
Strikethrough |
`code` |
Inline code |
[label](url) |
A link — url must pass UIInlineMarkup.IsSafeUrl (relative, #, ?, ., or http/https/mailto/tel) |
![glyph] |
An icon, named by glyph name only — never a URL |
[caption]{text} |
A fold: a caption with collapsible text, which may itself contain markup |
A marker is escaped with a backslash (\*) to appear literally; UIInlineMarkup.Escape(text) escapes every
marker in a value before it is substituted into a localized string. UIInlineMarkup.ToPlainText(text) strips
the markup for a place that takes plain text only, such as an aria-label.
Where to go next
- Components for the full built-in catalogue, starting with Container.
- Controllers for the observable state a view binds to.
- Binding for paths, scopes and modes.