NumberField
An interactive page: C# runs over a Blazor Server connection.
Usage
@* Label from [Display(Name)], limits from [Range]. *@
<NumberField @bind-Value="Model!.Quantity" />
@* Cents, with the currency attached. *@
<NumberField @bind-Value="Model!.Price" Step="0.01">
<StartAddon>$</StartAddon>
</NumberField>
@* Buttons at both ends, for a small count. *@
<NumberField @bind-Value="Model!.Guests" StepButtons="StepButtons.Split" />It's a native number input, so it posts with the form, the browser checks the limits before posting, and phones show a number keyboard. For a value people judge by eye rather than type, such as a volume, use a Slider.
Limits from the model
Use [Range] on the property for the lowest and highest values, or Min and Max to set them on the field.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="number-field-limits" OnValidSubmit="Save" MaxWidth="20.Rem" Grow>
<NumberField @bind-Value="Model!.Quantity" Hint="1 to 99 per order." />
<NumberField @bind-Value="Model!.Seats" Placeholder="No limit" />
<Row><Button Submit>Update order</Button></Row>
if (_saved is { } saved)
{
<Alert Status="Status.Success">saved.Quantity licenses, (saved.Seats?.ToString() ?? "unlimited") seats.</Alert>
}
</Form>
{
[SupplyParameterFromForm(FormName = "number-field-limits")]
private Order? Model { get; set; }
// What was saved, not the fields: on an interactive page they follow typing.
private (int Quantity, int? Seats)? _saved;
protected override void OnInitialized() => Model ??= new();
private void Save() => _saved = (Model!.Quantity, Model.Seats);
public sealed class Order
{
[Range(1, 99, ErrorMessage = "Order 1 to 99 licenses.")]
public int Quantity { get; set; } = 5;
[Range(1, 500), Display(Name = "Seats per license")]
public int? Seats { get; set; }
}
}Live: The browser checks in the model's words, as on static pages, and Update order runs C# without a page load.
The browser checks the range before posting, in the words of the attribute's ErrorMessage once the script runs, and the validator checks it again on the server. Without an ErrorMessage, as on Seats per license, the browser uses its own words. On a static page, a posted value that isn't a number (only a crafted post can send one) binds as empty, null or 0, with no message of its own.
Decimals and units
Use Step for the allowed increments, such as 0.01 for cents, and StartAddon or EndAddon for a currency or unit.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="number-field-decimals" MaxWidth="20.Rem" Grow>
<NumberField @bind-Value="Model!.Price" Step="0.01" Placeholder="0.00">
<StartAddon>$</StartAddon>
</NumberField>
<NumberField @bind-Value="Model!.Discount" Step="5">
<EndAddon>%</EndAddon>
</NumberField>
</Form>
{
[SupplyParameterFromForm(FormName = "number-field-decimals")]
private Product? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
public sealed class Product
{
[Range(typeof(decimal), "0", "10000")]
public decimal? Price { get; set; } = 49.99m;
[Range(0, 100), Display(Name = "Discount (%)")]
public int Discount { get; set; } = 10;
}
}Whole-number types step by 1 and decimal types allow any value unless you set Step; the browser turns away a value between steps in its own words. The value is written and read with a dot (49.99) whatever the page's culture, as the browser expects. Add-on text isn't part of the field's name, so put a unit that matters in the label, as Discount (%) does.
Step buttons
Use StepButtons to stack the increase and decrease buttons at the end (the default), split them across both ends, or leave them out.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="number-field-steppers" MaxWidth="20.Rem" Grow>
<NumberField @bind-Value="Model!.Nights" />
<NumberField @bind-Value="Model!.Guests" StepButtons="StepButtons.Split" />
<NumberField @bind-Value="Model!.Room" StepButtons="StepButtons.None" />
</Form>
{
[SupplyParameterFromForm(FormName = "number-field-steppers")]
private Booking? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
public sealed class Booking
{
[Range(1, 30)]
public int Nights { get; set; } = 3;
[Range(1, 8)]
public int Guests { get; set; } = 2;
[Display(Name = "Room number")]
public int? Room { get; set; } = 214;
}
}The script adds the buttons and hides the browser's own spin buttons. They stop at each end of the range, and holding one repeats. Up and Down arrows step the field with or without them.
Empty values
Bind a nullable type, such as int?, when the field can be left empty: an empty field posts as null, and [Required] makes it needed. A field bound to int can't hold nothing: emptied on a static page, it binds as 0, and on an interactive page it shows "The Quantity field must be a number."
@* Empty means "no limit": null when it posts empty. *@
<NumberField @bind-Value="Model!.Seats" Placeholder="No limit" />
@* Needed: a nullable type starts empty, and [Required] asks for it. *@
<NumberField @bind-Value="Model!.Quantity" />
{
public sealed class Order
{
[Range(1, 500)]
public int? Seats { get; set; }
[Required, Range(1, 99)]
public int? Quantity { get; set; }
}
}Sizes, read-only and disabled
Use Size to line up with buttons and other fields, ReadOnly for a value that posts but can't change, and Disabled for one that doesn't post.
Preview
Code
<Form Model="Model" FormName="number-field-states" MaxWidth="20.Rem" Grow>
<NumberField @bind-Value="Model!.Small" Label="Small" Size="ControlSize.Sm" />
<NumberField @bind-Value="Model!.Large" Label="Large" Size="ControlSize.Lg" />
<NumberField @bind-Value="Model!.Cores" Label="CPU cores" ReadOnly Hint="Read-only: posted, but can't be changed." />
<NumberField @bind-Value="Model!.Replicas" Label="Replicas" Disabled Hint="Disabled: not posted." />
</Form>
{
[SupplyParameterFromForm(FormName = "number-field-states")]
private Server? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
public sealed class Server
{
public int Small { get; set; } = 2;
public int Large { get; set; } = 4;
public int Cores { get; set; } = 8;
public int Replicas { get; set; } = 3;
}
}A read-only value posts like any other, so a crafted post can change it: check it on the server if it matters. A disabled field posts nothing, so after a static post its property holds its default (0, or null): keep its value on the server rather than saving it from the post. Use IncreaseLabel and DecreaseLabel to rename the step buttons for screen readers, or set them for the whole app as in Text and other languages.
Mode support
| Feature | Off | Static | Live |
|---|---|---|---|
| Typing and arrow keys, with Min, Max and Step checked before posting | Yes | Yes | Yes |
| A number keyboard on phones | Yes | Yes | Yes |
| Increase and decrease buttons (hold to repeat) | No | Yes | Yes |
Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).
Accessibility
- It renders a native number
input(a spin button to screen readers): Up and Down arrows step it withinMinandMax, with or without script. - The step buttons are named "Increase" and "Decrease" (
IncreaseLabel,DecreaseLabel). They aren't tab stops, since the arrow keys do the same, but screen readers can press them. - Each step button is disabled at its end of the range, and both are disabled when the field is disabled or read-only.
- Label, hint and errors work as in
TextField; put a unit that matters in the label, since add-on text isn't part of the name.
Parameters
| Prop | Type | Default |
|---|---|---|
ReadOnlyMakes the field read-only: it can be focused, its steppers are off, and it still posts. | bool | false |
MinThe lowest value. | double? | computed |
MaxThe highest value. | double? | computed |
StepThe step between allowed values, such as 0.01 for cents. | double? | computed |
PlaceholderExample text shown when empty. | string? | |
StartIconAn icon inside the field, before the number. | IconData? | |
StartAddonContent attached before the control, outside its border. | RenderFragment? | |
EndAddonContent attached after the control, outside its border, such as a unit. | RenderFragment? | |
StepButtonsIncrease and decrease buttons, shown once scripts run: stacked at the end (the default), split across both ends, or none. | StepButtons | StepButtons.Stacked |
IncreaseLabelThe increase button's accessible name. | string? | |
DecreaseLabelThe decrease button's accessible name. | string? | |
@bind-ValueThe value, bound with @bind-Value. | TValue | |
DisplayNameThe field's name in validation messages from the input itself, such as a value that can't be parsed. | string? |
Plus 26 field props.
Full API