Veto
Components

Menu

A button that drops a list of actions below it - the overflow menu on a toolbar, the export choices on a chart, the account menu in an app bar. Opens on a click or on a hover, and draws its items as icons, text, or both.

A menu of actions

The items are ordinary children rather than a list passed in, so one menu can hold actions, links, headings and a divider without the component modelling each of them. Choosing an item closes the menu and hands focus back to the trigger.

Chosen: nothing yet
razor
<VetoMenu Label="Actions" Icon="@VetoIcons.Bolt">
    <VetoMenuItem Icon="@VetoIcons.Edit" Text="Rename" Shortcut="F2" OnClick="Rename" />
    <VetoMenuItem Icon="@VetoIcons.Copy" Text="Duplicate" Shortcut="Ctrl+D" OnClick="Duplicate" />
    <VetoMenuItem Icon="@VetoIcons.Share" Text="Share" OnClick="Share" />
    <VetoMenuDivider />
    <VetoMenuItem Icon="@VetoIcons.Trash" Text="Delete" Destructive="true" OnClick="Delete" />
</VetoMenu>

Opening on hover

Hover suits the control a reader only occasionally wants - it costs one button of room and shows its contents when they go looking. It never becomes the only way in: a click still toggles the menu, the arrow keys still open it, and touch, which has no hover, gets the click. The gap between trigger and panel is bridged, so crossing it does not shut the menu.

razor
@* A click still toggles it, and the arrow keys still open it. *@
<VetoMenu Label="Export" Icon="@VetoIcons.Download" OpenOn="VetoMenuTrigger.Hover">
    <VetoMenuLabel Text="Export as" />
    <VetoMenuItem Icon="@VetoIcons.Csv" Text="CSV" OnClick="ExportCsvAsync" />
    <VetoMenuItem Icon="@VetoIcons.Json" Text="JSON" OnClick="ExportJsonAsync" />
    <VetoMenuItem Icon="@VetoIcons.Pdf" Text="PDF" OnClick="ExportPdfAsync" />
    <VetoMenuItem Icon="@VetoIcons.Spreadsheet" Text="Spreadsheet" Disabled="true" />
</VetoMenu>

Icons, text, or both

Appearance is set once on the menu rather than per item, so switching a crowded toolbar to icons is one change in one place. Icon-only keeps each item's text as its accessible name and its tooltip - it is a saving of space, not of meaning - and text-only drops the icon column entirely instead of leaving the items indented around a gap.

razor
@* Both, the default. *@
<VetoMenu Label="Both" Icon="@VetoIcons.Grid">…</VetoMenu>

@* Icons alone. Each item's Text is still its accessible name and tooltip. *@
<VetoMenu Label="Icons only" Icon="@VetoIcons.MoreVertical" Round="true"
          Appearance="VetoMenuAppearance.IconOnly" MinWidth="auto">…</VetoMenu>

@* Text alone - the icon column is left out rather than left empty. *@
<VetoMenu Label="Text only" Appearance="VetoMenuAppearance.TextOnly">…</VetoMenu>

Placement

The panel hangs from the corner you name. Placement is resolved in CSS against the trigger rather than measured against the window, which keeps the menu free of JavaScript and working in prerendered HTML - the trade is that it does not flip itself away from the edge of the screen, so a trigger at the end of a row wants an end placement. Collision handling waits for one shared popover engine rather than half of one here.

razor
<VetoMenu Label="Bottom start" Placement="VetoMenuPlacement.BottomStart">…</VetoMenu>
<VetoMenu Label="Bottom end"   Placement="VetoMenuPlacement.BottomEnd">…</VetoMenu>
<VetoMenu Label="Top start"    Placement="VetoMenuPlacement.TopStart">…</VetoMenu>
<VetoMenu Label="Top end"      Placement="VetoMenuPlacement.TopEnd">…</VetoMenu>

Choices that stay open

A checkable item reports itself through aria-checked and keeps its tick column whether or not it is ticked, so the items under it do not shuffle sideways as the reader toggles one. KeepOpen leaves the menu up afterwards, which is what you want for a set of toggles the reader is about to change again - and Selected marks a single current choice rather than a box with two states.

Sorted by Name, columns Owner, Progress
razor
@* Toggles: the menu stays up, and each item keeps its tick column. *@
<VetoMenu Label="Columns" Icon="@VetoIcons.Sidebar" CloseOnSelect="false">
    <VetoMenuLabel Text="Show" />
    <VetoMenuItem Text="Owner"    Checked="_owner" KeepOpen="true" OnClick="() => _owner = !_owner" />
    <VetoMenuItem Text="Due date" Checked="_due"   KeepOpen="true" OnClick="() => _due = !_due" />
</VetoMenu>

@* One current choice: Selected highlights, it does not tick. *@
<VetoMenu Label="Sort" Icon="@VetoIcons.SortAscending">
    <VetoMenuItem Text="Name"   Selected="@(_sort == "Name")"   OnClick="() => SortBy("Name")" />
    <VetoMenuItem Text="Newest" Selected="@(_sort == "Newest")" OnClick="() => SortBy("Newest")" />
</VetoMenu>

A menu of links

An item given an Href renders as an anchor rather than a button, so middle-click and open-in-new-tab work as the reader expects them to. It is still a menuitem to assistive technology.

razor
<VetoMenu Label="Follow" Icon="@VetoIcons.Share" Variant="VetoVariant.Text">
    <VetoMenuItem Icon="@VetoIcons.GitHub" Text="GitHub"
                  Href="https://github.com" Target="_blank" Rel="noopener" />
    <VetoMenuItem Icon="@VetoIcons.X" Text="X"
                  Href="https://x.com" Target="_blank" Rel="noopener" />
</VetoMenu>

Sizes and variants

The trigger carries the same size, variant and colour scale as VetoButton, so a menu drops into a toolbar beside ordinary buttons without looking like it came from somewhere else. The trigger's size also sizes the panel's text.

razor
<VetoMenu Label="Small" Size="VetoSize.Small" Dense="true">…</VetoMenu>
<VetoMenu Label="Medium">…</VetoMenu>
<VetoMenu Label="Large" Size="VetoSize.Large">…</VetoMenu>

<VetoMenu Label="Filled" Variant="VetoVariant.Filled" Color="VetoColor.Primary">…</VetoMenu>
<VetoMenu Label="Danger" Variant="VetoVariant.Text" Color="VetoColor.Danger">…</VetoMenu>
<VetoMenu Label="Disabled" Disabled="true">…</VetoMenu>

A menu inside something that clips

The panel is an absolutely positioned child of the menu, not a layer moved to the end of the document, so it is subject to whatever its ancestors do. A container with overflow: hidden will cut it off at its edge - and VetoCard clips by default, so that a media image is trimmed to the card's own corners. Every Veto component takes a RootOverflow parameter for exactly this - RootOverflow="VetoOverflow.Visible" on the card that holds the menu, which is what the demos on this page do.

This is the price of positioning in CSS rather than measuring in JavaScript: no interop, no layout thrash, correct in prerendered HTML - but no escape from an ancestor's clip and no flip away from the edge of the window. Both are the job of the one shared popover engine, which VetoDialog and VetoTooltip will want too.

Keyboard

The trigger is the menu's only tab stop; the items are reached with the arrow keys, which is the menu-button pattern assistive technology expects. The arrows wrap, and they skip disabled items rather than landing on something that does nothing.

Open the menu and focus its first item
Enter
Open the menu, or move to the next item
ArrowDown
Open the menu at its last item, or move back
ArrowUp
Jump to the first item
Home
Jump to the last item
End
Close the menu and return focus to the trigger
Esc
Close the menu and carry on through the page
Tab

VetoMenu

Parameter Type Default Description
Label string? — The trigger's text, and its accessible name when the appearance hides it.
Icon string? — The trigger's icon.
TriggerContent RenderFragment? — Replaces the trigger's contents; the button element stays the component's.
Open bool false Whether the menu is open. Supports @bind-Open.
OpenOn VetoMenuTrigger Click Click or Hover. Hover keeps the click and the keyboard alongside it.
Placement VetoMenuPlacement BottomStart Which corner of the trigger the panel hangs from.
Appearance VetoMenuAppearance IconAndText How much of each item is drawn.
Size VetoSize Medium The trigger's size; also sizes the panel's text.
Variant VetoVariant Outlined The trigger's visual treatment.
Color VetoColor Neutral The trigger's colour role.
Round bool false Draws the trigger as a circle.
Chevron bool? auto Whether the trigger shows a chevron. On for a trigger with text.
Dense bool false Packs the items tightly.
CloseOnSelect bool true Closes the menu when an item is chosen.
MinWidth string "11rem" The panel's minimum width.
Offset string "0.375rem" The gap between the trigger and the panel.
HoverOpenDelay int 120 Milliseconds the pointer must rest before a hover menu opens.
HoverCloseDelay int 280 Milliseconds a hover menu stays open after the pointer leaves.
Disabled bool false Disables the trigger and closes the menu.
OpenChanged EventCallback<bool> — Raised when the menu opens or closes.
OnClosed EventCallback — Raised when the menu closes, whatever closed it.

VetoMenuItem

Parameter Type Default Description
Text string? — The item's text, and its accessible name whatever the appearance hides.
Icon string? — The item's icon.
IconContent RenderFragment? — Markup to render instead of Icon - an avatar, a swatch.
Label string? — The accessible name, for an item built from ChildContent.
Shortcut string? — A keyboard hint drawn at the end. Decoration - the app binds the key.
Checked bool? null Makes the item a checkbox, reported through aria-checked.
Selected bool false Marks the item as the current choice - a highlight, not a checkbox.
Destructive bool false Draws the item in the danger colour.
Disabled bool false Disables the item; the arrow keys skip it.
Href string? — Renders an anchor rather than a button, so navigation stays real.
KeepOpen bool false Leaves the menu open after this item is chosen.
OnClick EventCallback<MouseEventArgs> — Raised when the item is chosen.
Something went wrong. Reload 🗙