# Input Amount

A formatted decimal input that emits exact raw decimal strings.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).





## Playground [#playground]

```tsx
"use client";

import { InputAmount } from "@/components/ui/input-amount";
import { useState } from "react";

export function InputAmountDemo() {
  const [amount, setAmount] = useState("1250000.50");
  return (
    <>
      <InputAmount
          aria-label={"Amount"}
          value={amount}
          onValueChange={setAmount}
          prefix={"Rp "}
          suffix={""}
          thousandSeparator={" "}
          decimalSeparator={","}
          decimalScale={2}
      />
    </>
  );
}
```

## Installation [#installation]

Initialize shadcn in your React project, then install this component from the Vandor UI registry:

```bash
npx shadcn@latest add https://vandor-ui.vercel.app/r/input-amount.json
```

The default installation target is components/ui/input-amount.tsx. Review the CLI's overwrite prompt before replacing local customizations.

## Dependencies [#dependencies]

Installed automatically by the registry CLI. For manual setup:

```bash
npm install cn react-number-format
```

## Usage [#usage]

```tsx
import { InputAmount } from "@/components/ui/input-amount";

<InputAmount
  aria-label="Amount in Rupiah"
  prefix="Rp "
  thousandSeparator="."
  decimalSeparator=","
  value={amount}
  onValueChange={setAmount}
/>;
```

Keep the value as decimal text, for example `"1234567.89"`. Configure separators explicitly for the display you want. Use `decimalScale` to constrain fractional digits; `fixedDecimalScale` affects display padding only.

## Examples [#examples]

The value readout is the exact raw decimal string, not the localized display text.

<ComponentPreview name="input-amount-demo" title="input-amount-demo.tsx">
  <InputAmountDemo />
</ComponentPreview>

### Optional Storybook Stories [#optional-storybook-stories]

Install Input Amount first, then run `npx shadcn@latest add https://vandor-ui.vercel.app/r/input-amount-stories.json`. Stories cover decimal-comma formatting, integer and negative amounts, controlled decimal-string output, and disabled/read-only/invalid states. Controls offer a space or no thousands separator, avoiding a collision when you change the decimal separator.

Stories install beside Input Amount under your configured UI alias and use relative imports. Use your existing matching `@storybook/react` packages (verified with 10.6.1), discover `**/*.stories.tsx`, and load your global Tailwind/theme stylesheet. No addons, extra packages, core overwrites, or Storybook config edits are required. Default-value Controls remount uncontrolled examples; Controlled owns local state. Storybook UI is not verified because no server exists here.

## Behavior & Accessibility [#behavior--accessibility]

Formatting preserves normal caret and paste editing. Values are never converted to floating-point numbers. `onValueChange` receives raw decimal text with `.` as the decimal separator, or `""` when cleared. The formatted display is not a substitute for caller-owned form serialization.

## Props [#props]

| Prop              | Type                    | Default                | Description                                                                |
| ----------------- | ----------------------- | ---------------------- | -------------------------------------------------------------------------- |
| allowNegative     | boolean                 | false                  | InputAmount: allows a leading minus sign.                                  |
| aria-label        | string                  | required without label | InputAmount: accessible name for the native input.                         |
| decimalScale      | number                  | undefined              | InputAmount: maximum fraction digits.                                      |
| decimalSeparator  | string                  | "."                    | InputAmount: displayed decimal separator.                                  |
| defaultValue      | string                  | undefined              | InputAmount: initial raw decimal text.                                     |
| fixedDecimalScale | boolean                 | false                  | InputAmount: pads display to decimalScale.                                 |
| name              | string                  | undefined              | InputAmount: native form field name.                                       |
| onValueChange     | (value: string) => void | undefined              | InputAmount: receives raw decimal text on user edits.                      |
| prefix            | string                  | ""                     | InputAmount: text displayed before the formatted number.                   |
| suffix            | string                  | ""                     | InputAmount: text displayed after the formatted number.                    |
| thousandSeparator | string \| boolean       | false                  | InputAmount: optional display grouping separator.                          |
| value             | string                  | uncontrolled           | InputAmount: controlled raw decimal text; decimal mark is always a period. |

The reference lists component-specific props and selected native props, not the complete React API. Other native attributes are forwarded to the rendered element.

## Source [#source]

```tsx
"use client";

import { cn } from "cn";
import type { ComponentProps } from "react";
import { NumericFormat } from "react-number-format";
import type { NumericFormatProps } from "react-number-format";

import { fieldSurfaceClassName } from "./input";

export type InputAmountProps = Omit<
  NumericFormatProps<ComponentProps<"input">>,
  | "value"
  | "defaultValue"
  | "onValueChange"
  | "thousandSeparator"
  | "decimalSeparator"
  | "prefix"
  | "suffix"
  | "decimalScale"
  | "fixedDecimalScale"
  | "allowNegative"
> & {
  value?: string;
  defaultValue?: string;
  onValueChange?: (value: string) => void;
  thousandSeparator?: string | boolean;
  decimalSeparator?: string;
  prefix?: string;
  suffix?: string;
  decimalScale?: number;
  fixedDecimalScale?: boolean;
  allowNegative?: boolean;
};

const fieldClassName =
  "h-9 w-full min-w-0 rounded-md border border-input bg-background px-3 text-sm text-foreground shadow-xs outline-none transition-[color,box-shadow] placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive dark:bg-input/30 motion-reduce:transition-none";

export const InputAmount = ({
  className,
  thousandSeparator = false,
  decimalSeparator = ".",
  prefix = "",
  suffix = "",
  decimalScale,
  fixedDecimalScale = false,
  allowNegative = false,
  onValueChange,
  ...props
}: InputAmountProps) => {
  if (
    typeof thousandSeparator === "string" &&
    thousandSeparator === decimalSeparator
  ) {
    throw new Error(
      "thousandSeparator and decimalSeparator must be different."
    );
  }
  return (
    <NumericFormat
      {...props}
      data-slot="amount-input"
      thousandSeparator={thousandSeparator}
      decimalSeparator={decimalSeparator}
      prefix={prefix}
      suffix={suffix}
      decimalScale={decimalScale}
      fixedDecimalScale={fixedDecimalScale}
      allowNegative={allowNegative}
      onValueChange={(values, sourceInfo) => {
        if (sourceInfo.source === "event") {
          onValueChange?.(values.value);
        }
      }}
      className={cn(fieldClassName, fieldSurfaceClassName, className)}
    />
  );
};

```
