# Number

Use `ui-number` for quantities, currency-like inputs, percentages, rates, and other numeric values that benefit from browser-native number input behavior plus your shared field styling and validation patterns.

## Import
```ts
import { NumberComponent } from 'ui';
```

## Basic numeric inputs
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NumberComponent } from 'ui';

@Component({
  selector: 'app-number-basic-demo',
  standalone: true,
  imports: [FormsModule, NumberComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));gap:1rem;width:100%;max-width:44rem"
    >
      <ui-number
        label="Seats"
        placeholder="0"
        [(ngModel)]="seats"
        [ngModelOptions]="{ standalone: true }"
        helpText="Simple integer input for counts and quantities."
      />

      <ui-number
        label="Budget"
        placeholder="0.00"
        [(ngModel)]="budget"
        [ngModelOptions]="{ standalone: true }"
        [step]="0.01"
        helpText="Decimal input is useful for money-like values."
      />
    </div>
  `,
})
export class NumberBasicDemoComponent {
  protected seats = 12;
  protected budget = 2500;
}
```

## Min, max, and step ranges
```ts
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonComponent, NumberComponent } from 'ui';

@Component({
  selector: 'app-number-step-ranges-demo',
  standalone: true,
  imports: [FormsModule, ButtonComponent, NumberComponent],
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:44rem">
      <div
        style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background2-rest)"
      >
        <ui-button variant="secondary" appearance="outline" (click)="reset()">Reset</ui-button>
        <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)"
          >Qty: <strong>{{ quantity }}</strong></span
        >
        <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)"
          >Price: <strong>{{ price }}</strong></span
        >
        <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)"
          >Temp: <strong>{{ temperature }}</strong></span
        >
      </div>

      <div
        style="display:grid;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));gap:1rem;width:100%"
      >
        <ui-number
          label="Quantity"
          placeholder="0"
          [(ngModel)]="quantity"
          [ngModelOptions]="{ standalone: true }"
          [step]="1"
          [min]="0"
          [max]="100"
          helpText="Min 0, max 100, step 1."
        />

        <ui-number
          label="Unit price"
          placeholder="0.00"
          [(ngModel)]="price"
          [ngModelOptions]="{ standalone: true }"
          [step]="0.25"
          [min]="0"
          helpText="Quarter increments fit pricing or rate adjustments."
        />

        <ui-number
          label="Temperature"
          placeholder="0"
          [(ngModel)]="temperature"
          [ngModelOptions]="{ standalone: true }"
          [step]="5"
          [min]="-20"
          [max]="60"
          helpText="Fixed increments are useful for bounded operational values."
        />
      </div>
    </div>
  `,
})
export class NumberStepRangesDemoComponent {
  protected quantity = 10;
  protected price = 9.5;
  protected temperature = 20;
  protected readonly resetTick = signal(0);

  protected reset(): void {
    this.quantity = 10;
    this.price = 9.5;
    this.temperature = 20;
    this.resetTick.update(value => value + 1);
  }
}
```

## Size and input variant
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NumberComponent } from 'ui';

@Component({
  selector: 'app-number-size-variant-demo',
  standalone: true,
  imports: [FormsModule, NumberComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem;width:100%;max-width:48rem"
    >
      <div
        style="display:flex;flex-direction:column;gap:0.875rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="font-size:0.875rem;font-weight:600">Sizes</div>
        <ui-number label="Small value" [size]="'small'" placeholder="0" />
        <ui-number label="Medium value" [size]="'medium'" placeholder="0" />
        <ui-number label="Large value" [size]="'large'" placeholder="0" />
      </div>

      <div
        style="display:flex;flex-direction:column;gap:0.875rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="font-size:0.875rem;font-weight:600">Input variants</div>
        <ui-number label="Filled" inputVariant="filled" placeholder="0" />
        <ui-number label="Filled gray" inputVariant="filled-gray" placeholder="0" />
        <ui-number label="Underlined" inputVariant="underlined" placeholder="0" />
      </div>
    </div>
  `,
})
export class NumberSizeVariantDemoComponent {}
```

## States and validation
```ts
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { NumberComponent } from 'ui';

@Component({
  selector: 'app-number-states-validation-demo',
  standalone: true,
  imports: [ReactiveFormsModule, NumberComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem;width:100%;max-width:48rem"
    >
      <ui-number
        label="Required quantity"
        placeholder="0"
        [formControl]="quantity"
        [required]="true"
        helpText="This field participates in reactive validation."
      />

      <ui-number
        label="Readonly base price"
        placeholder="0"
        [formControl]="readonlyPrice"
        [readonly]="true"
        helpText="Readonly is useful for calculated or locked values."
      />

      <ui-number
        label="Disabled archived value"
        placeholder="0"
        [formControl]="disabledValue"
        [disabled]="true"
        helpText="Disabled removes the field from interaction."
      />

      <ui-number
        label="Manual error state"
        placeholder="0"
        [formControl]="manualError"
        [errorText]="'Value exceeds available budget'"
      />
    </div>
  `,
})
export class NumberStatesValidationDemoComponent {
  protected readonly quantity = new FormControl<number | null>(null, {
    validators: [Validators.required, Validators.min(1)],
  });

  protected readonly readonlyPrice = new FormControl<number | null>(249, {
    nonNullable: false,
  });

  protected readonly disabledValue = new FormControl<number | null>({
    value: 1200,
    disabled: true,
  });

  protected readonly manualError = new FormControl<number | null>(3200);
}
```

## Budget or estimate form
```ts
import { Component, computed } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { NumberComponent } from 'ui';

@Component({
  selector: 'app-number-budget-form-demo',
  standalone: true,
  imports: [ReactiveFormsModule, NumberComponent],
  template: `
    <div
      style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:42rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
    >
      <div style="font-size:0.9375rem;font-weight:600">Budget setup</div>
      <div
        style="display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:1rem;width:100%"
      >
        <ui-number
          label="Seats"
          [formControl]="budgetForm.controls.seats"
          [min]="1"
          [step]="1"
          helpText="How many paid seats should be provisioned."
        />

        <ui-number
          label="Cost per seat"
          [formControl]="budgetForm.controls.costPerSeat"
          [min]="0"
          [step]="0.5"
          helpText="Half-unit steps fit common pricing adjustments."
        />

        <ui-number
          label="Reserve"
          [formControl]="budgetForm.controls.reserve"
          [min]="0"
          [step]="10"
          helpText="Optional buffer for overruns or changes."
        />
      </div>

      <div
        style="display:flex;flex-wrap:wrap;gap:1rem;align-items:center;padding:0.875rem 1rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background2-rest)"
      >
        <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)"
          >Monthly total: <strong>{{ total() }}</strong></span
        >
      </div>
    </div>
  `,
})
export class NumberBudgetFormDemoComponent {
  protected readonly budgetForm = new FormGroup({
    seats: new FormControl(12, { nonNullable: true }),
    costPerSeat: new FormControl(24.5, { nonNullable: true }),
    reserve: new FormControl(50, { nonNullable: true }),
  });

  protected readonly total = computed(() => {
    const seats = this.budgetForm.controls.seats.getRawValue();
    const costPerSeat = this.budgetForm.controls.costPerSeat.getRawValue();
    const reserve = this.budgetForm.controls.reserve.getRawValue();
    return (seats * costPerSeat + reserve).toFixed(2);
  });
}
```

## Accessibility

### Label and description
`ui-number` inherits the shared field labeling model. Use the visible label as the primary accessible name and reserve `ariaLabel` for exceptional cases where no visible label exists.

### Keyboard behavior
Keyboard behavior largely follows the native number input.

| Key | Action |
| --- | --- |
| Tab / Shift+Tab | Moves focus to or from the input. |
| ArrowUp / ArrowDown | May increment or decrement the native number input depending on browser behavior. |
| Enter | Follows the surrounding form behavior rather than a custom component action. |

### Validation and constraints
When you use `min`, `max`, and `step`, keep the visible help text or validation feedback aligned with those constraints so users understand why a value was clamped or rejected.
