ShinyButton | Blazor Usage
The Blazor <ShinyButton> mirrors the MAUI control parameter-for-parameter and renders a real <button type="button">, so keyboard activation, the focus ring and disabled all come from the platform.
Two differences the platforms force:
- There is no
ICommandon the web, so the command-state integration is MAUI-only. Its equivalent here is thatClickedis awaited — anasynchandler holds the button busy for exactly as long as it runs. - Motion icons default to
currentColor, so they inherit the button’s CSScolorincluding the hover and disabled states with nothing to wire up. Do not setIconColorunless you want to break that.
@using Shiny.Blazor.ControlsBasic Usage
Section titled “Basic Usage”<ShinyButton Text="Save" BusyText="Saving..." LeftMotionIcon="download" Clicked="SaveAsync" />
<ShinyButton Text="Delete" Appearance="ButtonAppearance.Outlined" Type="ButtonType.Critical" LeftMotionIcon="trash" Clicked="DeleteAsync" />
<ShinyButton Text="Cancel" Appearance="ButtonAppearance.Text" Clicked="@(() => open = false)" />
@code { bool open = true;
// Async: the button is busy for the whole call, then returns to normal. async Task SaveAsync() => await http.PostAsJsonAsync("/api/save", model);
async Task DeleteAsync() => await http.DeleteAsync($"/api/items/{id}");}A synchronous handler never produces a spinner flicker — the returned task is checked for completion before any state change, so a handler that finished inline is treated as instant.
States
Section titled “States”<ShinyButton Text="Submit" @bind-State="submitState" BusyText="Submitting..." SuccessText="Submitted" ErrorText="Failed" StateRevertDelay="@TimeSpan.FromSeconds(2)" Clicked="SubmitAsync" />
@code { ButtonState submitState = ButtonState.Normal;
async Task SubmitAsync() { await http.PostAsJsonAsync("/api/submit", model); submitState = ButtonState.Success; // the button respects this, rather than resetting }}@bind-State and @bind-IsBusy both work. The component tracks the rendered state separately from its parameters and only lets a parameter the parent actually changed override it — which is what lets an async handler hold the button busy while the parent keeps re-supplying the same IsBusy="false" on every render.
A handler that throws sets Error (when ShowErrorOnFault is set) and the exception is still rethrown, so your error boundary and logging see it.
Parameters
Section titled “Parameters”Everything from the MAUI surface, with these Blazor forms:
| Parameter | Type | Default | Notes |
|---|---|---|---|
Text |
string? |
null |
|
TextColor |
string? |
null |
CSS colour |
FontSize |
double |
15 |
px |
FontFamily |
string? |
null |
Unset inherits from the page |
FontWeight |
string? |
"500" |
CSS font-weight |
Appearance |
ButtonAppearance |
Filled |
|
Type |
ButtonType |
Primary |
|
ButtonBackgroundColor |
string? |
null |
CSS colour; wins over the tokens |
BorderColor |
string? |
null |
CSS colour |
BorderThickness |
double |
-1 |
px; -1 lets the appearance decide |
CornerRadius |
double |
10 |
px |
ContentPadding |
string |
"10px 16px" |
A CSS padding value, not a Thickness |
HasShadow |
bool? |
null |
Null lets the appearance decide |
FullWidth |
bool |
false |
Stretches to the container |
LeftIcon / RightIcon |
string? |
null |
An image URL or raw SVG/HTML markup |
LeftMotionIcon / RightMotionIcon |
string? |
null |
A motion icon name |
LeftIconContent / RightIconContent |
RenderFragment? |
null |
The LeftIconView equivalent |
IconSize |
double |
20 |
px |
IconColor |
string? |
null |
Unset leaves motion icons on currentColor |
IconSpacing |
double |
8 |
px |
ContentLayout |
ButtonContentLayout |
Sides |
|
State / StateChanged |
ButtonState |
Normal |
@bind-State |
IsBusy / IsBusyChanged |
bool |
false |
@bind-IsBusy |
BusyMode |
ButtonBusyMode |
ReplaceLeftIcon |
|
BusyText / SuccessText / ErrorText |
string? |
null |
Null keeps Text |
BusyMotionIcon |
string? |
"loader" |
Cleared falls back to a CSS spinner |
BusyContent |
RenderFragment? |
null |
Wins over BusyMotionIcon |
SuccessMotionIcon / ErrorMotionIcon |
string? |
"check" / "warning" |
|
StateRevertDelay |
TimeSpan |
1.5s | Zero holds |
DisableWhileBusy |
bool |
true |
|
AutoBusy |
bool |
true |
Hold busy for an awaited Clicked |
ShowErrorOnFault |
bool |
true |
|
Clicked |
EventCallback<MouseEventArgs> |
— | Awaited |
Disabled |
bool |
false |
The IsEnabled equivalent |
CssClass |
string? |
null |
Extra classes on the button element |
Anything else is splatted onto the <button>, so aria-label, id, data-*, title and form all work:
<ShinyButton LeftMotionIcon="settings" CornerRadius="22" ContentPadding="11px" aria-label="Settings" Clicked="OpenSettings" />Styling
Section titled “Styling”Colours come from the --shiny-color-* custom properties via shiny-btn--{appearance} and shiny-btn--{type} classes rather than inline styles, so the theme carries through and a theme switch needs no re-render. A type class sets four custom properties (base, on-base, container, on-container) and the appearance class decides which of them to paint with — which is also how an explicit colour parameter wins, since it overrides the same custom property inline with no specificity fight.
Hover and press are a filter: brightness() for the filled appearances and a color-mix() state layer for the transparent ones, so they work over any background — including a colour you supplied yourself, which no token rule could have anticipated.
prefers-reduced-motion is honoured: transitions are dropped and the fallback spinner slows down.
Busy modes
Section titled “Busy modes”<!-- Default: spinner takes the left icon's place, text stays, width cannot change --><ShinyButton Text="Save" LeftMotionIcon="download" BusyText="Saving..." Clicked="SaveAsync" />
<!-- Content fades but keeps its layout space, so the button holds its width --><ShinyButton Text="Save" BusyMode="ButtonBusyMode.ReplaceContent" Clicked="SaveAsync" />
<!-- Spinner alongside the existing content --><ShinyButton Text="Save" BusyMode="ButtonBusyMode.KeepContent" Clicked="SaveAsync" />
<!-- Your own indicator --><ShinyButton Text="Save" Clicked="SaveAsync"> <BusyContent> <MyCustomSpinner /> </BusyContent></ShinyButton>

