Shadcn Number Input: Stepper, Min, Max und Dezimalzahlen
Du brauchst ein Feld für eine Menge, ein Gewicht oder einen Preis, suchst nach einem shadcn number input und stellst fest: Es gibt keinen. Die Liste von shadcn/ui hat Input, Input Group und Field (geprüft im November 2026), also greifst du zu <Input type="number" />. In der Demo klappt das, dann kommen echte Nutzer: Jemand scrollt über dem fokussierten Feld, und der Preis sinkt um 3. Jemand anderes tippt 2,5 in einem Browser, der das Komma ablehnt, und dein State bekommt "" statt einer Zahl. Dieser Guide zeigt, was type="number" falsch macht, was ein gutes Zahlenfeld stattdessen tut und wie du eins baust: mit dem Number Input von Wingo UI oder mit dem kostenlosen Input, wenn dir die Grundlagen reichen.
Warum enttäuscht type="number" in einem shadcn-Formular?
Weil es für Werte gedacht ist, die man mit einem Spinner hoch- und runterzählt, während die meisten Formulare Werte abfragen, die Leute eintippen. Der Hinweis zur Barrierefreiheit bei MDN rät selbst, darauf zu verzichten, wenn der Spin-Button keine Rolle spielt, und warnt davor, dass Nutzer eine Zahl versehentlich erhöhen (MDN, input type="number" (opens in a new tab), gelesen im November 2026). Das Team des GOV.UK Design System ist 2020 davon abgerückt, nachdem es Probleme bei der Bedienbarkeit gefunden hatte, darunter Bugs mit den assistiven Technologien Dragon und NVDA (GDS-Blog, Februar 2020 (opens in a new tab)). Diese Probleme triffst du in einer React-App:
- Das Mausrad kann den Wert ändern. Du fokussierst das Feld, scrollst, um den Rest des Formulars zu lesen, und in manchen Browsern verschiebt sich die Zahl. Das merkt niemand, bis die Bestellung falsch ist.
- Die Pfeile sind winzig. Die nativen Spin-Buttons sind auf dem Desktop ein kleines Pfeilpaar und fehlen in Browsern auf dem Smartphone meist ganz. Als Hauptbedienung taugen sie also nicht, und auf dem Desktop machen sie das Feld trotzdem unruhig.
- Dezimalzahlen hängen von Browser und step ab. Mit dem Standardwert
step="1"ist2.5ungültig. Ein Number Input mit Komma als Dezimaltrennzeichen klappt in manchen Browsern für manche Locales, in anderen nicht. - Der Wert ist ein String, und manchmal ist er leer. Solange der Text keine gültige Zahl ist (
-,1e,2,5, wo das Komma nicht akzeptiert wird), istevent.target.valuegleich"". Das Feld zeigt den Text weiter an, aber dein State, deine Validierung und alles, was daraus abgeleitet ist, sehen ein leeres Feld. - Keine Formatierung.
12,500oder2.5 kgkannst du darin nicht anzeigen, nur die rohen Ziffern. - min und max verhindern kein Tippen. Sie begrenzen nur die Pfeile. Eine getippte
5000in einem Feld mitmax="100"bleibt stehen und wird von der Constraint Validation als ungültig markiert. Code, der den Wert aus dem State liest, oder ein Formular mitnoValidatebekommt ihn trotzdem.
Wie entferne ich die Pfeile aus einem shadcn number input?
Wenn du bei type="number" bleibst, entfernst du die Pfeile mit einer Arbitrary Property und zwei Arbitrary Variants von Tailwind v4:
appearance: textfield blendet die Pfeile des shadcn number input in Firefox aus, die beiden WebKit-Pseudo-Elemente erledigen das in Chrome und Safari. Damit ist nur die Optik repariert. Mausrad, ArrowUp und ArrowDown ändern den Wert weiterhin, und die Probleme mit Strings und Dezimalzahlen bleiben. Wenn du die Pfeile ohnehin entfernst, ist es sauberer, type="number" ganz aufzugeben.
Was sollte ein guter Number Input stattdessen tun?
Nimm einen Text-Input mit Zifferntastatur und gib ihm das Verhalten, das Leute von einem Stepper erwarten:
- Ein Text-Input mit
inputMode.inputMode="decimal"öffnet auf dem Smartphone eine Zifferntastatur mit Dezimaltaste,numeric, wenn nur ganze Zahlen sinnvoll sind. Keine Pannen mit dem Mausrad, keine winzigen Pfeile. - Parsing in der Locale der Person.
1,234.5in en-US und1.234,5in de-DE sind dieselbe Zahl, und2,5von einer Smartphone-Tastatur mit Komma sollte funktionieren. - Echte Buttons für + und -. Groß genug zum Antippen (44px auf Touchscreens), mit Wiederholung beim Gedrückthalten, die nach einer kurzen Pause startet und schneller wird.
- Tastatur wie bei einem Spinbutton. ArrowUp und ArrowDown ändern um einen Schritt, PageUp und PageDown um einen großen Schritt, Home und End springen zu min und max, dazu
role="spinbutton"undaria-valuenow, damit Screenreader den Wert ansagen. - min, max und step beim Übernehmen. Lass freies Tippen zu, zeig einen Fehlerring, solange der Wert außerhalb des Bereichs liegt, und begrenze und runde auf den Schritt, wenn das Feld den Fokus verliert.
- Formatierung im Ruhezustand. Zeig
$485,000oder2.5 kg, solange das Feld inaktiv ist, und die reine Zahl beim Bearbeiten. - Eine Zahl als Ergebnis, kein Text. Dein State bekommt
85odernull, nie"85 m²".
Wie baue ich das mit dem Number Input von Wingo UI?
Installier ihn mit der CLI. Die Field-Anatomie und die number-format-Helfer kommen gleich mit:
Probier die Demo unten aus: Tipp einen Wert über 1,000 ein, drück dann Tab und sieh zu, wie er zurückspringt. Halt den Button + gedrückt, dann merkst du, wie die Wiederholung schneller wird.
$ npx wingo-ui@latest add number-inputDie wichtigen Props sind min, max, step, decimals, steppers und format. Hier drei typische Felder: eine Menge, ein Gewicht mit einer Nachkommastelle und ein Preis:
Ein paar Details, die du kennen solltest:
steplegt auch die Genauigkeit fest.step={0.1}behält eine Nachkommastelle und schaltet die Smartphone-Tastatur auf Dezimal um. Ein React Number Input mit Dezimalzahlen braucht also kein zusätzliches Prop.decimalsüberschreibt das, wenn Schritt und Genauigkeit auseinandergehen.- Schritte zählen ab
min. Mitmin={1}undstep={5}geht + auf 1, 6, 11. Ein Wert, der nicht auf dem Raster liegt, springt zuerst auf den nächsten Rasterwert. clampBehaviorbestimmt, wie streng min und max sind. Der Standardblurlässt dich außerhalb des Bereichs tippen, zeigt den roten Ring und begrenzt beim Verlassen.strictverweigert einen Tastendruck, der den Wert nur weiter aus dem Bereich schieben kann.nonebegrenzt nie, für den Fall, dass dein Schema validiert.onValueChangeoderonValueCommit. Der erste feuert bei jedem Tastendruck und jedem Stepper-Schritt. Der zweite feuert einmal pro Bearbeitung (beim Verlassen, bei Enter, beim Loslassen eines gehaltenen Steppers). Dorthin gehört ein fetch oder eine Neuberechnung des Preises.- Das Mausrad lässt sich abschalten. Es ändert den Wert nur, solange das Feld fokussiert ist und der Mauszeiger darüber liegt, und die Seite scrollt in diesem Moment nicht. Übergib
wheel={false}für Felder in langen Formularen, durch die man scrollt. namesendet eine Zahl. Ein versteckter Input trägt"12.5"mit Dezimalpunkt.FormDataund Server Actions bekommen also einen Wert, den du anNumber()übergeben kannst, egal in welcher Locale das Feld anzeigt.
Dasselbe Feld parst so, wie die Locale Zahlen schreibt. Hier läuft es auf Deutsch mit halben Schritten, also tipp 12,5 und drück Tab:
$ npx wingo-ui@latest add number-inputIst er barrierefrei und auf dem Smartphone gut bedienbar?
Das Feld ist ein echtes <input> mit role="spinbutton", aria-valuenow, aria-valuemin, aria-valuemax und einem aria-valuetext, das den formatierten Wert vorliest. Ein Screenreader sagt also „85 m²“ statt „85“. Die Buttons für + und - haben zugängliche Namen (incrementLabel und decrementLabel, standardmäßig „Increase“ und „Decrease“) und werden an den Grenzen gedimmt. Label, Beschreibung und Fehler kommen aus der gemeinsamen Field-Anatomie, verknüpft über htmlFor und aria-describedby, sodass sich das Feld wie jeder andere Input ins Formular einfügt.
Auf dem Smartphone ist der Text 16px groß, damit iOS beim Fokus nicht zoomt, die Box in md wächst auf 44px, und jeder Stepper bekommt eine Trefferfläche von 44px. steppers="split" setzt - nach links und + nach rechts, den Wert in die Mitte. Für Mengen unter dem Daumen funktioniert dieses Layout am besten. Übereinanderliegende Chevrons mit stacked sind eine Desktop-Gewohnheit; auf Touchscreens werden daraus zwei Buttons von 44px nebeneinander.
Wann ist ein Currency Input das bessere Feld?
Wenn die Zahl Geld ist und sich die Währung ändern kann, oder wenn Leute große Beträge tippen. Ein Number Input mit currency="USD" reicht für einen Preis in fester Währung, den man meist per Stepper ändert. Ein Geldfeld, in das Leute tippen, braucht mehr: Tausendertrennzeichen schon beim Tippen, ohne dass der Cursor springt, Auffüllen auf die Nachkommastellen der Währung (2 für USD, 0 für JPY, 3 für BHD), optional eine Währungsauswahl und Chips für schnelle Beträge. Das ist der Currency Input:
$ npx wingo-ui@latest add currency-inputDie Cursor-Mathematik und das Parsing dahinter erklärt der Guide zum React Currency Input Schritt für Schritt.
Bekomme ich ein ordentliches Zahlenfeld nur mit kostenlosen Komponenten?
Ja, wenn du ohne Stepper und ohne Wiederholung beim Gedrückthalten auskommst. Der kostenlose Input behandelt type="number" bereits als Textfeld mit Dezimaltastatur, das Mausrad ändert ihn also nie. Die kostenlosen number-format-Helfer liefern dir Parsing nach Locale, Begrenzen und Runden auf den Schritt:
Installier beides mit npx wingo-ui@latest add input number-format. Parse text in deinem Submit-Handler noch einmal, oder halte eine separate Zahl im State. Brauchst du Stepper, die Tastenbelegung und die formatierte Anzeige, spart dir der Number Input aus Pro an dieser Stelle ein paar hundert Zeilen.
Was sind die typischen Stolperfallen?
- Den formatierten String speichern. Halte
number | nullim State und im Schema, und formatiere nur für die Anzeige. - Bei jedem Tastendruck begrenzen. Mit
max={100}wird150auf dem Weg zu15auf100begrenzt, und die Eingabe ist weg. Begrenze beim Verlassen des Felds. - Rundungsfehler bei Schritten.
0.1 + 0.2ergibt0.30000000000000004. Runde nach jedem Schritt auf die Nachkommastellen des Schritts (roundToin number-format erledigt das). - Einen Number Input für IDs verwenden. Postleitzahlen, Kartennummern und Telefonnummern sind Ziffernfolgen, keine Mengen. Nimm einen normalen Text-Input mit
inputMode="numeric"und einem Wert fürautoComplete.
Für den Rest des Formulars, also Labels, Fehler und Validierung mit Zod, lies den Guide zu React-Formularkomponenten und die anderen Beiträge in den Komponenten-Guides. Wenn du Number Inputs auf einer ganzen Seite sehen willst: Die Vorlage ROI Calculator verbindet mehrere davon mit einem Live-Ergebnis.
Wie geht es weiter?
Fang mit dem kostenlosen Input und den number-format-Helfern an, wenn ein einfaches Zahlenfeld reicht: Mit einem kostenlosen Konto (nur E-Mail) bekommst du den Quellcode. Brauchst du Stepper, Wiederholung beim Gedrückthalten und Intl-Formatierung, sind Number Input und Currency Input Teil von Pro; die Preise stehen auf der Preisseite.
Häufige Fragen
Hat shadcn/ui einen Number Input?
Nein. Stand November 2026 enthält die offizielle Komponentenliste von shadcn/ui Input, Input Group, Input OTP und Field, aber keinen Number Input und kein Number Field. Die meisten Projekte nehmen Input mit type="number" oder eine Komponente aus der Community.
Wie entferne ich die Pfeile aus einem shadcn number input?
Ergänze im className des Inputs [appearance:textfield] für Firefox und [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none für Chrome und Safari. Mausrad und Pfeiltasten ändern den Wert trotzdem, also überleg dir stattdessen einen Text-Input mit inputMode="decimal".
Wie erlaube ich Dezimalzahlen in einem React Number Input?
Bei type="number" setzt du step="any" oder einen Dezimalschritt wie 0.01, sonst markiert der Browser 2.5 als ungültig. Bei einem Text-Input nimmst du inputMode="decimal" und parst den Text selbst, damit 2.5 und 2,5 beide funktionieren.
Wie setze ich min und max bei einem React Number Input?
Die nativen Attribute min und max bremsen nur die Pfeile. Ein getippter Wert außerhalb des Bereichs wird trotzdem angenommen und nur als ungültig markiert. Begrenz die geparste Zahl beim Verlassen des Felds und dimm den Button für + oder -, sobald der Wert eine Grenze erreicht.
Warum liefert mein React Number Input einen String?
Der Wert jedes Inputs ist ein String, und bei type="number" ist er leer, solange der Text noch keine gültige Zahl ist, etwa "1e" oder "-". Nimm event.target.valueAsNumber oder parse den String, und speichere null für ein leeres Feld.
- Formulare
- Number Input
- shadcn
- Intl


