# Number Input

> Numeric input with stepper buttons, min/max clamping, precision, prefix/suffix slots, and locale-aware formatting via `Intl.NumberFormat`.

- Available in: `@sisyphos-ui/react`, `@sisyphos-ui/vue`, `@sisyphos-ui/angular`
- Docs: https://www.sisyphosui.com/docs/components/number-input

## Installation

Pick the framework binding that matches your stack:

```bash
pnpm add @sisyphos-ui/react   # React 18+
pnpm add @sisyphos-ui/vue     # Vue 3+
pnpm add @sisyphos-ui/angular # Angular 17+
```

## Import

```tsx
import "@sisyphos-ui/react/styles.css";
import { NumberInput } from "@sisyphos-ui/react";
```

## Framework usage

### React 18+

```tsx
import { useState } from "react";
import { NumberInput } from "@sisyphos-ui/react";

export const Quantity = () => {
  const [n, setN] = useState(1);
  return <NumberInput label="Quantity" value={n} onChange={setN} min={1} step={1} />;
};
```

### Vue 3+

```vue
<script setup lang="ts">
import { ref } from "vue";
import { NumberInput } from "@sisyphos-ui/vue";
const n = ref(1);
</script>

<template>
  <NumberInput label="Quantity" v-model="n" :min="1" :step="1" />
</template>
```

### Angular 17+

```ts
import { Component, signal } from "@angular/core";
import { NumberInput } from "@sisyphos-ui/angular";

@Component({
  selector: "app-quantity",
  standalone: true,
  imports: [NumberInput],
  template: `<sui-number-input label="Quantity" [(value)]="n" [min]="1" [step]="1" />`,
})
export class QuantityComponent {
  n = signal(1);
}
```

## Examples

### Default

```tsx
import { useState } from "react";
import { NumberInput } from "@sisyphos-ui/react";

export function Example() {
  const [value, setValue] = useState<number | null>(1);

  return (
    <div className="w-full max-w-[200px]">
      <NumberInput
        label="Quantity"
        min={0}
        max={99}
        step={1}
        value={value}
        onChange={setValue}
      />
    </div>
  );
}
```

### With currency prefix

`prefix` + `precision` format money-shaped values.

```tsx
import { useState } from "react";
import { NumberInput } from "@sisyphos-ui/react";

export function Example() {
  const [v, setV] = useState<number | null>(299);

  return (
    <NumberInput
      label="Monthly budget"
      prefix="$"
      min={0}
      max={10000}
      step={50}
      precision={2}
      value={v}
      onChange={setV}
    />
  );
}
```

### With suffix

Put units after the value with `suffix`.

```tsx
import { useState } from "react";
import { NumberInput } from "@sisyphos-ui/react";

export function Example() {
  const [v, setV] = useState<number | null>(30);

  return (
    <NumberInput
      label="Weight"
      suffix="kg"
      min={0}
      max={500}
      step={0.5}
      precision={1}
      value={v}
      onChange={setV}
    />
  );
}
```

### Error state

Track validation yourself and toggle `error` on the control.

```tsx
import { useState } from "react";
import { NumberInput } from "@sisyphos-ui/react";

export function Example() {
  const [v, setV] = useState<number | null>(0);
  const invalid = v === null || v < 1;

  return (
    <>
      <NumberInput label="Seats" min={1} max={100} value={v} onChange={setV} error={invalid} />
      {invalid && <p>At least 1 seat is required.</p>}
    </>
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| value | `number \| null` | — | Controlled numeric value. |
| defaultValue | `number \| null` | — | Initial uncontrolled value. |
| onChange | `(next: number \| null) => void` | — | Called with the next numeric value or `null` when the field is empty. |
| min | `number` | — | Lower bound; values below it are clamped. |
| max | `number` | — | Upper bound; values above it are clamped. |
| step | `number` | `1` | Step amount for stepper buttons and arrow keys. |
| precision | `number` | `0` | Decimal places shown when formatted. |
| locale | `string` | — | BCP 47 locale tag for `Intl.NumberFormat`. Defaults to the runtime locale; pass an explicit string for a fixed format. |
| numberFormatOptions | `Intl.NumberFormatOptions` | — | Custom `Intl.NumberFormat` options (overrides `precision` when set). |
| withStepper | `boolean` | `true` | Render +/- stepper buttons. |
| prefix | `ReactNode` | — | Slot rendered before the value (e.g. `$`). |
| suffix | `ReactNode` | — | Slot rendered after the value (e.g. `TL`, `kg`). |
| variant | `"standard" \| "outlined" \| "underline"` | `"outlined"` | Visual treatment, mirrors `<Input>`. |
| size | `"sm" \| "md" \| "lg"` | `"md"` | Field height + typography scale. |
| label | `string` | — | Field label rendered above the input. |
| error | `boolean` | `false` | Marks the field as invalid for styling and ARIA. |
| errorMessage | `string` | — | Message shown below the field when `error` is true. |
| fullWidth | `boolean` | `false` | Stretch the input to its container width. |

## Keyboard interactions

- **ArrowUp** — Increments by `step`.
- **ArrowDown** — Decrements by `step`.
- **PageUp + PageDown** — Coarse increment / decrement (10× step).

## Accessibility notes

- Stepper buttons carry descriptive `aria-label`s and a `tabIndex={-1}` so keyboard focus stays on the input.
- `aria-invalid` mirrors `error`; the error message is announced with `role="alert"`.

<!-- exports: { "NumberInput": "@sisyphos-ui/react" } -->