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