# NumberInput

A numeric field with stacked step buttons at its end, for quantities, counts and amounts with a known step (cart quantity, guest count, seats). It is a text input (type text, inputmode decimal), never type number, so it can show formatted text such as 1.000 ₫. Use Input for free text, Slider when the exact value matters less than the range.

## Cách dùng

### Khi nào dùng

- Enter or adjust a quantity whose valid steps are known, such as seats, guests or items in a cart.
- Show a number with locale-aware formatting, such as a currency amount, while keeping it editable.

### Khi nào không dùng

- Enter an identifier such as a phone or card number. Use Input with numeric input mode.
- Pick a position where the range matters more than the exact number. Use Slider.
- Enter free-form text. Use Input.

## Biến thể

### size

Control height from size-element-sm/md/lg/xl. Pick per region; md is the default.

Giá trị: `sm`, `md`, `lg`, `xl`. Mặc định: `md`.

## Ví dụ

### Quantity

```tsx
<Field label="Quantity">
  <NumberInput defaultValue={1} min={1} max={10} />
</Field>
```

### Controlled

```tsx
const [guests, setGuests] = React.useState(2)
<NumberInput value={guests} onValueChange={setGuests} min={1} max={16} size="lg" />
```

### Currency

```tsx
<NumberInput defaultValue={1000} step={1000} min={0} locale="vi-VN" formatOptions={{ style: "currency", currency: "VND" }} />
```

## Nên và không nên

### Nên

- Set min and max whenever the domain has them (a quantity is at least 1).
- Use inside Field so the label and error are linked.
- Use lg or xl on touch-first screens so the buttons are easy to hit.

### Không nên

- Do not use for numbers that are identifiers (card number, phone, postal code); use Input with inputmode numeric.
- Do not use for a range where position matters more than the value; use Slider.
- Do not fire network requests on every step; debounce in the app.

## Props

| Tên | Kiểu | Giá trị | Mặc định | Mô tả |
| --- | --- | --- | --- | --- |
| `value` | `number \| null` |  |  | The value when you control it. null is an empty field. |
| `defaultValue` | `number \| null` |  | `null` | The value on first render when you do not control it. |
| `onValueChange` | `(value: number \| null) => void` |  |  | Called with the committed number (after parse, clamp and step rounding) on a step key, a button press, Enter or blur, and with null when the field is cleared. Not called for text that does not parse (the field shows invalid instead) or on every keystroke. |
| `min` | `number` |  |  | Lowest value. Buttons and ArrowDown stop here. |
| `max` | `number` |  |  | Highest value. Must be greater than min. |
| `step` | `number` |  | `1` | Amount per step. |
| `largeStep` | `number` |  |  | Amount for PageUp and PageDown. Defaults to step times 10. |
| `clampOnBlur` | `boolean` |  | `true` | Clamp and round a typed value when the field loses focus. When false, an out-of-range value stays and the field shows invalid. |
| `size` | `enum` | `sm`, `md`, `lg`, `xl` | `md` | Control height from size-element-sm/md/lg/xl. Pick per region; md is the default. |
| `disabled` | `boolean` |  | `false` | No typing, no buttons, disabled look. |
| `readOnly` | `boolean` |  | `false` | The value can be read, focused and copied but not changed; the step buttons are hidden and the field drops its fill. The inner input gets the native readOnly attribute (its bindings use :read-only); the root group sets data-read-only. |
| `invalid` | `boolean` |  | `false` | Sets aria-invalid and the danger border. |
| `required` | `boolean` |  | `false` | Native required on the input. |
| `name` | `string` |  |  | Form field name; the submitted value is the plain number, not the formatted text. |
| `showStepButtons` | `boolean` |  | `true` | Show the decrement and increment buttons. Hide them for dense tables where arrow keys are enough. |
| `decrementLabel` | `string` |  | `Decrease` | Accessible name of the decrement button. Vietnamese: Giảm. |
| `incrementLabel` | `string` |  | `Increase` | Accessible name of the increment button. Vietnamese: Tăng. |
| `locale` | `string` |  |  | BCP 47 locale for formatting and for the separators used in parsing. Defaults to the document language. |
| `formatOptions` | `Intl.NumberFormatOptions` |  |  | How the value is shown when the field is not being edited and in aria-valuetext: currency, percent, unit, fraction digits. While focused the field shows the plain localized number. |

### Slot

| Tên | Bắt buộc | Mô tả |
| --- | --- | --- |
| `leadingText` | không | Optional fixed text before the number, such as a currency (follows the planned Input leadingText). |
| `trailingText` | không | Optional unit after the number, such as kg. |

## Cấu tạo

| Phần | Mô tả |
| --- | --- |
| `root` | The group that draws the field box: border, fill, corners, focus ring through :has(). The step buttons sit inside it at the end. |
| `input` | The text input with role spinbutton, font-size-input, tabular numbers, aligned to the start (left in LTR, right in RTL), with the step buttons at the end. |
| `decrement` | The lower ghost icon button of the stack at the end (Tabler chevron down, 16px); first of the pair on touch. |
| `increment` | The upper ghost icon button of the stack at the end (Tabler chevron up, 16px); second of the pair on touch. |
| `placeholder` | The placeholder text of the input, in secondary text like Input. |

## Khả năng truy cập

Role: `spinbutton`

| Phím | Hành động |
| --- | --- |
| `ArrowUp / ArrowDown` | Increase or decrease by step, clamped to min and max. |
| `PageUp / PageDown` | Increase or decrease by largeStep. |
| `Home / End` | Set min or max when they are set. |
| `Enter` | Commit the typed text: parse, clamp, round to step. |
| `Tab` | Moves to the next field. The step buttons are skipped (tabIndex -1); the keys above cover them. |

- The input has type text, inputmode decimal (numeric when step is an integer and min is at least 0) and role spinbutton with aria-valuenow, aria-valuemin and aria-valuemax.
- aria-valuetext is the value formatted with formatOptions and locale, for example 1.000 ₫, so screen readers say the same text the field shows.
- Parsing: the group and decimal separators of the locale come from Intl.NumberFormat formatToParts. Group separators are stripped, the decimal separator maps to a dot, then the text is parsed. A string that does not parse sets the invalid state and does not call onValueChange.
- Clearing the field calls onValueChange(null).
- Step buttons are real buttons named by decrementLabel and incrementLabel, disabled at min and max, skipped by Tab (tabIndex -1) because the arrow keys cover them.
- Layout: on a fine pointer the two buttons stack at the end of the field, increment on top. On touch (pointer: coarse) they sit side by side at the end, decrement then increment, each at least size-target-min (44px), with a color-border divider on each button's start edge so the touch area is visible.
- Holding a step button repeats after a short delay; the announcement is the final value, not each step.
- invalid sets aria-invalid; Field passes its error with aria-describedby.

## Cài đặt

```bash
npx werewolf-ui add number-input
```

Chạy `npx werewolf-ui init` một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem [Cài đặt](https://werewolf-ui.vercel.app/vi/docs/installation.md) để biết yêu cầu và cách cài qua registry.
