Toolbar
Since 0.1.0-alpha.13An interactive page: C# runs over a Blazor Server connection.
Usage
<Toolbar Label="Editor">
<ToolbarButtonGroup Label="History">
<ToolbarIconButton Icon="Lucide.Undo2" Label="Undo" />
<ToolbarIconButton Icon="Lucide.Redo2" Label="Redo" />
</ToolbarButtonGroup>
<ToolbarSeparator />
<ToolbarButton StartIcon="Lucide.Link" Priority="1">Link</ToolbarButton>
@* The main action, at the end, never moves into the … menu; Print is only ever there. *@
<ToolbarButton Position="ToolbarPosition.End" Overflow="ToolbarOverflow.Never" Accent="Accent.Primary" Variant="ButtonVariant.Solid">Publish</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Printer" Overflow="ToolbarOverflow.Always">Print</ToolbarButton>
</Toolbar>A Toolbar stays one row: what doesn't fit moves into a … menu at the end, where each control becomes a menu item with the same link, form post or OnClick. It works with JavaScript off, so a phone with a slow connection gets the same toolbar.
An editor toolbar
Put ToolbarButtons, ToolbarIconButtons, ToolbarButtonGroups and ToolbarMenus in it, the toolbar's own Button, IconButton, ButtonGroup and Menu with the same parameters, and narrow the window: they move into the … menu, a group as one. Other controls throw there: put them in a ToolbarItem.
Preview
Release notes
Start writing, or paste the notes from the changelog.
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody Gap="4">
<Toolbar Label="Editor">
<ToolbarButtonGroup Label="History">
<ToolbarIconButton Icon="Lucide.Undo2" Label="Undo" />
<ToolbarIconButton Icon="Lucide.Redo2" Label="Redo" />
</ToolbarButtonGroup>
<ToolbarButtonGroup Label="Text style" Variant="ButtonVariant.Outline" Priority="1">
<ToolbarIconButton Icon="Lucide.Bold" Label="Bold" />
<ToolbarIconButton Icon="Lucide.Italic" Label="Italic" />
<ToolbarIconButton Icon="Lucide.Underline" Label="Underline" />
</ToolbarButtonGroup>
<ToolbarSeparator />
<ToolbarButton StartIcon="Lucide.Link">Link</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Image">Image</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Eye" Position="ToolbarPosition.End">Preview</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Send" Accent="Accent.Primary" Variant="ButtonVariant.Solid" Position="ToolbarPosition.End" Overflow="ToolbarOverflow.Never">Publish</ToolbarButton>
</Toolbar>
<Divider />
<Stack Gap="2">
<Heading Level="2" Size="TextSize.Xl">Release notes</Heading>
<TextBlock Color="Color.Muted">Start writing, or paste the notes from the changelog.</TextBlock>
</Stack>
</CardBody>
</Card>Its buttons are Ghost, Neutral and small unless they set their own Variant, Accent and Size, so one solid button stands out.
What moves first
Use Priority on an item (higher stays longer) and Overflow: Never for the main action, Always for what only the menu needs.
Preview
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody>
<Toolbar Label="File">
<ToolbarButton StartIcon="Lucide.Upload" Accent="Accent.Primary" Variant="ButtonVariant.Solid" Overflow="ToolbarOverflow.Never">Upload</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Share2" Priority="1">Share</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Pencil">Rename</ToolbarButton>
<ToolbarButton StartIcon="Lucide.FolderInput">Move</ToolbarButton>
<ToolbarSeparator />
<ToolbarButton StartIcon="Lucide.Printer" Overflow="ToolbarOverflow.Always">Print</ToolbarButton>
<ToolbarButton StartIcon="Lucide.FileClock" Overflow="ToolbarOverflow.Always">Version history</ToolbarButton>
</Toolbar>
</CardBody>
</Card>With the same priority, controls move from the middle of the toolbar outward: the start from its end, then the end from its start, so the main action at the far end goes last. A ToolbarSeparator goes when there's nothing left on one side of it.
Start, center and end
Use Position on an item to put it in the middle or at the end; items are at the start by default. ToolbarText is text that stays, such as a document's name.
Preview
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody Gap="4">
<Toolbar Label="Document">
<ToolbarIconButton Icon="Lucide.ArrowLeft" Label="Back to files" Href="docs/components/toolbar" />
<ToolbarText Position="ToolbarPosition.Center" Width="Length.Rem(9)">Q3 report.pdf</ToolbarText>
<ToolbarButtonGroup Label="Zoom" Position="ToolbarPosition.End">
<ToolbarIconButton Icon="Lucide.ZoomOut" Label="Zoom out" />
<ToolbarIconButton Icon="Lucide.ZoomIn" Label="Zoom in" />
</ToolbarButtonGroup>
<ToolbarIconButton Icon="Lucide.Printer" Label="Print" Position="ToolbarPosition.End" />
<ToolbarIconButton Icon="Lucide.Download" Label="Download" Position="ToolbarPosition.End" />
</Toolbar>
<AspectRatio Ratio="5.0" Bg="Color.Fill" Radius="Radius.Md" />
</CardBody>
</Card>The center stays centered, so the start and the end each get half of the room around it, and the wider of the two gives up its controls first.
Posting a form
Use Submit, Name and Value on its buttons as on a Button: in the … menu they post the same, with JavaScript off too. Put the form around the toolbar.
Preview
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody Gap="3">
<form method="post" @formname="toolbar-bulk-actions" @onsubmit="Apply" data-enhance>
<AntiforgeryToken />
<Toolbar Label="Selected messages">
<ToolbarText>3 selected</ToolbarText>
<ToolbarButton Submit Name="intent" Value="archive" StartIcon="Lucide.Archive" OnClick='() => _pressed = "archive"'>Archive</ToolbarButton>
<ToolbarButton Submit Name="intent" Value="read" StartIcon="Lucide.MailOpen" OnClick='() => _pressed = "read"'>Mark read</ToolbarButton>
<ToolbarButton Submit Name="intent" Value="delete" StartIcon="Lucide.Trash" Accent="Accent.Danger" OnClick='() => _pressed = "delete"'>Delete</ToolbarButton>
</Toolbar>
</form>
if (_result is not null)
{
<Alert Status="Status.Success">_result</Alert>
}
</CardBody>
</Card>
{
// Static pages: the pressed button's (or menu item's) Name=Value posts with the form.
[SupplyParameterFromForm(FormName = "toolbar-bulk-actions", Name = "intent")]
private string? Intent { get; set; }
// Interactive pages post nothing, so OnClick says which it was.
private string? _pressed;
private string? _result;
private void Apply() => _result = (Intent ?? _pressed) switch
{
"archive" => "Archived 3 messages.",
"read" => "Marked 3 messages as read.",
"delete" => "Deleted 3 messages.",
_ => null,
};
}Live: OnClick says which was pressed, from the bar or the menu; nothing is posted.
Fields, a SegmentedControl and a Menu
Put other content in a ToolbarItem with a Width, and give it MenuContent for what stands in for it in the menu; without that, it stays in the bar.
Preview
Showing open issues.
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody Gap="3">
<Form Model="Model" FormName="toolbar-filters" OnValidSubmit="Apply">
<Toolbar Label="Filters">
<ToolbarItem Overflow="ToolbarOverflow.Never" Width="Length.Rem(9)">
<TextField @bind-Value="Model!.Search" Label="Search issues" HideLabel Placeholder="Search issues"
StartIcon="Lucide.Search" Size="ControlSize.Sm" />
</ToolbarItem>
@* In the … menu, the same choice as menu items: posting state=closed, or binding on interactive pages. *@
<ToolbarItem Priority="1" Width="Length.Rem(9.5)">
<ChildContent>
<SegmentedControl @bind-Value="Model!.State" Label="State" HideLabel Size="ControlSize.Sm" />
</ChildContent>
<MenuContent>
<MenuRadioGroup Label="State" Name="state" Value="Model!.State.ToString()" ValueChanged="Choose">
<MenuItem Value="Open">Open</MenuItem>
<MenuItem Value="Closed">Closed</MenuItem>
<MenuItem Value="All">All</MenuItem>
</MenuRadioGroup>
</MenuContent>
</ToolbarItem>
<ToolbarMenu Label="Export" StartIcon="Lucide.Download" Position="ToolbarPosition.End">
<MenuItem Submit Name="export" Value="csv" OnClick='() => _export = "csv"'>CSV</MenuItem>
<MenuItem Submit Name="export" Value="xlsx" OnClick='() => _export = "xlsx"'>Excel</MenuItem>
</ToolbarMenu>
<ToolbarButton Submit Accent="Accent.Primary" Variant="ButtonVariant.Solid" Position="ToolbarPosition.End" Overflow="ToolbarOverflow.Never">Apply</ToolbarButton>
</Toolbar>
</Form>
<TextBlock Size="TextSize.Sm" Color="Color.Muted">_result</TextBlock>
</CardBody>
</Card>
{
[SupplyParameterFromForm(FormName = "toolbar-filters")]
private Filter? Model { get; set; }
// Static pages: a menu item posts export=csv; interactive pages say it with OnClick.
[SupplyParameterFromForm(FormName = "toolbar-filters", Name = "export")]
private string? Export { get; set; }
// The state chosen in the … menu, posted under its own name so it doesn't clash with the
// SegmentedControl's radios, which post too.
[SupplyParameterFromForm(FormName = "toolbar-filters", Name = "state")]
private IssueState? MenuState { get; set; }
private string? _export;
private string _result = "Showing open issues.";
protected override void OnInitialized() => Model ??= new();
private void Choose(string value) => Model!.State = Enum.Parse<IssueState>(value);
private void Apply()
{
if (MenuState is { } state)
{
Model!.State = state;
}
var search = string.IsNullOrWhiteSpace(Model!.Search) ? "" : $" matching \"{Model.Search}\"";
_result = (Export ?? _export) is { } format
? $"Exported {Model.State.ToString().ToLowerInvariant()} issues{search} as {(format == "csv" ? "CSV" : "Excel")}."
: $"Showing {Model.State.ToString().ToLowerInvariant()} issues{search}.";
_export = null;
}
public enum IssueState
{
Open,
Closed,
All,
}
public sealed class Filter
{
public string? Search { get; set; }
public IssueState State { get; set; }
}
}A ToolbarMenu becomes a submenu. The state's MenuRadioGroup posts under its own name, state: the SegmentedControl's radios post with the form too, even while it's in the menu.
Running C# on click
Use OnClick on its buttons on interactive pages; it runs from the bar or the menu.
Always Live
Preview
Count: 0
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody Gap="3">
<Toolbar Label="Counter">
<ToolbarIconButton Icon="Lucide.Minus" Label="Decrease" OnClick="() => _count--" />
<ToolbarIconButton Icon="Lucide.Plus" Label="Increase" OnClick="() => _count++" />
<ToolbarButton StartIcon="Lucide.RotateCcw" OnClick="() => _count = 0">Reset count</ToolbarButton>
<ToolbarButton StartIcon="Lucide.ChevronsUp" OnClick="() => _count += 10">Add ten</ToolbarButton>
</Toolbar>
<TextBlock Size="TextSize.Sm" Color="Color.Muted">Count: _count</TextBlock>
</CardBody>
</Card>
{
private int _count;
}Toggle buttons
Use @bind-Pressed on its buttons for tools that turn on and off. In the … menu they're checkbox items that toggle the same.
Always Live
Preview
Bold on, italic off, marks off, spelling on
Code
@* Toggles in a toolbar: in the … menu they're checkbox items, which toggle the same. *@
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody Gap="3">
<Toolbar Label="Format">
<ToolbarButtonGroup Label="Text style" Priority="1">
<ToolbarIconButton Icon="Lucide.Bold" Label="Bold" @bind-Pressed="_bold" />
<ToolbarIconButton Icon="Lucide.Italic" Label="Italic" @bind-Pressed="_italic" />
</ToolbarButtonGroup>
<ToolbarSeparator />
<ToolbarButton StartIcon="Lucide.Pilcrow" @bind-Pressed="_marks">Show marks</ToolbarButton>
<ToolbarButton StartIcon="Lucide.SpellCheck" @bind-Pressed="_spelling">Check spelling</ToolbarButton>
</Toolbar>
<TextBlock Size="TextSize.Sm" Color="Color.Muted">
Bold OnOff(_bold), italic OnOff(_italic), marks OnOff(_marks), spelling OnOff(_spelling)
</TextBlock>
</CardBody>
</Card>
{
private bool _bold = true;
private bool _italic;
private bool _marks;
private bool _spelling = true;
private static string OnOff(bool on) => on ? "on" : "off";
}Icons before the menu
Use CollapseLabels to drop the text of buttons and menus with a StartIcon before anything moves into the … menu: they show only their icons, all at once, and the text stays for screen readers and as the tooltip. Narrow the window: once the icons alone don't fit, items move into the menu as before. One with Overflow="ToolbarOverflow.Never", such as Publish here, keeps its text.
Preview
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody>
<Toolbar Label="Insert" CollapseLabels>
<ToolbarButton StartIcon="Lucide.Heading">Heading</ToolbarButton>
<ToolbarButton StartIcon="Lucide.List">List</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Quote">Quote</ToolbarButton>
<ToolbarMenu Label="Media" StartIcon="Lucide.Image">
<MenuItem Icon="Lucide.Image">Image</MenuItem>
<MenuItem Icon="Lucide.Video">Video</MenuItem>
</ToolbarMenu>
<ToolbarButton StartIcon="Lucide.Send" Accent="Accent.Primary" Variant="ButtonVariant.Solid" Position="ToolbarPosition.End" Overflow="ToolbarOverflow.Never">Publish</ToolbarButton>
</Toolbar>
</CardBody>
</Card>Wrapping instead
Use Wrap for a toolbar where everything must show: it wraps onto more rows, with no menu.
Preview
Code
<Card Size="ControlSize.Sm" Width="Length.Full">
<CardBody>
<Toolbar Label="Insert" Wrap>
<ToolbarButton StartIcon="Lucide.Heading">Heading</ToolbarButton>
<ToolbarButton StartIcon="Lucide.List">List</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Quote">Quote</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Code">Code</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Table">Table</ToolbarButton>
<ToolbarButton StartIcon="Lucide.Image">Image</ToolbarButton>
</Toolbar>
</CardBody>
</Card>In an app header
Put the header's actions in a Toolbar with Grow, after the brand, at its end: on phones they move into the … menu. See it run.
<AppHeader>
<SidebarToggle />
<AppBrand Href="/" Icon="Lucide.Mail" Label="Acme Mail" />
<Toolbar Label="Mail actions" Grow>
<ToolbarIconButton Icon="Lucide.Search" Label="Search mail" Position="ToolbarPosition.End" />
<ToolbarIconButton Icon="Lucide.Bell" Label="Notifications" Position="ToolbarPosition.End" />
<ToolbarButton Position="ToolbarPosition.End" Priority="1" Accent="Accent.Primary" Variant="ButtonVariant.Solid">Compose</ToolbarButton>
</Toolbar>
</AppHeader>The header stays the page's banner, and its navigation links belong in a SiteNav: a toolbar is for controls.
Without script
The server works out the width at which each control moves, from its label and icons in Inter, the library's font, and the toolbar shows the result with container queries. The script measures and corrects it: for Inter the two agree, so nothing moves when it arrives.
- Give a
ToolbarItemaWidth, so the server can allow for it. - A theme with another font can be a few pixels out until the script measures.
- A toolbar takes its width from its container, as a container query needs: in a row, or in a box that takes its width from its content, give it
Growor a width. - The toolbar draws its items itself, in the order they reach it: one inside a component of your own (an
AuthorizeView), or one an@ifadds on an interactive page, comes after the rest of its position. A cascading value written around an item inside the toolbar doesn't reach it.
Parts
Written inside Toolbar. The API page lists their parameters.
ToolbarButton: a Button in a Toolbar or a ToolbarButtonGroup.ToolbarIconButton: an IconButton in a Toolbar or a ToolbarButtonGroup.ToolbarButtonGroup: a ButtonGroup in a Toolbar, holding ToolbarButtons and ToolbarIconButtons.ToolbarMenu: a Menu in a Toolbar.ToolbarText: text in a Toolbar, such as "3 selected" or a document's name.ToolbarItem: anything that isn't a toolbar control, in a Toolbar: a search field, a SegmentedControl, a Tooltip around a button.ToolbarSeparator: a vertical line between groups of a Toolbar's items; in its "…" menu, a divider.
Mode support
| Feature | Off | Static | Live |
|---|---|---|---|
| What doesn't fit moves into the … menu, which opens and posts with no script | Yes | Yes | Yes |
| Widths measured in the browser (without script, worked out on the server) | No | Yes | Yes |
| One tab stop, with arrow keys between its controls | No | Yes | Yes |
| OnClick runs from the bar or the menu | No | No | Yes |
Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).
Accessibility
- With script, it's a
toolbarnamed byLabel(optional; name each one when a page has more than one), with one tab stop: Left and Right move between its controls and wrap, Home and End go to the ends (reversed on right-to-left pages), and focus comes back to the control last used. - A text field keeps Left and Right until the caret reaches its start or end, and a SegmentedControl until its first or last option; a Select keeps Up and Down. A Slider, and a number, email or date field (whose caret the script can't read), keep every arrow key, so put one last, or outside the toolbar.
- The "…" button is the last control, named by
MoreLabel; Enter, Space or Down opens its menu ofmenuitems (a ToolbarButtonGroup is a labeled group, a ToolbarMenu a submenu), and Escape comes back. - If the focused control moves into the menu as the toolbar narrows, focus moves to "…".
- With
CollapseLabels, a button that drops its text keeps it for screen readers, and with script shows it as its tooltip. - With JavaScript off and until the script loads, it's a
groupnamed byLabel, each control its own tab stop. - A dialog it opens (a ConfirmButton's) keeps its own Tab order and keys.
Parameters
| Prop | Type | Default |
|---|---|---|
LabelWhat the toolbar is for, for screen readers: "Formatting", "Editor". | string? | |
AccentThe buttons' accent, unless a button sets its own. | Accent | Accent.Neutral |
VariantThe buttons' variant, unless a button sets its own. | ButtonVariant | ButtonVariant.Ghost |
SizeThe buttons' size, unless a button sets its own, and the "…" button's. | ControlSize | ControlSize.Sm |
GapThe space between items, in spacing steps (one step = 4px). | int | 2 |
WrapWraps onto more rows instead of moving items into a "…" menu, for toolbars where everything must show. | bool | false |
CollapseLabelsBefore anything moves into the "…" menu, buttons and menus with a StartIcon drop their text, all at once, and show only the icon (a menu keeps its chevron). | bool | false |
MoreLabelThe "…" button's name for screen readers and its tooltip. | string? | "More actions" |
NonceA CSP nonce for the style element that holds the toolbar's widths. | string? | computed |
ChildContentThe items: ToolbarButton, ToolbarIconButton, ToolbarButtonGroup, ToolbarMenu, ToolbarText, ToolbarItem and ToolbarSeparator. | RenderFragment? |
Plus 16 item props and 4 common props.
Full APISee also
- ButtonGroup: joined or spaced buttons with shared defaults.
- Menu: links, form posts and actions in a popover menu.
- IconButton: a square button with a required label.
- AppShell: header, sidebar, main area, aside and footer, arranged by CSS.
- Demos: In an app header.