Message Bars & Links

Two small controls that hand-built HTML used to stand in for. A banner is a MessageBarControl, never a Controls.Html("<div style=…>") box. A link in running text is a LinkControl, never Controls.Html("<a href=…>"). Both work the same in every renderer (Blazor, React, React Native), in both themes and with assistive technology. Every value is a literal or a data-binding pointer.


Message Bar

Controls.MessageBar(body) draws a banner that tells the reader something about the page: it is read-only, an account is not connected, a save worked, a sync failed. WithIntent takes one of the MessageBarIntent constants, Info (the default), Success, Warning or Error. WithTitle adds a bold lead-in.

Controls.Stack
    .WithVerticalGap("8px")
    .WithView(Controls.MessageBar("This node is read-only for you.").WithTitle("Read-only"))
    .WithView(Controls.MessageBar("Pushed 3 files to main.").WithIntent(MessageBarIntent.Success))
    .WithView(Controls.MessageBar("The repository has no default branch yet.").WithIntent(MessageBarIntent.Warning))
    .WithView(Controls.MessageBar("The token was refused by GitHub.")
        .WithIntent(MessageBarIntent.Error)
        .WithTitle("Sync failed"))

Accessibility. The renderer gives the bar a live-region role that follows its intent: role="alert" for Warning and Error, which a screen reader reads at once, and role="status" for everything else, which it reads when it is free. MessageBarIntent.AriaRoleOf(intent) is that rule, and every renderer calls it or mirrors it. So choose the intent by how urgent the message is, not by the colour you want.

An unknown intent. MessageBarIntent is an open vocabulary. A renderer that meets a value it does not know draws the neutral Info styling with role="status", and names the value in its log. It never makes an unknown value look like a warning or an error.

An action

WithAction(control) puts one control beside the message. Use a Button with its own click action or WithNavigateToHref, or a Link. The action renders as the bar's own child area, {area}/Action, so its click reaches its own handler exactly as it would anywhere else on the page, with the button's pending state included (Buttons: Pending State).

Controls.MessageBar("Connect your GitHub account to sync this space.")
    .WithIntent(MessageBarIntent.Warning)
    .WithTitle("Not connected")
    .WithAction(Controls.Button("Connect").WithNavigateToHref("/auth/github/connect"))

The body is plain text. When the message itself needs a link or emphasis, give it an action, or put a Markdown control next to the bar. Never put markup in the body.

The outcome of a click: ActionNotice

A click that reports back ("Sending invitation…", "Draft created.", "Couldn't connect: …") writes an ActionNotice into a data id, and the page renders that slot as a message bar. The notice is DATA: an intent and a text, never a pre-rendered HTML string. ActionNotice.Info, Success, Warning and Error build one; host.ShowNotice(id, notice) writes it; host.NoticeView(id) is the slot's view, empty until the first notice arrives.

const string ResultId = "inviteResult";

stack = stack
    .WithView(Controls.Button("Invite").WithClickAction(ctx =>
    {
        ctx.Host.ShowNotice(ResultId, ActionNotice.Info("Sending invitation…"));
        invitations.Send(email).Subscribe(
            _ => ctx.Host.ShowNotice(ResultId, ActionNotice.Success($"Invitation sent to {email}.")),
            ex => ctx.Host.ShowNotice(ResultId, ActionNotice.Error(ex.Message)));
        return Task.CompletedTask;
    }))
    .WithView((h, _) => h.NoticeView(ResultId));

Use "Info" for work in progress. Choose the intent by what happened, because it also sets the bar's live-region role.


Hint

Controls.Hint(text) is the muted line under a heading, an empty state ("No additional sync sources."), or a loading placeholder. It is a Body label in the theme's hint colour (Controls.HintColor, var(--neutral-foreground-hint)), so it follows light and dark mode. It replaces Controls.Html("<p style=\"color: var(--neutral-foreground-hint)\">…</p>").

Controls.Stack
    .WithVerticalGap("4px")
    .WithView(Controls.H3("Additional sync sources"))
    .WithView(Controls.Hint("Sync this Space with more repositories, each with its own branch and direction."))
    .WithView(Controls.Hint("No additional sync sources."))

When the line needs a link or code formatting, use Controls.Markdown instead.


Link

Controls.Link(text, href) is an inline text link for body content: "open the pull request", "download the report", "read the guide". Put it in a horizontal Stack next to labels, or use it as a message bar's action.

Controls.Stack
    .WithOrientation(Orientation.Horizontal)
    .WithHorizontalGap("16px")
    .WithWrap(true)
    .WithView(Controls.Link("Data binding", "/Doc/GUI/DataBinding"))
    .WithView(Controls.Link("Fluent UI", "https://www.fluentui-blazor.net/").WithOpenInNewTab())
    .WithView(Controls.Link("Download the report", "/api/content/ACME/Reports/sample.csv").WithDownload("sample.csv"))
    .WithView(Controls.Link("Write to support", "mailto:support@example.com").WithTooltip("Opens your mail app"))
Builder What it does
WithOpenInNewTab() / WithTarget("_blank") Opens in a new browser tab.
WithDownload(true) / WithDownload("name.pdf") Downloads instead of navigating, under the server's file name or the one given.
WithTooltip(text) Hover text, rendered as the element's title. Pass a localized string.

Write a mesh path as an absolute path (/Doc/GUI). Never write @/… in an href: that form is for markdown links only.

A file that belongs to a node is downloaded through /api/content/{node}/{file}, which checks that the reader may read the owning node. Never point a link at /static/… for mesh content: /static serves only the build's own assets and checks no permission at all.

Href safety

The renderer applies the rules in LinkHrefs, not the code that builds the control, so a bound href is held to the same rules as a literal one:

Controls.Stack
    .WithView(Controls.Label($"https://example.com → safe: {LinkHrefs.IsSafe("https://example.com")}, rel: {LinkHrefs.RelFor("https://example.com", null)}"))
    .WithView(Controls.Label($"/Doc/GUI → safe: {LinkHrefs.IsSafe("/Doc/GUI")}, rel: {LinkHrefs.RelFor("/Doc/GUI", null) ?? "(none)"}"))
    .WithView(Controls.Label($"javascript:alert(1) → safe: {LinkHrefs.IsSafe("javascript:alert(1)")}"))
You want Use
A link in running text Controls.Link
An entry in a side menu or navigation Controls.NavLink (Navigation Menus)
An action that looks like a button Controls.Button(...).WithNavigateToHref(...)
Links inside prose you are writing anyway Controls.Markdown

See Also

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.