Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

NumberField

A number input with stepper buttons.

An interactive page: C# runs over a Blazor Server connection.

Usage

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

1 to 99 per order.

Code

Limits.razor
@using 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>

@code {
    [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

Decimals.razor
@using 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>

@code {
    [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

Steppers.razor
@using 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>

@code {
    [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."

razor
@* 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" />

@code {
    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

Read-only: posted, but can't be changed.

Disabled: not posted.

Code

States.razor
<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>

@code {
    [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

What works in each mode, NumberField
FeatureOffStaticLive
Typing and arrow keys, with Min, Max and Step checked before postingYesYesYes
A number keyboard on phonesYesYesYes
Increase and decrease buttons (hold to repeat)NoYesYes

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 within Min and Max, 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

NumberField parameters
PropTypeDefault
ReadOnlyMakes the field read-only: it can be focused, its steppers are off, and it still posts.boolfalse
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.StepButtonsStepButtons.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?

See also

  • Slider: a range input bound to a number.
  • TextField: a labeled text input with add-ons and a password reveal.
  • Form: posts on static pages, validates live on interactive ones.