XGitHub

NumberFlow Integration

Compose NumoraInput with @number-flow/react to add animated digit transitions to numeric displays - without compromising the precision guarantees of your editable field.

$0.00

Why compose, not replace

NumoraInput deliberately ships zero animation. The editable field manipulates values as strings only and updates synchronously on every keystroke. @number-flow/react is a display-only animator that takes a JavaScript number and tweens each digit between renders.

Use them together: NumoraInput owns the editable string, NumberFlow renders the animated readout. The animation seam is the only place where you cross the string → number boundary, and only for display digits the user is reading (not editing).

Precision boundary: @number-flow/react requires a number prop. Converting via Number(rawValue) is safe for values within the IEEE-754 safe range (≤ 15 significant digits) which covers the vast majority of UI read-outs. Do not round-trip back to a string for the editable field - keep the raw string as the source of truth and use it directly anywhere precision matters (balances, swaps, transfers).

Installation

pnpm add numora-react @number-flow/react
# or
npm install numora-react @number-flow/react

Pattern 1 - Display animation alongside the input

The recommended pattern. The user types into a NumoraInput; a separate NumberFlow elsewhere on the page (a converted amount, a running total, a portfolio balance) animates as the value changes. Both views share the same raw string state.

import { useState } from 'react'
import { NumoraInput, NumoraInputChangeEvent } from 'numora-react'
import NumberFlow from '@number-flow/react'

function SwapForm() {
  const [amount, setAmount] = useState('')

  // The animated readout reads a number, but the input keeps the raw string.
  const displayValue = amount === '' ? 0 : Number(amount)
  const exchangeRate = 1850.42
  const converted = displayValue * exchangeRate

  return (
    <div>
      <label>
        You pay (ETH)
        <NumoraInput
          value={amount}
          onChange={(e: NumoraInputChangeEvent) => setAmount(e.target.value)}
          decimalMaxLength={6}
          thousandSeparator=","
          placeholder="0.0"
        />
      </label>

      <div>
        You receive (USD):{' '}
        <NumberFlow
          value={converted}
          format={{ style: 'currency', currency: 'USD', maximumFractionDigits: 2 }}
        />
      </div>
    </div>
  )
}

The input itself snaps (no animation) - typing latency is unchanged. Only the converted-amount readout animates. This is the cheapest integration path and keeps the precision contract intact: the editable string never round-trips through Number.

Pattern 2 - Blur-mode swap (advanced)

If you want the editable field itself to look animated when the user isn't typing, swap the input out for an animated display on blur. On focus, swap back.

import { useState } from 'react'
import { NumoraInput, FormatOn } from 'numora-react'
import NumberFlow from '@number-flow/react'

function AnimatedField() {
  const [amount, setAmount] = useState('')
  const [editing, setEditing] = useState(false)

  if (editing) {
    return (
      <NumoraInput
        autoFocus
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
        onBlur={() => setEditing(false)}
        formatOn={FormatOn.Blur}
        decimalMaxLength={2}
        thousandSeparator=","
      />
    )
  }

  return (
    <button type="button" onClick={() => setEditing(true)}>
      <NumberFlow
        value={amount === '' ? 0 : Number(amount)}
        format={{ minimumFractionDigits: 2 }}
      />
    </button>
  )
}

Tradeoff: the visual context-switch on focus/blur is more jarring than always-on display animation. The animated display also can't show partial states like a trailing decimal ("1.") - it animates to whatever number the string parses to. Use FormatOn.Blur on the input so the displayed value matches what was just typed.

Pattern 3 - Read-only animated totals

For read-only sections of a page (a portfolio summary that updates from a live price feed, an aggregated total across several inputs), skip NumoraInput entirely and use NumberFlow directly.

import NumberFlow from '@number-flow/react'

function PortfolioTotal({ totalUsd }: { totalUsd: number }) {
  return (
    <div>
      Total balance:{' '}
      <NumberFlow
        value={totalUsd}
        format={{ style: 'currency', currency: 'USD' }}
      />
    </div>
  )
}

This isn't really an integration - it's just the right tool for read-only animated numbers. Use it wherever the value is a number that already exists in your state and you don't need string-precision math at the display point.

What about animating the input itself?

NumoraInput does not animate digits inside the editable field, by design. The two libraries it could compete with - @daformat/react-number-flow-input - implement digit-roll animations directly on a contenteditable with absolutely-positioned overlay wheels. That approach adds roughly 3,500+ lines of animation-only code, requires switching off real <input> elements (losing native undo, IME, and accessibility primitives), and re-implements platform behaviour that numora delegates to the browser.

Numora's position: keep the editable element a real <input>, keep typing snappy, and compose with @number-flow/react when display animation is wanted. Use Pattern 1 by default and Pattern 2 only when the visual identity of "everything animates" matters more than the focus/blur context-switch.

Key points

  • NumoraInput stays the source of truth. Keep the raw string in state and pass it to anything that needs precision (math libraries, API calls, blockchain transactions).
  • Convert to Number only at the display seam. Number(rawValue) is safe for ≤ 15 significant digits - fine for human-readable readouts, not fine for token amounts with 18 decimals at full precision.
  • No peer dependency. numora-react doesn't require @number-flow/react. Install it only if you want animated displays.
  • Pattern 1 is the default. Animate readouts, not the editable field. Cheapest, safest, no API change to numora.
  • RSC compatibility: @number-flow/react is a client component (uses DOM APIs and ResizeObserver). Mark any page using it with 'use client' in Next.js App Router.