Rxova
Skip to content

React Inputs

Lightweight, accessible and UX-first, with zero runtime dependencies. Every release is tested end-to-end in three browsers, scanned against WCAG, and held to a bundle budget CI enforces on every commit.

One import to your first input

No stylesheet to import. No provider to mount. No configuration. Install it and render it.

Install

npm install @rxova/react-inputs

Use

import { CurrencyInput } from '@rxova/react-inputs'
export function Price({ value, onChange }) {
// `name` is all it takes to post with the form.
return (
<CurrencyInput name="price" locale="de-DE" currency="EUR" value={value} onChange={onChange} />
)
}

Why this one

Small on purpose, deep where it counts

A short list of inputs, each taken all the way — formatting, caret behaviour, accessibility and native form support treated as the whole job rather than the last mile.

  • Maintained, in the open

    Shipped under the Rxova organisation alongside Journey and use-everywhere. Changesets-driven releases, provenance-signed npm publishes, an API reference generated from source.

  • Lightweight, and held to it

    Every package carries a size budget that CI enforces on each build. Exceed it and the build fails — the number on this page is that budget, not an estimate.

  • No dependencies to inherit

    react is the only peer. Nothing else reaches your bundle, nothing else can break you, and everything tree-shakes down to what you actually imported.

  • Accessible by construction

    Native platform semantics do the work — an interactive rating is a real radiogroup, and an OTP field is one real input. Keyboard, focus and form behaviour come from the browser, and every build is checked with axe.

  • UX-first, down to the caret

    The caret stays where you put it while the value reformats underneath. Paste works. Autofill works. So do IME composition, RTL and server rendering.

  • Correct on the seams

    Bulgarian's space-only-above-9999 grouping, Hindi lakh grouping, Arabic-Indic digits, formatted paste, Chrome's auto-translate rewriting your DOM. The cases that only show up in production.

Proof

Numbers you can go and check

Every figure below is read out of the config that enforces it, at the moment this page is built. None of them is typed by hand, so none of them can quietly go stale.

  • 0

    Runtime dependencies

    react is the only peer, across every package

  • 10 kB

    Whole suite, Brotli

    Enforced by size-limit in CI — 5 kB for a single tree-shaken component

  • 95%

    Coverage floor, per file

    Per-file thresholds, so a thin module cannot hide behind a well-covered one

  • 3

    Engines under E2E

    chromium, firefox, webkit

  • 0

    Accessibility violations

    axe-core against wcag2a, wcag2aa, wcag21a, wcag21aa — including RTL and error states

  • MIT

    Licensed, every package

    Permissive and unchanged since the first release — use it at work without asking anyone

See for yourself

This is not a screenshot. Edit it.

Switch the locale and watch the grouping, symbol placement, decimal rules and digit system follow — all from Intl, none of it from a regex. Then drag the rating and paste into the code field. The source underneath is editable too.

Editable — try changing it
function PlayWithMe() {
// de-DE German · bg-BG space only above 9999 · hi-IN lakh grouping
// ar-EG native digits · ja-JP no decimals
const CURRENCY = { 'de-DE': 'EUR', 'bg-BG': 'BGN', 'hi-IN': 'INR', 'ar-EG': 'EGP', 'ja-JP': 'JPY' }
const [locale, setLocale] = useState('de-DE')
const [score, setScore] = useState(3.5)
const [code, setCode] = useState('')
return (
  <div style={{ display: 'grid', gap: 24, justifyItems: 'start' }}>
    <p style={{ margin: 0, fontSize: 20, fontWeight: 600 }}>Play with me 👋</p>
    <label>
      Locale{' '}
      <select value={locale} onChange={(e) => setLocale(e.target.value)}>
        {Object.keys(CURRENCY).map((l) => <option key={l}>{l}</option>)}
      </select>
    </label>
    <CurrencyInput
      aria-label="Amount"
      locale={locale}
      currency={CURRENCY[locale]}
      defaultValue={5000000}
    />
    <Rating value={score} onChange={setScore} precision={0.5} label="Rate it" />
    <OtpInput value={code} onChange={setCode} length={6} label="One-time code" />
  </div>
)
}

Every input has a playground like this one on its own page, alongside the full prop reference. See the whole suite →

Budgets

A ceiling, not an aspiration

Take the whole suite or take one input — either way you know the cost before you install, because the build fails when it moves.

Read from each package's size-limit configuration when this page was built. CI checks the same numbers on every commit.
PackageYou importBrotli
@rxova/react-intl-currency-input{ CurrencyInput }≤ 3.25 kB
@rxova/react-intl-currency-input{ useCurrencyInput }≤ 3.25 kB
@rxova/react-date-input{ DateInput }≤ 4 kB
@rxova/react-date-input{ useDateInput }≤ 3 kB
@rxova/react-file-input{ FileInput }≤ 4.5 kB
@rxova/react-file-input{ useFileInput }≤ 3.5 kB
@rxova/react-otp-input{ OtpInput }≤ 4.5 kB
@rxova/react-otp-input{ useOtpInput }≤ 2.8 kB
@rxova/react-password-input{ PasswordInput }≤ 5 kB
@rxova/react-password-input{ usePasswordInput }≤ 3.25 kB
@rxova/react-password-input{ estimateStrength }≤ 1.5 kB
@rxova/react-phone-input{ PhoneInput }≤ 4.55 kB
@rxova/react-phone-input{ usePhoneInput }≤ 3.7 kB
@rxova/react-phone-input{ parsePhone }≤ 3 kB
@rxova/react-rating-input{ Rating }≤ 3 kB
@rxova/react-rating-input{ useRating }≤ 1.25 kB
@rxova/react-tags-input{ TagsInput }≤ 4 kB
@rxova/react-tags-input{ useTagsInput }≤ 3 kB
@rxova/react-time-input{ TimeInput }≤ 4 kB
@rxova/react-time-input{ useTimeInput }≤ 3.25 kB

Headless

Style it with CSS, not with props

There is no stylesheet to import and no theme object to configure. Every component reads its appearance from custom properties and exposes its state as data attributes, so it inherits whatever your app already looks like.

.field {
--rx-otp-border: 1px solid var(--my-border);
--rx-otp-radius: 8px;
--rx-otp-active-ring: 2px solid var(--my-brand);
}
.field [data-filled] {
font-weight: 600;
}