Veto
Components

Button reveal

One button that unfolds into several when the pointer reaches it. The compact form of a toolbar that would otherwise take a whole row - and the revealed buttons are ordinary icon buttons, so they behave exactly as they would anywhere else.

Hover to reveal

Closed, the cluster is one button. Bring the pointer to it and the actions unroll beside it, each arriving a moment after the one before. Keyboard focus reveals them too - tab to the trigger and the cluster opens, then Tab walks into the actions - so hover is never the only way in.

Shared to: nothing yet
razor
<VetoButtonReveal Icon="@VetoIcons.Share" Label="Share" Round="true">
    <VetoIconButton Icon="@VetoIcons.GitHub"   Label="GitHub"   OnClick="ShareToGitHub" />
    <VetoIconButton Icon="@VetoIcons.X"        Label="X"        OnClick="ShareToX" />
    <VetoIconButton Icon="@VetoIcons.LinkedIn" Label="LinkedIn" OnClick="ShareToLinkedIn" />
    <VetoIconButton Icon="@VetoIcons.Mail"     Label="Email"    OnClick="ShareByEmail" />
</VetoButtonReveal>

A trigger that does something itself

Without OnClick the trigger is a disclosure and a click toggles the cluster - which is what makes it usable on a touch screen, where there is no hover. Give it OnClick and it becomes a real action instead: the click runs the default - Save - and the cluster stays the pointer's shortcut to the variants.

Last action: none
razor
@* With OnClick the trigger is an action, not a disclosure: the click saves, and the
   cluster is the shortcut to the variants. *@
<VetoButtonReveal Icon="@VetoIcons.Save" Label="Save" ShowLabel="true" OnClick="SaveAsync">
    <VetoIconButton Icon="@VetoIcons.Copy"     Label="Save a copy"        OnClick="SaveCopyAsync" />
    <VetoIconButton Icon="@VetoIcons.Download" Label="Save and download"  OnClick="SaveAndDownloadAsync" />
</VetoButtonReveal>

Directions

The cluster unfolds whichever way suits where the trigger sits - out to either side, up, or down. Start and Up put the actions before the trigger on screen while leaving the trigger first in the DOM, so Tab still reaches the trigger before the actions it discloses.

razor
<VetoButtonReveal Icon="@VetoIcons.ChevronRight" Label="Out to the end">…</VetoButtonReveal>

<VetoButtonReveal Icon="@VetoIcons.ChevronLeft" Label="Out to the start"
                  Direction="VetoRevealDirection.Start">…</VetoButtonReveal>

<VetoButtonReveal Icon="@VetoIcons.ChevronDown" Label="Downwards"
                  Direction="VetoRevealDirection.Down">…</VetoButtonReveal>

<VetoButtonReveal Icon="@VetoIcons.ChevronUp" Label="Upwards"
                  Direction="VetoRevealDirection.Up">…</VetoButtonReveal>

Opening on a click instead

Hover is right for a cluster beside the thing it acts on. For one that should not open in passing - a row of destructive actions, or a cluster in a dense table - a click is the better trigger, and the cluster then ignores the pointer merely crossing it.

razor
<VetoButtonReveal Icon="@VetoIcons.MoreHorizontal" Label="Row actions"
                  OpenOn="VetoRevealTrigger.Click"
                  Variant="VetoVariant.Outlined" Color="VetoColor.Neutral">
    <VetoIconButton Icon="@VetoIcons.Edit"    Label="Edit" />
    <VetoIconButton Icon="@VetoIcons.Archive" Label="Archive" />
    <VetoIconButton Icon="@VetoIcons.Trash"   Label="Delete" Color="VetoColor.Danger" />
</VetoButtonReveal>

Sizes, variants and spacing

The trigger takes the same size, variant and colour scale as VetoButton, so a cluster sits in a toolbar beside ordinary buttons without looking imported. Stagger can be turned off for a cluster that should appear whole, and it is ignored anyway for a reader who asked for reduced motion.

razor
<VetoButtonReveal Icon="@VetoIcons.Plus" Label="Small" Size="VetoSize.Small" Round="true">…</VetoButtonReveal>
<VetoButtonReveal Icon="@VetoIcons.Plus" Label="Large" Size="VetoSize.Large" Round="true">…</VetoButtonReveal>

@* Appears whole rather than unrolling, with a wider gap. *@
<VetoButtonReveal Icon="@VetoIcons.Bolt" Label="All at once"
                  Stagger="false" Gap="VetoSpacing.Medium"
                  Variant="VetoVariant.Text" Color="VetoColor.Primary">…</VetoButtonReveal>

Reach without a pointer

A cluster that only opened on hover would put its actions out of reach of anyone using a keyboard, and out of reach entirely on a touch screen. So there are three ways in, and the component keeps all of them: the pointer arriving, keyboard focus landing anywhere in the cluster, and a tap or click on the trigger. Esc closes it and returns focus to the trigger.

While the cluster is closed the actions stay in the DOM but are marked inert, so they hold their place in the layout - which is what lets the reveal be a transition rather than a jump - while taking no focus and going unannounced. That is also why focus can move into them the moment the cluster opens, with no second render to wait for.

Parameters

Parameter Type Default Description
Icon string? — The trigger's icon.
Label string? — The trigger's accessible name, and the cluster's group label. Always supply one.
ShowLabel bool false Whether the label is drawn beside the icon.
Tooltip string? Label The trigger's tooltip.
ActionsLabel string? Label The revealed group's accessible name, when it should differ.
TriggerContent RenderFragment? — Replaces the trigger's contents; the button element stays the component's.
Expanded bool false Whether the actions are showing. Supports @bind-Expanded.
ExpandedChanged EventCallback<bool> — Raised when the cluster opens or closes.
OpenOn VetoRevealTrigger Hover Hover or Click. Focus and touch work either way.
Direction VetoRevealDirection End Which way the actions unfold: End, Start, Up or Down.
Size VetoSize Medium The trigger's size. The revealed buttons keep their own.
Variant VetoVariant Filled The trigger's visual treatment.
Color VetoColor Primary The trigger's colour role.
Round bool false Draws the trigger as a circle - the speed-dial look.
Gap VetoSpacing ExtraSmall The gap between the revealed buttons.
Stagger bool true Whether each action arrives after the one before it.
OpenDelay int 0 Milliseconds the pointer must rest before the cluster opens.
CloseDelay int 240 Milliseconds the cluster stays open after the pointer leaves.
Disabled bool false Disables the trigger and closes the cluster.
OnClick EventCallback<MouseEventArgs> — Makes the trigger a real action rather than a disclosure.
Something went wrong. Reload 🗙