React Currency Input: Intl-Formatierung ohne Cursor-Sprünge

Ein React Currency Input wirkt einfach, bis echte Menschen etwas eintippen. Jemand in Berlin tippt 1234,5 und erwartet 1.234,5, jemand in Chicago tippt 1234.5 und erwartet 1,234.5, und beide erwarten, dass der Cursor direkt hinter der Ziffer bleibt, die sie gerade getippt haben. Deine API will 1234.5 als Zahl und daneben einen Währungscode. Dieser Guide zeigt, was ein gutes Geldfeld können muss, warum der Cursor in selbst gebauten Versionen springt und wie du das mit Intl behebst, und wie du den Currency Input von Wingo UI in einem Formular einsetzt.
Was muss ein React Currency Input können?
Ein guter React Currency Input formatiert schon beim Tippen in der Locale des Nutzers, lässt den Cursor, wo er war, und gibt deinem Code eine reine Zahl. Im Detail sind es sieben Verhaltensweisen:
- Beim Tippen formatieren, in der Locale des Nutzers. Tausendertrennzeichen erscheinen, sobald Ziffern dazukommen (
1,234in en-US,1.234in de-DE), mit den Trennzeichen, dieIntl.NumberFormatfür diese Locale meldet. - Den Cursor stehen lassen. Ein eingefügtes Trennzeichen darf den Cursor nicht ans Ende des Felds werfen.
- Beide Dezimaltasten annehmen. Die Ziffernblöcke auf dem Smartphone zeigen je nach Systemsprache einen Punkt oder ein Komma. Beide sollten also zum Dezimaltrennzeichen der Locale werden, wenn sie am Ende getippt werden.
- Das Symbol dort hinsetzen, wo die Locale es hinsetzt.
$1,234.56in en-US,1.234,56 €in de-DE,1.234,56 leiin ro-RO. Das Symbol steht neben dem Text und ist nie Teil des bearbeitbaren Werts. - Die Nachkommastellen der Währung respektieren. USD hat 2, JPY hat 0, BHD hat 3. Ein Yen-Feld ignoriert die Dezimaltaste.
- Beim Blur festschreiben.
1,234.5wird zu1,234.50aufgefüllt und auf min und max begrenzt, wenn das Feld den Fokus verliert, nicht während jemand noch mitten in der Zahl steckt. - Ein sauberer Wert nach außen. Dein State und dein Formular bekommen
1234.5, nie"$1,234.50".
Warum springt der Cursor in einem formatierten Geldfeld?
Der Cursor springt, weil sich die Länge des Strings unter ihm ändert. Du tippst 5 hinter 1,234, und dein onChange schreibt 12,345. React setzt den Wert des Felds auf einen neuen String, der Browser kann nicht wissen, wohin deine alte Cursorposition darin gehört, und der Cursor landet am Ende. Hast du mitten in der Zahl getippt, geht der nächste Tastendruck an die falsche Stelle.
Die Lösung: Zähl Ziffern, nicht Zeichenpositionen. Vor dem Formatieren zählst du die Ziffern links vom Cursor. Nach dem Formatieren gehst du den neuen String durch und setzt den Cursor hinter dieselbe Anzahl Ziffern. Trennzeichen, die der Formatter hinzufügt oder entfernt, zählen nie mit, also verschieben sie ihn auch nie. Die kostenlosen number-format-Helfer liefern genau diesen Ansatz als digitsBeforeCaret und caretAfterDigits, dazu formatEditable (formatiert Text beim Tippen neu, ohne einen . oder Nullen am Ende zu verlieren), parseNumber (Parsing nach Locale) und getCurrencyParts (wohin das Symbol gehört).
Hier ein minimales Geldfeld, das nur auf diesen Helfern aufbaut:
getCurrencyParts("USD", "en-US") gibt { symbol: "$", position: "prefix", spaced: false, fractionDigits: 2 } zurück und getCurrencyParts("EUR", "de-DE") das Euro-Zeichen als Suffix mit Leerzeichen. Dieselbe Komponente ordnet sich also je nach Locale selbst an. Weil Trennzeichen und Symbolposition aus Intl kommen statt aus fest eingetragenen , und ., funktioniert ein Intl.NumberFormat Currency Input in Berlin und Chicago gleichermaßen. getCurrencyParts nimmt standardmäßig narrowSymbol, das Symbol, das Menschen tatsächlich schreiben: In ro-RO liefert die Standardanzeige symbol von Intl 1.234,56 RON, mit narrowSymbol steht dort 1.234,56 lei.
Probier die Helfer unten einzeln aus. Die Demo steht auf de-DE, also gruppiert der Punkt die Tausender und das Komma trennt die Dezimalstellen. Tipp mitten in der Zahl: Die Gruppierung ändert sich, der Cursor bleibt stehen, und die Zeilen zeigen die geparste Zahl.
$ npx wingo-ui@latest add number-formatDiese Skizze deckt den Normalfall ab. Sie füllt beim Blur nicht auf, begrenzt nicht, ändert den Wert nicht per Pfeiltaste und liest kein eingefügtes $1,234.56. In de-DE wird ein Punkt, der hinter 1.234 getippt wird, als weiteres Tausendertrennzeichen verworfen, statt zum Dezimalkomma zu werden. Backspace auf einem Trennzeichen fängt sie auch nicht ab: Der Formatter setzt das Trennzeichen sofort wieder ein, und die Taste wirkt tot. Dazu kommt ein leiserer Bug: Tippst du einen Buchstaben, gibt formatEditable denselben Text zurück, setText verwirft das Update, React schreibt den alten Wert zurück ins DOM, und der Cursor springt trotzdem ans Ende. Die vollständige Komponente behandelt all das. Den letzten Fall löst sie, indem sie den Cursor selbst zurücksetzt, wenn ein Tastendruck nichts ändert.
Wie nutzt du den Currency Input von Wingo UI?
Installier ihn mit npx wingo-ui@latest add currency-input und render ihn dann mit einer Locale, einem Wert und einem name für Formulare:
Tipp unten einen Betrag ein, klick dann auf den Währungs-Button und wähl einen anderen Code: Das Symbol ändert sich, der Betrag bleibt gleich.
$ npx wingo-ui@latest add currency-inputDie wichtigsten Props:
valueundonValueChangetragen den Betrag in der Haupteinheit (1234.56), mitnullfür ein leeres Feld.onValueChangewird live beim Tippen ausgelöst und ist bis zum Blur nicht begrenzt. Berücksichtige das bei der Validierung.localelegt die Trennzeichen und die Seite des Symbols fest. Lässt du sie leer, liest das Feld die Locale ausUIProvideroderLocaleProvider. Fehlen beide, rendert der Serveren-US, und der Browser wechselt nach der Hydration zur Sprache der Seite. Setz also eine Locale, wenn schon der erste Paint stimmen muss.defaultCurrency/currencynehmen einen ISO-4217-Code. Die Nachkommastellen folgen ihm (decimals={-1}, der Standard, bedeutet „die der Währung“), also tippt man bei USD zwei Nachkommastellen und bei JPY keine.currenciesmit zwei oder mehr Codes zeigt am Ende des Felds einen kompakten Button mit dem Code. Ein Währungswechsel rechnet den Betrag nie um; hat die neue Währung weniger Nachkommastellen, wird der Betrag darauf gerundet.min,maxundstepbegrenzen beim Blur oder bei Enter, markieren das Feld als ungültig, solange der Wert außerhalb liegt, und legen fest, was ArrowUp und ArrowDown addieren (mit Shift zehn Schritte).presetsrendert Schnellbeträge als Chips unter dem Feld, das Muster aus Spenden- und Trinkgeldformularen.allowNegativelässt ein Minus zu, für Erstattungen oder Saldokorrekturen.
Welchen Wert sendet der Currency Input ab?
Mit name rendert der Currency Input ein verstecktes Feld, dessen Wert der Betrag als "1234.56" mit Dezimalpunkt ist, oder leer, wenn das Feld leer ist. Das sichtbare Feld hat kein name-Attribut. currencyName ergänzt ein zweites verstecktes Feld mit dem gewählten Code. Ein einfaches Formular oder eine Server Action liest also Zahlen statt formatiertem Text:
Das versteckte Feld aktualisiert sich im selben Event wie der Tastendruck. Drückst du im Feld Enter, wird der Betrag aufgefüllt und der neue Wert in einem Zug abgeschickt. Runde, schneide nie ab: 1.15 * 100 ergibt in JavaScript 114.99999999999999, also liefert Math.trunc 114, wo Math.round 115 liefert. Das * 100 setzt eine Währung mit zwei Nachkommastellen voraus; für JPY oder BHD multiplizierst du stattdessen mit 10 ** decimals.
Wie schlägt sich der Currency Input gegen react-currency-input-field und einen shadcn Currency Input?
react-currency-input-field ist das kostenlose, spezialisierte Paket, react-number-format der Allzweck-Formatter, shadcn/ui hat kein Geldfeld, und die Komponente von Wingo UI ist eine Pro-Quelldatei mit eingebauter Währungsauswahl und eingebautem Smartphone-Verhalten. Stand Oktober 2026, laut npm und der Doku der jeweiligen Projekte:
Suchst du ein kostenloses npm-Paket für ein React Currency Input Field und stylst es gern selbst, ist react-currency-input-field die spezialisierte Wahl: Lies für die Zahl values.float aus seinem onValueChange, denn das erste Argument ist ein String, und setz disableAbbreviations, wenn ein getipptes k nicht Tausend bedeuten soll. Suchst du nach einem shadcn Currency Input oder einem Beispiel für einen React Money Input, ist die übliche Abkürzung ein Input mit type="number" und einem Dollar-Icon in einer Input Group. Das bringt dir weder Gruppierung noch Locale. Die Komponente von Wingo UI passt, wenn du das Feld, den fertigen Aufbau mit Label und Fehlermeldung und das Smartphone-Verhalten in einer Datei willst, die dir gehört.
Wie funktioniert der Currency Input auf dem Smartphone und mit der Tastatur?
Auf Smartphones nutzt der Currency Input inputMode="decimal" für den Dezimal-Ziffernblock und enterKeyHint="done", und es nimmt beide Trenntasten an, weil der Ziffernblock das Zeichen der Systemsprache zeigt. Der Text ist 16px groß, damit iOS Safari nie hineinzoomt, das Feld in Größe md wächst auf Touchscreens auf 44px, und der Währungs-Button und die Preset-Chips haben Tippflächen von 44px. Unter 768px öffnet sich die Währungsliste als Bottom Sheet statt als Popover (responsive={false} behält das Popover). Die Dezimal-Ziffernblöcke von iOS haben keine Minustaste. Mit allowNegative entsteht ein negativer Betrag also über ArrowDown oder eine Hardware-Tastatur.
Bei Tastaturbedienung markiert das Feld beim Fokus den ganzen Betrag, sodass Tippen ihn ersetzt (selectOnFocus, standardmäßig an). ArrowUp und ArrowDown ändern den Wert um einen Schritt, Enter übernimmt den Wert und sendet das Formular trotzdem ab, und mit clearable leert Escape das Feld. Auf dem Währungs-Button öffnen Enter, Leertaste oder die Pfeiltasten die Liste, in der ein getippter Buchstabe zu einer Währung springt („u“ zu USD). Das Symbol ist aria-hidden, und ein visuell verstecktes „Amount in US dollars“ ist über aria-describedby verknüpft. Ein Screenreader hört die Währung also einmal zusammen mit dem Feld statt eines verirrten Dollarzeichens.
Solltest du für Geld lieber den Number Input nehmen?
Nimm den Number Input, wenn Leute einen Wert schrittweise ändern, und den Currency Input, wenn sie einen Betrag neu eintippen. Der Number Input nimmt ebenfalls eine Prop currency und formatiert mit Intl, arbeitet aber andersherum: Im Ruhezustand zeigt er $125,000, beim Fokus wird daraus die reine Zahl zum Bearbeiten. Er ergänzt Stepper-Buttons, die beim Gedrückthalten wiederholen, das Mausrad und Home / End, um zu min und max zu springen. Das passt zu einer Menge, einem Budget oder einem Preis, den du in Schritten von 1.000 anpasst. Für einen Betrag, den Leute neu eintippen, etwa eine Zahlung, eine Spende oder den Preis eines Inserats, passen die Live-Gruppierung, die Währungsauswahl und die Presets des Currency Input besser.
Welche Fallen bei Währungsfeldern zeigen sich in Produktion?
Die meisten Geld-Bugs entstehen, wenn der formatierte Text als Daten behandelt wird oder mit rohen Gleitkommazahlen gerechnet wird. Die üblichen:
- Den formatierten String speichern.
"$1,234.50"lässt sich weder summieren noch vergleichen. Speichere die Zahl und den Währungscode als zwei Felder. - Gleitkommarechnung mit Geld.
0.1 + 0.2ergibt in JavaScript0.30000000000000004. Runde vor dem Speichern auf die Nachkommastellen der Währung oder speichere ganzzahlige Untereinheiten. Die number-format-Helfer enthaltenroundTo(0.1 + 0.2, 2), das0.3liefert. - Beim Tippen begrenzen. Wer
5bei jedem Tastendruck auf ein Minimum von10hochsetzt, macht50untippbar. Markier den Wert beim Tippen als ungültig und begrenz ihn beim Blur. - Mit
parseFloatparsen.parseFloat("1.234,56")ergibt1.234undNumber("1,234.56")ergibtNaN. Parse mit den Trennzeichen der Locale (parseNumber(text, locale)) oder lies das versteckte Feld, das immer einen Dezimalpunkt hat. - Mit undefinierter Locale formatieren.
new Intl.NumberFormat()nimmt auf dem Server die Sprache des Servers und im Browser die des Browsers.1,234.5und1.234,5passen dann nicht zusammen, und die Hydration scheitert. Übergib eine Locale oder umschließ die App mitLocaleProvideraus@/hooks/use-locale. - Beim Währungswechsel umrechnen. Wer in einem Formular die Währung wechselt, korrigiert meist nur eine falsche Auswahl. Behalte den Betrag und lass die Person ihn bearbeiten.
Für Rechnungen, bei denen jeder Zeilenbetrag in eine Mehrwertsteuersumme fließt, zeigt unser Guide zur Mehrwertsteuerberechnung für e-Factura das Runden pro Steuersatz. Das Geldfeld ist eines von vielen Eingabefeldern in unserem Guide zu React-Formularkomponenten, und weitere Deep Dives zu einzelnen Komponenten findest du unter Komponenten-Guides.
Wie installierst du den Currency Input?
Führ npx wingo-ui@latest add currency-input aus. Der Befehl kopiert den Currency Input und die kostenlosen Teile, auf denen er aufbaut (Field, Select, Button und die Helfer number-format, use-locale und motion), und installiert die npm-Pakete, die sie importieren, etwa motion, lucide-react und vaul für das Sheet auf dem Smartphone. Currency Input und Number Input gehören zu Wingo UI Pro, also meld dich zuerst mit npx wingo-ui@latest login an; npx wingo-ui@latest add number-format allein ist kostenlos.
Häufige Fragen
Wie formatiere ich einen Currency Input in React?
Halte den Text im State, formatiere ihn bei jeder Änderung neu mit den Intl-Trennzeichen der Locale und setz den Cursor zurück, indem du die Ziffern davor zählst und ihn im neuen Text hinter dieselbe Anzahl setzt. Speichere die geparste Zahl getrennt und füll die Nachkommastellen erst beim Blur auf.
Warum springt der Cursor in meinem Währungsfeld ans Ende?
Wenn onChange einen neuen String mit einem hinzugefügten oder entfernten Tausendertrennzeichen schreibt, kann der Browser die alte Cursorposition nicht darauf abbilden und setzt den Cursor ans Ende. Zähl die Ziffern vor dem Cursor, formatiere neu und ruf dann in einem useLayoutEffect setSelectionRange hinter derselben Ziffernzahl auf.
Gibt es einen shadcn Currency Input?
Nicht in der offiziellen Komponentenliste von shadcn/ui, Stand Oktober 2026. Dort gibt es Input und Input Group, aber keinen Währungs- oder Zahlen-Input. Eine verbreitete Abkürzung ist ein Input mit type="number" und einem Dollar-Icon in einer Input Group. Das bringt dir weder Gruppierung noch Locale.
Sollte ich type="number" für ein Geldfeld in React nehmen?
Nein. Ein Number Input kann weder Tausendertrennzeichen noch ein Währungssymbol zeigen, sein Wert ist ein leerer String, solange der Text ungültig ist, und das Mausrad kann ihn aus Versehen ändern. Nimm ein Textfeld mit inputMode="decimal" und parse den Text selbst.
Wie speichere ich einen Geldbetrag?
Speichere die Zahl und den ISO-4217-Währungscode als zwei Felder, nie den formatierten String. Viele Teams speichern in der Datenbank ganzzahlige Untereinheiten (Cent), um Gleitkommarechnung zu vermeiden. Behältst du in JavaScript Nachkommastellen, runde vor dem Speichern auf die Nachkommastellen der Währung.
- Formulare
- Währungsfeld
- Intl
- React


