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.
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:
- Only
http,https,mailtoandtel, or a relative address. Any other scheme is refused:javascript:,vbscript:,data:andfile:. A refused link is drawn as plain text, never as a link and never rewritten into a different address. - An external address, or one that opens in another tab, carries
rel="noopener noreferrer". The page it opens gets no handle on the portal's window and no referrer. - Both rules read the address the way a browser's URL parser does (
LinkHrefs.Normalize, shared by every rule). Tabs and line breaks are removed everywhere, leading and trailing control characters and spaces are trimmed, and\counts as/. Sojavafollowed by a tab andscript:is refused, and/+ tab +/example.com,\\example.com,/\example.comand\/example.comall count as another site, exactly as a browser resolves them.
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)")}"))
Which link control?
| 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
- Badges, Icons & Status: badges, icons, progress bars
- Container Controls: stacks, tabs, toolbars, splitters
- Data Binding: binding any value to a JSON pointer