NumberInput


The NumberInput component is built around an HTML input of type number that restricts user input based on the set parameters.

API Documentation

How it works #

The NumberInput component renders an HTML number field and binds its value to a strongly typed property, keeping the UI and model in sync.

How to use:
  1. Declare a backing field of a numeric type in @code. In this demo: int? enteredValue = null; (nullable allows clearing the input).
  2. Add the NumberInput component and set the generic TValue to the same type, e.g., TValue="int?".
  3. Bind the value using @bind-Value, e.g., @bind-Value="enteredValue", to enable two-way binding.
  4. Optionally render the value to verify updates, e.g., Entered value: @enteredValue.
This demo shows the minimal setup: choose a numeric type via TValue and use @bind-Value for instant model synchronization.
Entered value:
<NumberInput TValue="int?" @bind-Value="enteredValue" />
<div class="mb-3">Entered value: @enteredValue</div>

@code {
    private int? enteredValue = null;
}

Generic type #

The NumberInput component is generic. Set TValue to the numeric type you need so parsing, formatting, and validation match your model. The demo shows int, int?, float, float?, double, double?, decimal, and decimal?.

How to use:
  1. Choose the numeric type for your model. Use a nullable type (e.g., int?) if you want the input to be clearable.
  2. Declare a backing field in @code, e.g., private decimal? amount;
  3. Add NumberInput and set TValue to the same type, e.g., TValue="decimal?".
  4. Bind the value with @bind-Value, e.g., @bind-Value="amount", to keep the UI and model synchronized.
  5. Optionally render the current value, handling nullables, e.g., @(amount.HasValue ? amount.Value.ToString() : "null").
This demo highlights type flexibility: the same component works with multiple numeric types by switching the generic TValue.
Entered value: 0
Entered value: null
Entered value: 0
Entered value: null
Entered value: 0
Entered value: null
Entered value: 0
Entered value: null
<div class="field">
    <label class="form-label">Enter int number</label>
    <NumberInput TValue="int" @bind-Value="value1" />
    <div>Entered value: @value1</div>
</div>

<div class="field">
    <label class="form-label">Enter int? number</label>
    <NumberInput TValue="int?" @bind-Value="value2" />
    <div>Entered value: @(value2.HasValue? value2.Value.ToString() : "null")</div>
</div>

<div class="field">
    <label class="form-label">Enter float number</label>
    <NumberInput TValue="float" @bind-Value="value3" />
    <div>Entered value: @value3</div>
</div>

<div class="field">
    <label class="form-label">Enter float? number</label>

    <NumberInput TValue="float?" @bind-Value="value4" />
    <div>Entered value: @(value4.HasValue? value4.Value.ToString() : "null")</div>
</div>

<div class="field">
    <label class="form-label">Enter double number</label>

    <NumberInput TValue="double" @bind-Value="value5" />
    <div>Entered value: @value5</div>
</div>
<div class="field">
    <label class="form-label">Enter double? number</label>

    <NumberInput TValue="double?" @bind-Value="value6" />
    <div>Entered value: @(value6.HasValue? value6.Value.ToString() : "null")</div>
</div>
<div class="field">
    <label class="form-label">Enter decimal number</label>

    <NumberInput TValue="decimal" @bind-Value="value7" />
    <div>Entered value: @value7</div>
</div>
<div class="field">
    <label class="form-label">Enter decimal? number</label>

    <NumberInput TValue="decimal?" @bind-Value="value8" />
    <div>Entered value: @(value8.HasValue? value8.Value.ToString() : "null")</div>
</div>

@code {
    private int value1;
    private int? value2;
    private float value3;
    private float? value4;
    private double value5;
    private double? value6;
    private decimal value7;
    private decimal? value8;
}

Placeholder #

Use the Placeholder parameter to show hint text in the number field until a value is entered. This is most effective with nullable numeric types so the input can be empty and display the hint.

How to use:
  1. Select a nullable numeric type if you want the field to be clearable (e.g., int?).
  2. Declare a backing field in @code, e.g., private int? enteredValue = null;
  3. Add NumberInput with matching TValue, bind the value, and set Placeholder, e.g., <NumberInput TValue="int?" @bind-Value="enteredValue" Placeholder="Enter a value" />
  4. Optionally display the current value to verify updates, e.g., Entered value: @enteredValue.
The placeholder appears when the value is null/empty and hides as soon as a number is entered; it reappears when the input is cleared. With non-nullable types (e.g., int), a default value like 0 prevents the placeholder from showing.
Entered value:
<NumberInput TValue="int?" @bind-Value="enteredValue" Placeholder="Enter a value" />
<div>Entered value: @enteredValue</div>

@code {
    private int? enteredValue = null;
}

Colors #

Apply theme colors to the input using the Color parameter. Choose a value from NumberInputColor to switch the visual variant (e.g., link, primary, info, success, warning, danger) while keeping the same behavior and binding.

How to use:
  1. Pick your model type and (optionally) make it nullable so the field can be cleared, e.g., private int? enteredValue = null;
  2. Add the component with a matching TValue and set Color, e.g., <NumberInput Class="mb-3" Color="NumberInputColor.Primary" TValue="int?" @bind-Value="enteredValue" Placeholder="Primary number input" />
  3. Use different NumberInputColor values to preview variants: Link, Primary, Info, Success, Warning, Danger.
  4. Optionally stack multiple inputs (as shown in the demo) and bind them to the same field to compare styles while sharing the value.
Omitting Color uses the default styling. The color selection affects appearance only; validation and value binding work the same across variants.
<NumberInput Class="mb-3" Color="NumberInputColor.Link" TValue="int?" @bind-Value="enteredValue" Placeholder="Link number input" />
<NumberInput Class="mb-3" Color="NumberInputColor.Primary" TValue="int?" @bind-Value="enteredValue" Placeholder="Primary number input" />
<NumberInput Class="mb-3" Color="NumberInputColor.Info" TValue="int?" @bind-Value="enteredValue" Placeholder="Info number input" />
<NumberInput Class="mb-3" Color="NumberInputColor.Success" TValue="int?" @bind-Value="enteredValue" Placeholder="Success number input" />
<NumberInput Class="mb-3" Color="NumberInputColor.Warning" TValue="int?" @bind-Value="enteredValue" Placeholder="Warning number input" />
<NumberInput Class="mb-3" Color="NumberInputColor.Danger" TValue="int?" @bind-Value="enteredValue" Placeholder="Danger number input" />

@code {
    private int? enteredValue = null;
}

Sizes #

Control the input’s visual scale with the Size parameter. Choose a value from NumberInputSize to adjust height and font size while keeping behavior and binding unchanged. Available sizes: Small, Normal (default), Medium, and Large.

How to use:
  1. Declare a backing field (nullable if you want the field to be clearable), e.g., private int? enteredValue = null;
  2. Add the component with a matching TValue and set Size, e.g., <NumberInput Class="mb-3" Size="NumberInputSize.Small" TValue="int?" @bind-Value="enteredValue" Placeholder="Small number input" />
  3. Repeat with other sizes to compare variants, e.g., NumberInputSize.Normal, NumberInputSize.Medium, NumberInputSize.Large.
  4. Omit the Size parameter to use the default Normal size.
  5. Optionally stack multiple inputs (as in the demo) bound to the same field to preview sizes side-by-side.
Sizing only affects appearance; parsing, validation, and two-way binding behave identically across all sizes.
<NumberInput Class="mb-3" Size="NumberInputSize.Small" TValue="int?" @bind-Value="enteredValue" Placeholder="Small number input" />
<NumberInput Class="mb-3" Size="NumberInputSize.Normal" TValue="int?" @bind-Value="enteredValue" Placeholder="Normal number input" />
<NumberInput Class="mb-3" Size="NumberInputSize.Medium" TValue="int?" @bind-Value="enteredValue" Placeholder="Medium number input" />
<NumberInput Class="mb-3" Size="NumberInputSize.Large" TValue="int?" @bind-Value="enteredValue" Placeholder="Large number input" />

@code {
    private int? enteredValue = null;
}

Styles #

Adjust visual style details such as rounded corners using the IsRounded parameter. When enabled, the input renders with fully rounded borders (Bulma’s rounded style) while keeping behavior and binding the same.

How to use:
  1. Declare a backing field (nullable if you want the field to be clearable), e.g., private int? enteredValue = null;
  2. Add the component with a matching TValue, bind the value, and set IsRounded="true", e.g., <NumberInput TValue="int?" @bind-Value="enteredValue" IsRounded="true" Placeholder="Rounded number input" />
  3. Optionally display the current value to verify updates, e.g., <div>Entered value: @enteredValue</div>
  4. Combine with Color and Size to create consistent visual variants across your UI.
Omitting IsRounded (or setting it to false) uses the standard corner radius; functionality, validation, and two-way binding are unaffected by this style option.
Entered value:
<NumberInput TValue="int?" @bind-Value="enteredValue" IsRounded="true" Placeholder="Rounded number input" />
<div class="my-3">Entered value: @enteredValue</div>

@code {
    private int? enteredValue = null;
}

States #

Control the input’s visual state with the State parameter. Force a specific appearance for design previews or programmatic styling: Normal (default), Hovered, Focused, and Loading.

How to use:
  1. Declare a backing field (nullable if you want the field to be clearable), e.g., private int? enteredValue = null;
  2. Render a default input for the normal state, e.g., <NumberInput Class="mb-3" TValue="int?" @bind-Value="enteredValue" Placeholder="Normal number input" />
  3. Force the hover style with State="NumberInputState.Hovered", e.g., <NumberInput Class="mb-3" State="NumberInputState.Hovered" TValue="int?" @bind-Value="enteredValue" Placeholder="Hovered number input" />
  4. Force the focus style with State="NumberInputState.Focused", e.g., <NumberInput Class="mb-3" State="NumberInputState.Focused" TValue="int?" @bind-Value="enteredValue" Placeholder="Focused number input" />
  5. Show a progress indicator with State="NumberInputState.Loading", e.g., <NumberInput Class="mb-3" State="NumberInputState.Loading" TValue="int?" @bind-Value="enteredValue" Placeholder="Loading number input" />
  6. Optionally bind all variants to the same field (as in the demo) to compare appearances with a consistent value.
Omitting State lets natural user interaction control hover/focus visuals. The State parameter affects appearance only; parsing, validation, and two-way binding behave the same across all states.
<NumberInput Class="mb-3" TValue="int?" @bind-Value="enteredValue" Placeholder="Normal number input" />
<NumberInput Class="mb-3" State="NumberInputState.Hovered" TValue="int?" @bind-Value="enteredValue" Placeholder="Hovered number input" />
<NumberInput Class="mb-3" State="NumberInputState.Focused" TValue="int?" @bind-Value="enteredValue" Placeholder="Focused number input" />
<NumberInput Class="mb-3" State="NumberInputState.Loading" TValue="int?" @bind-Value="enteredValue" Placeholder="Loading number input" />

@code {
    private int? enteredValue = null;
}

Disabled #

Use the Disabled parameter to render a non-interactive number input while preserving value binding. This is useful for read-only displays where you still want consistent styling and formatting.

How to use:
  1. Declare a backing field (nullable if you want it clearable), e.g., private int? enteredValue = 1532;
  2. Add NumberInput with matching TValue, bind the value, and set Disabled="true", e.g., <NumberInput TValue="int?" @bind-Value="enteredValue" Disabled="true" Placeholder="Disabled number text" />
  3. Optionally render the current value below the input to show it can still be read from the model, e.g., Entered value: @enteredValue.
When disabled, the control can’t be focused or edited (typing, arrow keys, and spinner buttons are inactive). The bound value remains available for display and can still be changed programmatically.
Entered value: 1532
<NumberInput TValue="int?" @bind-Value="enteredValue" Disabled="true" Placeholder="Disabled number text" />
<div class="mb-3">Entered value: @enteredValue</div>

@code {
    private int? enteredValue = 1532;
}

Alignment #

Control the text’s horizontal alignment inside the input using the TextAlignment parameter. Choose from Left, Center, or Right to match your UI or formatting needs (e.g., right-align numbers).

How to use:
  1. Declare a backing field, e.g., private double? enteredValue = 23.54;
  2. Render a default input (inherits the browser’s default alignment), e.g., <NumberInput Class="mb-3" TValue="double?" @bind-Value="enteredValue" />
  3. Set TextAlignment to adjust alignment, e.g., <NumberInput Class="mb-3" TextAlignment="TextAlignment.Left" TValue="double?" @bind-Value="enteredValue" />, TextAlignment="TextAlignment.Center", TextAlignment="TextAlignment.Right".
  4. Optionally bind multiple inputs to the same field (as in the demo) to compare alignments side-by-side.
Alignment is purely visual; parsing, validation, and two-way binding remain unchanged regardless of the chosen alignment.
<NumberInput Class="mb-3" TValue="double?" @bind-Value="enteredValue" />
<NumberInput Class="mb-3" TextAlignment="TextAlignment.Left" TValue="double?" @bind-Value="enteredValue" />
<NumberInput Class="mb-3" TextAlignment="TextAlignment.Center" TValue="double?" @bind-Value="enteredValue" />
<NumberInput Class="mb-3" TextAlignment="TextAlignment.Right" TValue="double?" @bind-Value="enteredValue" />

@code {
    private double? enteredValue = 23.54;
}

Enable Min Max #

Enforce a numeric range by enabling Min/Max limits. Set EnableMinMax="true" and supply Min and Max values to restrict input to the specified bounds.

How to use:
  1. Declare a backing field of a suitable numeric type, e.g., private decimal? amount;
  2. Add NumberInput with matching TValue, bind the value, and configure limits, e.g., <NumberInput TValue="decimal?" @bind-Value="amount" EnableMinMax="true" Min="10" Max="500" Placeholder="Enter amount" />
  3. Optionally show a helper message to communicate the valid range and render the current value to verify binding.
With min/max enabled, the spinner buttons and arrow keys respect the bounds, and the browser/validation logic can flag out-of-range values. Choose a numeric TValue that fits your domain (e.g., decimal? for money).

Tip: The amount must be between 10 and 500.

Entered value:
<div class="field">
    <label class="label">Amount</label>
    <div class="control">
        <NumberInput TValue="decimal?"
                     @bind-Value="@amount"
                     EnableMinMax="true"
                     Min="10"
                     Max="500"
                     Placeholder="Enter amount" />
    </div>
    <p class="help">Tip: The amount must be between 10 and 500.</p>
</div>
<div class="mb-3">Entered value: @amount</div>

@code {
    private decimal? amount;
}

Step #

Use the Step parameter to control the increment/decrement applied by spinner buttons, the mouse wheel, and the ArrowUp/ArrowDown keys. Choose an integer step for whole numbers or a fractional step for decimal values.

How to use:
  1. Select an appropriate numeric type and declare a backing field. For whole numbers use int? (e.g., private int? amount;); for fractional values use decimal? (e.g., private decimal? amount2;).
  2. Add NumberInput with a matching TValue and set Step:
    • <NumberInput TValue="int?" @bind-Value="amount" Step="10" Placeholder="Enter Amount" />
    • <NumberInput TValue="decimal?" @bind-Value="amount2" Step="2.5" Placeholder="Enter Amount" />
  3. Optionally combine with EnableMinMax along with Min/Max to keep stepped values within bounds.
  4. Render the current value to verify behavior, e.g., Entered value: @amount or @amount2.
The step size affects interaction only; users can still type any number allowed by the type and range. Use a decimal-capable TValue (e.g., decimal?) when you need fractional steps.

Info: Here Step parameter is set to 10.

Entered value:

Info: Here Step parameter is set to 2.5.

Entered value:
<!-- Step 1 example -->
<div class="field">
    <label class="label">Amount</label>
    <div class="control">
        <NumberInput TValue="int?" @bind-Value="amount" Step="10" Placeholder="Enter Amount" />
    </div>
    <p class="help">Info: Here <code>Step</code> parameter is set to <b>10</b>.</p>
</div>
<div class="mb-3">Entered value: @amount</div>

<!-- Step 2 example -->
<div class="field">
    <label class="label">Amount</label>
    <div class="control">
        <NumberInput TValue="decimal?" @bind-Value="amount2" Step="2.5" Placeholder="Enter Amount" />
    </div>
    <p class="help">Info: Here <code>Step</code> parameter is set to <b>2.5</b>.</p>
</div>
<div class="mb-3">Entered value: @amount2</div>

@code {
    private int? amount = null;
    private decimal? amount2 = null;
}

Validations #

Integrate NumberInput with Blazor forms validation to enforce rules like required and range. The demo uses EditForm, DataAnnotationsValidator, and model attributes to validate price, discount, and a computed total.

How to use:
  1. Create a model with validation attributes:
    • [Required] for mandatory fields
    • [Range(min, max)] to constrain values
    Example: decimal? Price, decimal? Discount, decimal? Total.
  2. Wrap inputs in an EditForm and add <DataAnnotationsValidator />. Initialize an EditContext for the model if needed.
  3. Render NumberInput for each field:
    • For validated fields, wire the triad Value, ValueChanged, and ValueExpression so Blazor can associate validation with the property:
      <NumberInput TValue="decimal?" Value="invoice.Price" ValueExpression="() => invoice.Price" ValueChanged="(v) => PriceChanged(v)" Placeholder="Enter price" />
    • For read-only/calculated values, bind and disable:
      <NumberInput TValue="decimal?" @bind-Value="invoice.Total" Disabled="true" />
  4. Show messages with <ValidationMessage For="@(() => invoice.Price)" /> (repeat for each field).
  5. In change handlers (e.g., PriceChanged, DiscountChanged), assign the incoming value to the model and recalculate totals to keep UI and validation in sync.
  6. Handle form submission using OnValidSubmit to process valid data. Optionally provide a custom FieldCssClassProvider to apply Bulma classes (e.g., return "is-danger" for invalid fields).
This setup enables immediate feedback for invalid inputs while keeping calculated fields synchronized and styled consistently with Bulma.

@using Microsoft.AspNetCore.Components.Forms
@using System.ComponentModel.DataAnnotations

<EditForm EditContext="@editContext" OnValidSubmit="HandleOnValidSubmit">
    <DataAnnotationsValidator />
    <div class="field is-horizontal">
        <div class="field-label is-normal">
            <label class="label">Item Price: <span class="has-text-danger">*</span></label>
        </div>
        <div class="field-body">
            <div class="field">
                <p class="control is-expanded">
                    <NumberInput TValue="decimal?"
                                 Value="invoice.Price"
                                 ValueExpression="() => invoice.Price"
                                 ValueChanged="(value) => PriceChanged(value)"
                                 Placeholder="Enter price" />
                    <ValidationMessage For="@(() => invoice.Price)" />
                </p>
            </div>
        </div>
    </div>

    <div class="field is-horizontal">
        <div class="field-label is-normal">
            <label class="label">Item Discount:</label>
        </div>
        <div class="field-body">
            <div class="field">
                <p class="control is-expanded">
                    <NumberInput TValue="decimal?"
                                 Value="invoice.Discount"
                                 ValueExpression="() => invoice.Discount"
                                 ValueChanged="(value) => DiscountChanged(value)"
                                 Placeholder="Enter discount" />
                    <ValidationMessage For="@(() => invoice.Discount)" />
                </p>
            </div>
        </div>
    </div>

    <div class="field is-horizontal">
        <div class="field-label is-normal">
            <label class="label">Total Amount: <span class="has-text-danger">*</span></label>
        </div>
        <div class="field-body">
            <div class="field">
                <p class="control is-expanded">
                    <NumberInput TValue="decimal?"
                                 @bind-Value="invoice.Total"
                                 Disabled="true"
                                 Placeholder="Enter discount" />
                    <ValidationMessage For="@(() => invoice.Total)" />
                </p>
            </div>
        </div>
    </div>

    <div class="field is-grouped is-grouped-right">
        <div class="control">
            <Button Type="ButtonType.Submit" Color="ButtonColor.Link">Save</Button>
        </div>
        <div class="control">
            <Button Color="ButtonColor.Link" IsLightVersion="true" @onclick="ResetForm">Reset</Button>
        </div>
    </div>
</EditForm>

@code {
    private Invoice invoice = new();
    private EditContext? editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(invoice);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    protected override void OnParametersSet() => CalculateToatl();

    private void PriceChanged(decimal? value)
    {
        invoice.Price = value;
        CalculateToatl();
    }

    private void DiscountChanged(decimal? value)
    {
        invoice.Discount = value;
        CalculateToatl();
    }

    private void CalculateToatl()
    {
        var price = invoice.Price.HasValue ? invoice.Price.Value : 0;
        var discount = invoice.Discount.HasValue ? invoice.Discount.Value : 0;
        invoice.Total = price - discount;
    }

    public void HandleOnValidSubmit()
    {
        Console.WriteLine($"Price: {invoice.Price}");
        Console.WriteLine($"Discount: {invoice.Discount}");
        Console.WriteLine($"Total: {invoice.Total}");
    }

    private void ResetForm()
    {
        invoice = new();
        editContext = new EditContext(invoice);
    }

    public class Invoice
    {
        [Required(ErrorMessage = "Price required.")]
        [Range(60, 500, ErrorMessage = "Price should be between 60 and 500.")]
        public decimal? Price { get; set; } = 232M;

        [Range(0, 50, ErrorMessage = "Discount should be between 0 and 50.")]
        public decimal? Discount { get; set; }

        [Required(ErrorMessage = "Amount required.")]
        [Range(10, 500, ErrorMessage = "Total should be between 60 and 500.")]
        public decimal? Total { get; set; }
    }

    /// <summary>
    /// Custom FieldCssClassProvider to apply custom CSS classes to valid and invalid fields.
    /// </summary>
    public class CustomFieldClassProvider : FieldCssClassProvider
    {
        public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
        {
            var isValid = editContext.IsValid(fieldIdentifier);

            return isValid ? "" : "is-danger";
        }
    }
}

Value Changed #

React to user input with the ValueChanged callback. This event fires whenever the value changes, allowing you to update state, trigger calculations, or format display text. When using Value/ValueChanged, remember to set the backing value in your handler.

How to use:
  1. Declare backing fields, e.g., private decimal? price = 10M; and private string? displayPrice;
  2. Render NumberInput with the triad:
    <NumberInput TValue="decimal?" Value="price" ValueExpression="() => price" ValueChanged="(value) => PriceChanged(value)" Placeholder="Enter price" />
  3. In the handler, assign the new value and perform side effects:
    price = value; // required to keep UI and model in sync
    displayPrice = $"Price: {value}, changed at @DateTime.Now.ToLocalTime().";
  4. Optionally render @displayPrice to show feedback.
Prefer @bind-Value for simple two-way binding. Use Value/ValueChanged/ValueExpression when you need to intercept and handle changes explicitly.
<div class="field">
    <label class="label">Item Price: <span class="has-text-danger">*</span></label>
    <div class="control">
        <NumberInput TValue="decimal?"
                     Value="price"
                     ValueExpression="() => price"
                     ValueChanged="(value) => PriceChanged(value)"
                     Placeholder="Enter price" />
    </div>
</div>
<div class="mb-3">
    @displayPrice
</div>

@code {
    private decimal? price = 10M;
    private string? displayPrice;

    private void PriceChanged(decimal? value)
    {
        price = value; // this is mandatory
        displayPrice = $"Price: {value}, changed at {DateTime.Now.ToLocalTime()}.";
    }
}
DO YOU KNOW?
This demo website is built using the BlazorExpress.Bulma library and published on the Azure Web App. See our source code on GitHub.