# NumberInput
*Capture [numeric input](../../01_Onboarding/02_Concepts/03_Widgets.md) with built-in validation, minimum/maximum constraints, step increments, and custom formatting options.*
The `NumberInput` [widget](../../01_Onboarding/02_Concepts/03_Widgets.md) provides an input field specifically for numeric values. It includes validation for numeric entries and options for
setting minimum/maximum values, step increments, and formatting.
> **tip:** Unless you explicitly specify `Min` and `Max` for a `NumberInput`, common default values will be applied based on the numeric type. For example, integer types use their natural limits, while decimal, double, and float types use practical defaults (e.g., ±999,999.99 for sliders). If you need a specific range, always set `Min` and `Max` yourself.
## Basic Usage
Here's a simple example of a `NumberInput` that allows users to input a number. It also allows to set a minimum
and a maximum limit.
```csharp
public class SimpleNumericValueDemo : ViewBase
{
public override object? Build()
{
var value = UseState(0);
return new NumberInput<double>(value)
.Min(-10)
.Max(10);
}
}
```
The `NumberInput` allows users to enter numeric values directly.
## Variants
`NumberInput`s come in several variants to suit different use cases.
### Slider
This variant helps create a slider that changes the value as the slider is pulled to the right.
This creates the `NumberInputs.Slider` variant.
The following demo shows how a slider can be used to give a visual clue.
```csharp
public class NumberSliderInput : ViewBase
{
public override object? Build()
{
var tapes = UseState(1.0);
var cart = UseState("");
return Layout.Vertical()
| new NumberInput<double>(
tapes.Value,
e => {
tapes.Set(e);
cart.Set($"Added {tapes} cm tape to your cart");
})
.Min(30.0)
.Max(500.0)
.Precision(2)
.Step(0.5)
.Variant(NumberInputs.Slider)
.WithField()
.Label("Tapes")
| Text.Block(cart);
}
}
```
### Money
To enable users to enter money amounts, this variant should be used. The extension function `ToMoneyInput`
should be used to create this variant. This is the idiomatic way to use Ivy.
The following demo uses `NumberInputs.Number` with `NumberFormatStyle.Currency` to create
`NumberInput`s that can take money inputs. `ToMoneyInput` hides all these complexities.
```csharp
public class MoneyInputDemo : ViewBase
{
public override object? Build()
{
var moneyInUSD = UseState<decimal>(0.00M);
var moneyInGBP = UseState<decimal>(0.00M);
var moneyInEUR = UseState<decimal>(0.00M);
// Currency Conversion Rates
var euroToUSD = 1.80M;
var euroToGBP = 0.86M;
return Layout.Vertical()
| Text.H3("Simple Currency Converter")
| new NumberInput<decimal>(
moneyInEUR.Value,
e => {
moneyInEUR.Set(e);
moneyInUSD.Set(e * euroToUSD);
moneyInGBP.Set(e * euroToGBP);
}
)
.FormatStyle(NumberFormatStyle.Currency)
.Currency("EUR")
.Placeholder("€0.00")
.WithField()
.Label("Enter EUR amount:")
| moneyInUSD.ToMoneyInput()
.Currency("USD")
.Disabled()
.WithField()
.Label("USD:")
| moneyInGBP.ToMoneyInput()
.Currency("GBP")
.Disabled()
.WithField()
.Label("GBP:");
}
}
```
## Styling
`NumberInput`s can be customized with various styling options, including `Disabled` and `Invalid` states:
```csharp
public class NumberStylingDemo : ViewBase
{
public override object? Build()
{
var num = UseState(3.14);
return Layout.Vertical()
| num.ToNumberInput()
.Disabled()
.WithField().Label("Disabled")
| num.ToNumberInput()
.Invalid(num.Value > 3.1 ? "Value should be less than 3.1" : "")
.WithField().Label("Invalid");
}
}
```
### Precision and Step
To set the precision of a `NumberInput` this style should be used. This can be used via the extension function
`Precision`. To customize the amount by which the value of a `NumberInput` is changed can be set by `Step`.
The following demo shows these two in action.
```csharp
public class MoneyPrecisionDemo : ViewBase
{
public override object? Build()
{
var precValue = UseState(0.50M);
return Layout.Horizontal()
| new NumberInput<decimal>(precValue)
.Min(0.0)
.Max(100.0)
.Step(0.5)
.Precision(2)
.FormatStyle(NumberFormatStyle.Currency)
.Currency("USD")
.WithField()
.Description("Min 0, Max 100, Step 0.5, Precision 2");
}
}
```
### FormatStyle
There are three different kinds of formats that a `NumberInput` can have. The following shows these in action.
```csharp
public class FormatStyleDemos : ViewBase
{
public override object? Build()
{
var num = UseState(3.14);
var amount = UseState(30.14);
var passingPercentage = UseState(0.35);
return Layout.Vertical()
| num.ToNumberInput().FormatStyle(NumberFormatStyle.Decimal)
| amount.ToNumberInput().FormatStyle(NumberFormatStyle.Currency).Currency("GBP")
| passingPercentage.ToNumberInput().FormatStyle(NumberFormatStyle.Percent);
}
}
```
## Event Handling
`NumberInput`s can handle change and blur events:
```csharp
var onChangedState = UseState(0);
var onChangeLabel = UseState("");
new NumberInput<int>(onChangedState.Value, e =>
{
onChangedState.Set(e);
onChangeLabel.Set("Changed");
});
```
## API
[View Source: NumberInput.cs](https://github.com/Ivy-Interactive/Ivy-Framework/blob/main/src/Ivy/Widgets/Inputs/NumberInput.cs)
### Constructors
| Signature |
|-----------|
| `new NumberInput<TNumber>(IAnyState state, string placeholder = null, bool disabled = false, NumberInputs variant = NumberInputs.Number, NumberFormatStyle formatStyle = NumberFormatStyle.Decimal)` |
| `new NumberInput<TNumber>(TNumber value, Func<Event<IInput<TNumber>, TNumber>, ValueTask> onChange, string placeholder = null, bool disabled = false, NumberInputs variant = NumberInputs.Number, NumberFormatStyle formatStyle = NumberFormatStyle.Decimal)` |
| `new NumberInput<TNumber>(TNumber value, Action<TNumber> state, string placeholder = null, bool disabled = false, NumberInputs variant = NumberInputs.Number, NumberFormatStyle formatStyle = NumberFormatStyle.Decimal)` |
| `new NumberInput<TNumber>(string placeholder = null, bool disabled = false, NumberInputs variant = NumberInputs.Number, NumberFormatStyle formatStyle = NumberFormatStyle.Decimal)` |
| `ToSliderInput(IAnyState state, string placeholder = null, bool disabled = false, NumberFormatStyle formatStyle = NumberFormatStyle.Decimal)` |
| `ToNumberInput(IAnyState state, string placeholder = null, bool disabled = false, NumberInputs variant = NumberInputs.Number, NumberFormatStyle formatStyle = NumberFormatStyle.Decimal)` |
| `ToMoneyInput(IAnyState state, string placeholder = null, bool disabled = false, NumberInputs variant = NumberInputs.Number, string currency = "USD")` |
### Supported Types
| Group | Type | Nullable |
|-------|------|----------|
| Numeric | `short` | `short?` |
| Numeric | `int` | `int?` |
| Numeric | `long` | `long?` |
| Numeric | `byte` | `byte?` |
| Numeric | `float` | `float?` |
| Numeric | `double` | `double?` |
| Numeric | `decimal` | `decimal?` |
### Properties
| Name | Type | Setters |
|------|------|---------|
| `Currency` | `string` | `Currency` |
| `Disabled` | `bool` | `Disabled` |
| `FormatStyle` | `NumberFormatStyle` | `FormatStyle` |
| `Height` | `Size` | - |
| `Invalid` | `string` | `Invalid` |
| `Max` | `double?` | `Max` |
| `Min` | `double?` | `Min` |
| `Nullable` | `bool` | `Nullable` |
| `Placeholder` | `string` | `Placeholder` |
| `Precision` | `int?` | `Precision` |
| `Scale` | `Scale?` | - |
| `Step` | `double?` | `Step` |
| `TargetType` | `string` | - |
| `Value` | `TNumber` | - |
| `Variant` | `NumberInputs` | `Variant` |
| `Visible` | `bool` | - |
| `Width` | `Size` | - |
### Events
| Name | Type | Handlers |
|------|------|----------|
| `OnBlur` | `Func<Event<IAnyInput>, ValueTask>` | `HandleBlur` |
| `OnChange` | `Func<Event<IInput<TNumber>, TNumber>, ValueTask>` | - |
## Examples
### Simple Grocery App
The following shows a realistic example of how several `NumberInput`s can be used.
```csharp
public class GroceryAppDemo : ViewBase
{
public override object? Build()
{
var eggs = UseState(0);
var breads = UseState(0);
var eggCost = 3.45M;
var breadCost = 6.13M;
return Layout.Vertical()
| (Layout.Horizontal()
| eggs.ToNumberInput()
.Min(0)
.Max(12)
.Width(10)
.WithField()
.Label("Egg")
.Description("Maximum 12"))
| (Layout.Horizontal()
| breads.ToNumberInput()
.Min(0)
.Max(5)
.Width(10)
.WithField()
.Label("Bread")
.Description("Maximum 5"))
| Text.P($"{eggs} eggs and {breads} breads").Large()
| (Layout.Horizontal()
| Text.P("Bill : ").Large()
// Since it is disabled, no need to have an onChange event
| new NumberInput<decimal>(eggs.Value * eggCost + breadCost * breads.Value,_ => { })
.Disabled()
.Variant(NumberInputs.Number)
.Precision(2)
.FormatStyle(NumberFormatStyle.Currency)
.Currency("EUR"));
}
}
```
For `NumberInput`s that use `NumberFormatStyle.Currency` the recommended type is `decimal`
like `new NumberInput<decimal>`