# React Animated Counter mit rollenden Ziffern

> Bau einen React Animated Counter, der jede Ziffer wie ein Kilometerzähler rollt, Trennzeichen aus Intl holt, seine Breite hält und Reduced Motion beachtet.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 19. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 9 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-animated-number-counter
- Englische Fassung: https://wingo-ui.com/blog/react-animated-number-counter

## Kurz gesagt

Ein guter React Animated Counter rollt jede Ziffer auf ihrem eigenen Rad, ausgehend von der Zahl auf dem Bildschirm, sodass schnelle Updates weiterdrehen statt neu zu starten, und dank Tabellenziffern ändert sich die Breite nur, wenn eine Stelle dazukommt oder wegfällt. Tausender- und Dezimaltrennzeichen sollte er aus Intl holen, Screenreadern einen einzigen Endwert geben und bei Reduced Motion direkt auf die neue Zahl springen. Der kostenlose Animated Counter von Wingo UI erledigt das mit einem einzigen value-Prop; die Pro-Komponente Stat Card packt ihn in eine Dashboard-Kachel.

Du brauchst einen React Animated Counter für einen Umsatz, eine Live-Besucherzahl oder einen Preis-Umschalter. Die erste Version ist meist ein `setInterval`, das eine Zahl im State hochzählt, bis sie das Ziel erreicht. Dann ändert sich die Zahl mitten im Zählen und startet wieder bei null, der Text zittert hin und her, weil eine 1 schmaler ist als eine 8, ein Besucher aus Deutschland sieht `12,480.00` statt `12.480,00`, und ein Screenreader sagt vierzig Zahlen hintereinander an. Dieser Guide zeigt, was ein produktionsreifer Zähler braucht, welche Props im [Animated Counter](https://wingo-ui.com/components/animated-counter) von Wingo UI wichtig sind und wie die [Stat Card](https://wingo-ui.com/components/stat-card) ihn in eine Dashboard-Kachel setzt.

## Was muss ein React Animated Counter können?

Er animiert vom Wert auf dem Bildschirm aus, hält seine Breite, formatiert passend zur Locale, gibt Screenreadern einen einzigen Wert und beachtet Reduced Motion. Im Detail:

1. **Vom sichtbaren Wert aus animieren.** Ändert sich die Zahl mitten in einer Animation, soll der Zähler von den Ziffern aus weiterlaufen, die gerade zu sehen sind, statt neu anzufangen. Preise, Punktestände und Live-Zahlen ändern sich oft, deshalb ist das wichtiger als die erste Animation.
2. **Eine stabile Breite.** Tabellenziffern (`font-variant-numeric: tabular-nums`, in Tailwind `tabular-nums`) geben jeder Ziffer dieselbe Breite, sodass die Zahl nur wächst, wenn eine Stelle dazukommt.
3. **Formatierung nach Locale.** Tausender- und Dezimaltrennzeichen hängen von der Locale ab. `Intl.NumberFormat` kennt sie schon, also soll der Zähler dort nachfragen, statt ein Komma fest einzubauen.
4. **Ein Wert für Screenreader.** Die bewegten Ziffern sind Dekoration. Hilfstechnologien sollen die formatierte Endzahl genau einmal bekommen.
5. **Reduced Motion.** Bei `prefers-reduced-motion: reduce` soll sich die Zahl an Ort und Stelle aktualisieren, ganz ohne Rollen.

## Count-up oder Odometer: welche Art animierter Zahl brauchst du?

Nimm einen Count-up für eine einmalige Einblendung und einen Odometer für einen Wert, der sich laufend ändert. Das sind die beiden Arten von React Animated Numbers. Ein Count-up lässt den ganzen Wert von A nach B laufen und zeigt jede Zahl dazwischen. Das passt zu einer einmaligen Einblendung („12.000 Kunden“). Ein Odometer, also ein rollender Zähler wie ein Kilometerzähler, gibt jeder Ziffer ihr eigenes senkrechtes Rad und rollt nur die Ziffern, die sich geändert haben. Bei Werten, die in Bewegung bleiben, liest sich das besser: Ein Preis, der von 1.249 auf 1.251 steigt, bewegt zwei Räder, statt durch jede Zahl dazwischen zu flackern.

Diese Optionen tauchen auf, wenn du nach einem React Odometer oder einer Counter-Komponente suchst. Die Fakten stammen aus der öffentlichen Doku und dem README der Projekte, Stand Oktober 2026.

| Option | Stil | Formatierung | Reduced Motion | Preis |
|---|---|---|---|---|
| [react-countup](https://github.com/glennreyes/react-countup) | Count-up-Tween, Wrapper um CountUp.js; Odometer über ein Plugin | Props `separator`, `decimals`, `prefix`, `suffix` | Im README nicht erwähnt | Kostenlos, MIT |
| [NumberFlow](https://number-flow.barvian.me/) (`@number-flow/react`) | Rollende Ziffern | `format` nimmt `Intl.NumberFormatOptions`, `locales` eine Intl-Locale | `respectMotionPreference`, standardmäßig an | Kostenlos, MIT |
| [Motion AnimateNumber](https://motion.dev/docs/react-animate-number) | Rollende Ziffern, auf Basis von Motion | `Intl.NumberFormat` (Währung, kompakt) | Auf der öffentlichen Seite nicht angegeben | Motion+-Mitgliedschaft, Einmalzahlung |
| [Wingo UI Animated Counter](https://wingo-ui.com/components/animated-counter) | Rollende Ziffern, auf Basis von Motion | Tausender- und Dezimaltrennzeichen aus `Intl` über `locale`, oder eigene Trennzeichen | Folgt `prefers-reduced-motion` | Kostenlos, als Quellcode in dein Projekt kopiert |

NumberFlow ist die bessere Wahl, wenn der Zähler selbst die volle Ausgabe von `Intl.NumberFormat` liefern soll, etwa kompakte Schreibweise (1.2K) oder Prozent, denn es formatiert mit dem ganzen Options-Objekt. react-countup reicht für eine einzelne Einblendung auf einer Landingpage. Der Animated Counter passt, wenn du den Quellcode in deinem Repo, Tailwind-Klassen und dieselben Tokens und Spring-Presets wie im Rest deiner UI willst.

## Welche Props braucht der Animated Counter?

Einen: `value`. Für alles andere gibt es einen Standardwert, mit dem der Zähler schon fertig aussieht.

```bash
npx wingo-ui@latest add animated-counter
```

```tsx
"use client";

import { AnimatedCounter } from "@/components/ui/animated-counter";

export function Revenue({ total }: { total: number }) {
  return (
    <AnimatedCounter
      value={total}
      decimals={2}
      size="lg"
      weight="semibold"
      prefix={<span className="mr-0.5">$</span>}
    />
  );
}
```

Die Props, die du am häufigsten änderst:

- `decimals` (Standard `0`, bis 15): Der Wert wird auf diese Genauigkeit gerundet, damit die Räder immer auf einer ganzen Ziffer landen.
- `duration` (Standard `0.6` Sekunden): ungefähr die Zeit, bis eine Ziffer zur Ruhe kommt. Für Werte, die sich jede Sekunde ändern, setzt du ihn niedriger.
- `bounce` (Standard `0.18`): wie stark die Spring der Räder überschwingt, von 0 bis 1.
- `padStart` (Standard `1`): die Mindestzahl an Vorkommastellen, mit Nullen aufgefüllt, damit ein Timer oder ein Punktestand seine Breite hält, wenn er eine Zehnerpotenz überschreitet.
- `prefix` und `suffix`: ein Währungszeichen, eine Einheit oder ein Icon. Sie animieren nie, gleiten aber mit, wenn sich die Breite ändert.
- `fade` und `fadeSize` (Standard `true` und `22`): die weiche Blende oben und unten in jedem Ziffernfenster. Schalt `fade` aus, wenn du einen harten, mechanischen Schnitt willst.

Zieh über das Lineal unten. Die Ziffern rollen nach oben, wenn die Zahl wächst, und wieder nach unten, wenn sie schrumpft. Eine neue Stelle gleitet herein, und die Trennzeichen wandern mit.

> Live-Demo (Animated Counter): Zieh über das Lineal oder fokussier es und nutz die Pfeiltasten: Jede Ziffer rollt auf ihrem eigenen Rad, und eine neue Tausenderstelle gleitet mit ihrem Komma herein. Probier sie unter [Animated Counter](https://wingo-ui.com/components/animated-counter) aus und installier sie mit `npx wingo-ui@latest add animated-counter`.

### Wie style ich einen animierten Zähler mit Tailwind?

Lass `size`, `font` und `weight` auf ihrem Standard `inherit`, dann verhält sich der Zähler wie Text: Eine Klasse am Zähler oder an seinem Elternelement bestimmt die Größe. Ein Tailwind Animated Counter braucht dann keine zusätzlichen Props: Eine responsive Größe besteht einfach aus responsiven Klassen.

```tsx
<AnimatedCounter value={visitors} className="text-4xl font-semibold sm:text-6xl" />
```

Das Root-Element trägt schon `tabular-nums`. Darin hat jede Ziffernspalte `data-slot="animated-counter-digit"` und jedes Trennzeichen `data-slot="animated-counter-mark"`. Beide erreichst du mit einer Arbitrary Variant, zum Beispiel `[&_[data-slot=animated-counter-mark]]:opacity-60` für dezentere Kommas. Für eine eigene Farbe gibt es `color` (Standard `currentColor`); `currentColor` folgt dem Text und funktioniert im Light und im Dark Mode ohne zusätzliche Klassen.

## Wie formatiere ich die Zahl mit Intl?

Gib `locale` mit. Der Zähler ruft einmal `Intl.NumberFormat(locale).formatToParts()` auf und holt sich daraus die Tausender- und Dezimaltrennzeichen der Locale: `en-US` schreibt 1,234.5, `de-DE` 1.234,5 und `fr-FR` 1 234,5 mit einem schmalen Leerzeichen. Setz die Locale einmal für die ganze App mit `UIProvider`, dann übernimmt jeder Zähler sie.

```tsx
import { UIProvider } from "@/lib/ui-config";

export function Providers({ children }: { children: React.ReactNode }) {
  return <UIProvider locale="de-DE">{children}</UIProvider>;
}
```

Ein explizit gesetzter `separator` oder `decimalSeparator` hat immer Vorrang vor der Locale. So schaltest du die Gruppierung für eine Jahreszahl oder eine Bestellnummer ab (`separator=""`). `grouping="indian"` setzt das erste Trennzeichen nach den letzten drei Ziffern und danach alle zwei Ziffern: 12,34,567.

Zwei Grenzen solltest du kennen. Der Zähler holt aus Intl nur die Trennzeichen, nicht den komplett formatierten String. Ein Währungszeichen ist also dein `prefix` oder `suffix`, und wo es steht, entscheidest du. Außerdem zeichnet er immer die Ziffern 0 bis 9. Soll das Symbol dort stehen, wo die Locale es vorsieht, oder brauchst du Prozent oder kompakte Schreibweise, nimm die Stat Card weiter unten: Sie formatiert mit Intl und wechselt bei kompakten und Prozentwerten zu einem Count-up.

## Wie geht ein Zähler mit Werten um, die sich ständig ändern?

Jedes Rad zielt von dort aus, wo es gerade steht. Genau das brauchst du, wenn du in React eine Zahl animieren willst, die nie stillsteht. Ändert sich der Wert, während eine Ziffer noch rollt, setzt die Spring von der sichtbaren Ziffer aus neu an, statt erst die alte Drehung zu beenden. Eine Zahl, die sich jede Sekunde ändert, dreht also flüssig, statt zu ruckeln. Die Richtung folgt der Änderung: Die Ziffern rollen nach oben, wenn der Wert wächst, und nach unten, wenn er schrumpft.

Bei einem Live-Wert verkürzt du die Dauer, damit die Räder nah an der echten Zahl bleiben:

```tsx
<AnimatedCounter value={visitors} duration={0.4} weight="semibold" />
```

Für einen Countdown füllst du Minuten und Sekunden auf zwei Stellen auf und schaltest die Gruppierung ab, damit sich die Breite nie ändert:

```tsx
<div className="flex items-baseline font-mono text-4xl font-semibold">
  <AnimatedCounter value={Math.floor(secondsLeft / 60)} padStart={2} separator="" />
  <span className="px-0.5">:</span>
  <AnimatedCounter value={secondsLeft % 60} padStart={2} separator="" />
</div>
```

Brauchst du Tage, Stunden und Beschriftungen, ist die Pro-Komponente [Countdown](https://wingo-ui.com/components/countdown) genau dafür gebaut.

## Wie zähle ich hoch, sobald die Zahl ins Bild scrollt?

Der Zähler zeigt seinen Wert beim ersten Paint ohne Animation. So stimmen das Server-HTML und der erste Frame. Damit er beim Scrollen hochrollt, startest du bei null und gibst den echten Wert weiter, sobald das Element sichtbar ist:

```tsx
"use client";

import { useRef } from "react";
import { useInView } from "motion/react";
import { AnimatedCounter } from "@/components/ui/animated-counter";

export function CustomerCount({ total }: { total: number }) {
  const ref = useRef<HTMLSpanElement>(null);
  const seen = useInView(ref, { once: true, amount: 0.5 });

  return (
    <span ref={ref}>
      <AnimatedCounter value={seen ? total : 0} size="xl" weight="bold" />
    </span>
  );
}
```

Der Haken: Im Server-HTML steht jetzt 0, und genau das sehen Crawler und Leser ohne JavaScript. Bei einer Marketing-Zahl, die für die Suche zählt, schreib den echten Wert zusätzlich in den Text daneben. Oder nimm die Stat Card: Ihr Server-HTML und ihr erster Paint zeigen schon die Endzahl, und sie zählt erst von null hoch, wenn sie ins Bild scrollt.

## Ist ein animierter Zähler barrierefrei?

Ja, wenn die bewegten Teile versteckt sind. Der Animated Counter rendert die rollenden Räder mit `aria-hidden` und legt die formatierte Zahl samt Minuszeichen in einen visuell versteckten Span daneben. `prefix` und `suffix` bleiben außerhalb des versteckten Teils. Mit einem `$` als Text-Prefix liest ein Screenreader also einmal „$12,480.00“ vor und nie die Ziffern, die vorbeirollen. Gib einem Icon als Prefix ein eigenes `aria-hidden` oder ein Label.

Dazu kommen zwei Regeln:

- **Pack keine tickende Zahl in `aria-live`.** Eine Besucherzahl, die sich alle zwei Sekunden ändert, würde den Nutzer alle zwei Sekunden unterbrechen. Sag nur Änderungen an, die wirklich zählen, in einer eigenen Live-Region mit `aria-live="polite"` und in einem Tempo, dem ein Mensch folgen kann.
- **Reduced Motion heißt: kein Rollen.** Wenn das System reduzierte Bewegung anfordert, setzt der Zähler jede Ziffer direkt auf ihren neuen Wert und ändert die Größe sofort. Stellen, die dazukommen oder wegfallen, wechseln ohne Übergang. Der Hook dahinter liest die Media Query erst nach der Hydration, sodass Server und Client dasselbe Markup rendern. Wie dieses Muster im Detail funktioniert, steht im [Guide zu prefers-reduced-motion in React](https://wingo-ui.com/de/blog/prefers-reduced-motion-react).

Das Rollen selbst ist eine Motion-Spring (ihre `visualDuration` ist `duration`, ihr Bounce ist `bounce`) auf einem Motion Value, der `transform` steuert. React rendert also nicht in jedem Frame neu. Willst du die Bewegung über `bounce` hinaus feinjustieren, erklärt der [Guide zu Spring-Presets](https://wingo-ui.com/de/blog/framer-motion-spring-presets), was Bounce und Visual Duration bewirken.

## Wann nehme ich stattdessen die Stat Card?

Wenn die Zahl eine Dashboard-Kennzahl mit Label, Änderungs-Pill und vielleicht einer Sparkline ist. Die [Stat Card](https://wingo-ui.com/components/stat-card) hat drei Animationsmodi:

- `animation="count"` (Standard): zählt beim ersten Mal, wenn die Karte ins Bild scrollt, von null hoch (900 ms, Ease-out), bei späteren Änderungen vom vorherigen Wert aus.
- `animation="roll"`: der Odometer des Animated Counter, für Live-Zahlen. Er funktioniert mit `format="number"` und `format="currency"`; die Karte setzt das Währungszeichen dorthin, wo die Locale es vorsieht. Andere Formate fallen auf count zurück.
- `animation="none"`: statisch.

```tsx
import { Wallet } from "lucide-react";
import { StatCard } from "@/components/ui/stat-card";

export function CollectedToday({ amount }: { amount: number }) {
  return (
    <StatCard
      label="Heute eingenommen"
      icon={<Wallet />}
      value={amount}
      format="currency"
      decimals={2}
      animation="roll"
      period="live"
      locale="en-US"
    />
  );
}
```

Screenreader hören einen einzigen Satz mit dem Endwert und der Änderung, etwa „$128,450, up 12.4% vs. last month“. Bei Reduced Motion erscheinen Zahl und Diagramm sofort im Endzustand.

> Live-Demo (Stat Card): Drück auf „Simulate a new day“: Der Wert rollt Ziffer für Ziffer zur neuen Summe, und das Delta aktualisiert sich. Probier sie unter [Stat Card](https://wingo-ui.com/components/stat-card) aus und installier sie mit `npx wingo-ui@latest add stat-card`.

Ein komplettes Dashboard aus diesen Kacheln baut das [Tutorial zum Next.js Admin Dashboard](https://wingo-ui.com/de/blog/nextjs-admin-dashboard-tutorial).

## Welche typischen Fehler gibt es bei animierten Zählern?

- **Proportionale Ziffern.** Ohne Tabellenziffern ist 1.111 schmaler als 8.888, und der Text verschiebt sich bei jeder Änderung. Der Animated Counter setzt `tabular-nums` und macht jede Spalte zusätzlich so breit wie ihre breiteste Ziffer, für Schriften ohne Tabellenziffern.
- **Einen String übergeben.** `value` ist eine Zahl. Formatier sie über die Props, nicht vorher mit `toLocaleString()`.
- **Riesige oder unsichere Werte.** Der Zähler deckelt den skalierten Wert bei `Number.MAX_SAFE_INTEGER`, weil Ziffern darüber hinaus nur Rauschen sind. Halte Geldbeträge auf dem Server in Cent und teile erst für die Anzeige.
- **Trennzeichen, die der Locale widersprechen.** Wenn `separator` gesetzt ist, hat er Vorrang vor `locale`. Lass ihn weg, wenn du die Trennzeichen der Locale willst.
- **Eine lange Zahl auf einem 360px breiten Smartphone.** Ein Wert in `text-7xl` mit zwei Nachkommastellen kann über eine schmale Karte hinauslaufen. Nimm responsive Größenklassen oder in Karten eine kleinere `size`.

## Wie baue ich ihn in mein Projekt ein?

Führ `npx wingo-ui@latest add animated-counter` aus. Die CLI kopiert den Quellcode nach `components/ui`, ergänzt die Helfer `motion` und `ui-config`, die die Komponente importiert, und installiert `motion`. Der Animated Counter ist kostenlos; die Stat Card ist eine Pro-Komponente ([Preise](https://wingo-ui.com/de/pricing)). Weitere Deep Dives zu einzelnen Komponenten findest du in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components), und der [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide) wendet dieselben Regeln für Barrierefreiheit und mobile Nutzung auf jedes Formularelement an.

## Komponenten in diesem Artikel

- [Animated Counter](https://wingo-ui.com/components/animated-counter) (Free): A React animated counter that rolls each digit to its new value like an odometer, for stats, prices, timers and live numbers. Installation: `npx wingo-ui@latest add animated-counter`
- [Stat Card](https://wingo-ui.com/components/stat-card) (Pro): A React stat card for dashboards: a label, a big number that counts up, a delta pill that knows good from bad, a sparkline and a goal bar. Installation: `npx wingo-ui@latest add stat-card`

## Häufige Fragen

### Wie animiere ich in React eine Zahl, wenn sie sich ändert?

Halte die Zahl im State und gib sie an eine Komponente weiter, die zwischen altem und neuem Wert animiert, etwa einen Odometer-Zähler oder einen Count-up-Tween. Animiere nur transform und opacity und starte jede Animation bei dem Wert, der gerade zu sehen ist, damit sich schnelle Updates nicht aufstauen.

### Was ist der Unterschied zwischen einem Count-up und einem Odometer-Zähler?

Ein Count-up lässt die ganze Zahl durch alle Zwischenwerte laufen und rendert den Text in jedem Frame neu. Ein Odometer rollt jede Ziffer auf ihrem eigenen Rad zur neuen Ziffer, sodass sich nur die geänderten Ziffern bewegen und die Breite stabil bleibt.

### Wie mache ich einen animierten Zähler barrierefrei?

Versteck die bewegten Ziffern vor Hilfstechnologien und gib den formatierten Endwert als normalen Text aus. Pack keine Zahl, die jede Sekunde tickt, in eine aria-live-Region, sonst liest ein Screenreader jedes Update vor.

### Beachtet ein animierter Zähler prefers-reduced-motion?

Nur wenn er die Media Query prüft. Der Animated Counter von Wingo UI und NumberFlow tun das standardmäßig; das README von react-countup erwähnt es nicht, dort prüfst du die Einstellung also selbst.

### Ist der Animated Counter von Wingo UI kostenlos?

Ja. Der Animated Counter ist eine kostenlose Komponente, die du mit npx wingo-ui@latest add animated-counter installierst. Die Stat Card, die ihn für Dashboard-Kacheln nutzt, ist eine Pro-Komponente.

---

Quelle: https://wingo-ui.com/de/blog/react-animated-number-counter
