React Number Flow Input: Animate Digits When Typing

Description:

React Number Flow Input is a zero-dependency React number input component that creates editable animated numbers with digit rolls, formatted separators, and native form support.

It works well for price fields, quantity inputs, dashboard controls, financial forms, and any React number input example where the value should feel more polished than a plain <input type="number">.

Features

  • Animates digits as users type.
  • Rolls changed digits with a barrel wheel effect.
  • Formats large numbers with locale separators.
  • Accepts controlled and uncontrolled values.
  • Handles decimal limits and negative values.
  • Preserves raw numeric text for precision-sensitive fields.
  • Mirrors values into native form submissions.
  • Injects scoped styles on first mount.

Use Cases

  • Pricing forms get animated currency-style numeric fields.
  • Dashboard controls display live numeric edits with clear feedback.
  • Checkout forms collect quantities, totals, discounts, and limits.
  • Finance apps keep exact raw values for large numbers.

How To Use It

Install React Number Flow Input

Install the package with package managers

npm install @daformat/react-number-flow-input
yarn add @daformat/react-number-flow-input
pnpm add @daformat/react-number-flow-input
bun add @daformat/react-number-flow-input
deno add npm:@daformat/react-number-flow-input

Use the component inside a client component when you work in the Next.js App Router. The input handles user events and DOM animation, so the file should include "use client".

Quick Start Usage

This minimal setup renders an animated numeric field with grouped thousands and a change callback.

"use client";
import { NumberFlowInput } from "@daformat/react-number-flow-input";
export default function RevenueInput() {
  return (
    <NumberFlowInput
      defaultValue={8750}
      format
      // Receives the parsed number after each valid edit.
      onChange={(value) => console.log("Revenue value:", value)}
    />
  );
}

Example 1: Controlled Price Input

Use a controlled value when the number belongs to React state, a form store, or a server action draft.

"use client";
import { useState } from "react";
import { NumberFlowInput } from "@daformat/react-number-flow-input";
export function ProductPriceField() {
  const [price, setPrice] = useState<number | undefined>(149.99);
  return (
    <label className="grid gap-2">
      <span className="text-sm font-medium">Product price</span>
      <NumberFlowInput
        value={price}
        // Updates React state with the parsed numeric value.
        onChange={setPrice}
        // Adds grouped separators to the visible number.
        format
        // Keeps the decimal part to two digits during editing.
        decimalScale={2}
        // Adds a custom wrapper class for your app stylesheet.
        className="rounded-md border px-3 py-2 text-lg"
        placeholder="0.00"
      />
    </label>
  );
}

Example 2: Percentage Input with Validation

Use isAllowed when the user can type only a specific numeric range.

"use client";
import { NumberFlowInput } from "@daformat/react-number-flow-input";
export function DiscountInput() {
  return (
    <NumberFlowInput
      defaultValue={15}
      decimalScale={1}
      maxLength={5}
      placeholder="Discount percent"
      // Rejects edits outside the accepted range.
      isAllowed={(value) => value == null || (value >= 0 && value <= 100)}
      // Runs only after a valid edit passes validation.
      onChange={(value) => console.log("Accepted discount:", value)}
    />
  );
}

Example 3: Native Form Submission

Use name, required, min, and max when the field should participate in a normal HTML form submission.

export function InventoryForm() {
  return (
    <form action="/products/update-stock" method="post" className="grid gap-4">
      <label className="grid gap-2">
        <span>Stock quantity</span>
        <NumberFlowInput
          name="stock"
          defaultValue={24}
          required
          min={0}
          max={5000}
          format
          // The visible editable value stays animated.
          className="rounded border px-3 py-2"
        />
      </label>
      <button type="submit">Save stock</button>
    </form>
  );
}

Example 4: High-Precision Numeric Text

Use onChangeText when JavaScript number precision can damage the value. This matters for large IDs, token amounts, exact currency strings, and big-decimal workflows.

"use client";
import { useState } from "react";
import { NumberFlowInput } from "@daformat/react-number-flow-input";
export function LedgerAmountField() {
  const [rawAmount, setRawAmount] = useState("1000000000000000000.00");
  return (
    <div className="grid gap-2">
      <NumberFlowInput
        value={rawAmount}
        format
        decimalScale={2}
        // Keeps the exact raw string from the input.
        onChangeText={setRawAmount}
        // The parsed number remains available for normal numeric workflows.
        onChange={(value) => console.log("Parsed value:", value)}
      />
      <code>Stored raw value: {rawAmount}</code>
    </div>
  );
}

Example 5: Locale-Aware Number Formatting

Set locale when your UI needs region-specific group and decimal characters.

"use client";
import { NumberFlowInput } from "@daformat/react-number-flow-input";
export function LocalizedBudgetInput() {
  return (
    <NumberFlowInput
      defaultValue={1234567.89}
      format
      locale="de-DE"
      decimalScale={2}
      // Accepts both "." and the locale decimal character during input.
      onChangeText={(raw) => console.log("Raw internal value:", raw)}
    />
  );
}

Configuration Options

  • value (number | string | undefined): Sets the controlled value. External changes animate by default after the first mount.
  • defaultValue (number | string): Sets the uncontrolled starting value.
  • onChange ((value: number | undefined) => void): Runs with the parsed number. Intermediate text states return undefined.
  • onChangeText ((rawText: string) => void): Runs with the raw numeric string. Use it for exact precision storage.
  • animateOnValueChange (boolean): Controls animation for external value prop updates. Defaults to true.
  • format (boolean | (raw: string) => string): Adds grouped formatting through Intl.NumberFormat or a custom formatter.
  • locale (string | Intl.Locale): Sets decimal and group separators for formatted display.
  • decimalScale (number): Limits fractional digits. A value of 0 blocks decimal entry.
  • autoAddLeadingZero (boolean): Converts .5 to 0.5 and -.5 to -0.5.
  • allowNegative (boolean): Accepts a leading negative sign.
  • maxLength (number): Limits the raw input length before formatting.
  • minLength (number): Passes the minimum length constraint to the hidden native input.
  • min (number): Passes the minimum numeric constraint to the hidden native input.
  • max (number): Passes the maximum numeric constraint to the hidden native input.
  • isAllowed ((value: number | null) => boolean): Validates each proposed edit before the component commits it.
  • id (string): Sets the component ID.
  • name (string): Sets the hidden native input name for form submission.
  • form (string): Associates the hidden native input with a form ID.
  • required (boolean): Marks the hidden native input as required.
  • placeholder (string): Sets placeholder text on the editable display.
  • className (string): Applies a class to the root wrapper.
  • style (React.CSSProperties): Applies inline styles to the root wrapper.
  • onFocus (() => void): Runs when the editable display receives focus.
  • onBlur (() => void): Runs when the editable display loses focus.
  • autoFocus (boolean): Focuses the editable display on mount.

API Methods

// React Number Flow Input exposes no plugin-style public methods.
// Use React props for updates and a forwarded ref for direct DOM access.
import { useRef } from "react";
import { NumberFlowInput } from "@daformat/react-number-flow-input";
export function FocusableAmountInput() {
  const amountRef = useRef<HTMLElement>(null);
  function focusAmountField() {
    // Focuses the contenteditable element.
    amountRef.current?.focus();
  }
  return (
    <>
      <NumberFlowInput ref={amountRef} defaultValue={3200} format />
      <button type="button" onClick={focusAmountField}>
        Focus amount
      </button>
    </>
  );
}

Events and Callbacks

<NumberFlowInput
  defaultValue={2500}
  format
  // Runs after the component parses a valid numeric value.
  onChange={(value) => {
    console.log("Parsed number:", value);
  }}
  // Runs with the exact raw numeric text.
  onChangeText={(rawText) => {
    console.log("Raw text:", rawText);
  }}
  // Runs when the editable display receives focus.
  onFocus={() => {
    console.log("Amount field focused");
  }}
  // Runs when the editable display loses focus.
  onBlur={() => {
    console.log("Amount field blurred");
  }}
/>

Alternatives and Related Resources

FAQs

Q: Does React Number Flow Input support Next.js App Router?
A: Yes. Use it inside a client component. The component handles DOM animation and input events after hydration.

Q: Why does onChange round very large numbers?
A: onChange returns a JavaScript number. Use onChangeText and string values when you need exact digits beyond Number.MAX_SAFE_INTEGER.

Q: Can I use it for currency inputs?
A: Yes. Use format, decimalScale={2}, and a custom formatter for a currency prefix. Store exact currency values with onChangeText when precision matters.

Q: Does it need a CSS import?
A: No. The component injects minimal scoped styles on first mount. You can customize the visible editable element through its data attributes.

Add Comment