Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

Toolbar

Since 0.1.0-alpha.13
One row of controls; what doesn't fit moves into a … menu.

JavaScript off: what the HTML and CSS do on their own.

Usage

razor
<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

History
Text style

Release notes

Start writing, or paste the notes from the changelog.

Code

Editor.razor
<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>

Off: Widths worked out on the server choose what moves into the … menu; each control is its own tab stop.

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

Priority.razor
<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

Q3 report.pdf
Zoom

Code

Regions.razor
<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

3 selected

Code

BulkActions.razor
<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>

@code {
    // 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,
    };
}

Off: Each button, in the bar or the … menu, posts intent=archive, read or delete with the form.

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

State
State

Showing open issues.

Code

Filters.razor
<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>

@code {
    [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

This example runs C# over a Blazor Server connection, which an Off page doesn't have. Show this page Live

Code

OnClick.razor
<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>

@code {
    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

This example runs C# over a Blazor Server connection, which an Off page doesn't have. Show this page Live

Code

Toggles.razor
@* 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>

@code {
    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

CollapseLabels.razor
<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

Wrap.razor
<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.

razor
<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 ToolbarItem a Width, 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 Grow or 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 @if adds 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

What works in each mode, Toolbar
FeatureOffStaticLive
What doesn't fit moves into the … menu, which opens and posts with no scriptYesYesYes
Widths measured in the browser (without script, worked out on the server)NoYesYes
One tab stop, with arrow keys between its controlsNoYesYes
OnClick runs from the bar or the menuNoNoYes

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 toolbar named by Label (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 of menuitems (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 group named by Label, each control its own tab stop.
  • A dialog it opens (a ConfirmButton's) keeps its own Tab order and keys.

Parameters

Toolbar parameters
PropTypeDefault
LabelWhat the toolbar is for, for screen readers: "Formatting", "Editor".string?
AccentThe buttons' accent, unless a button sets its own.AccentAccent.Neutral
VariantThe buttons' variant, unless a button sets its own.ButtonVariantButtonVariant.Ghost
SizeThe buttons' size, unless a button sets its own, and the "…" button's.ControlSizeControlSize.Sm
GapThe space between items, in spacing steps (one step = 4px).int2
WrapWraps onto more rows instead of moving items into a "…" menu, for toolbars where everything must show.boolfalse
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).boolfalse
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?

See 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.