Zurück zum Blog
Messen · Essay

Die am wenigsten hinterfragte Komponente

Simon Doba·2. August 2026·6 Min. Lesezeit

Jedes Design System hat eine Progress Bar. Ein abgerundetes Rechteck, das sich von links nach rechts füllt, und in den meisten Systemen endet das Nachdenken genau dort.

Merkwürdig eigentlich. Es ist eine der wenigen Komponenten, deren ganze Aufgabe darin besteht, jemandem, der wartet, eine Zahl mitzuteilen. Das ist ein echtes Gestaltungsproblem, und die Antwort, die alle ausgeliefert haben, ist ein Rechteck.

Ich baue seit einer Weile an einem kleinen Satz Progress- und Datenkomponenten, um zu sehen, was passiert, wenn man das Rendering ernst nimmt und alles andere in Ruhe lässt.

Gleiche Semantik, anderes Bild

Die Komponente stellt drei Renderer hinter einer API bereit. Ein variant-Prop wählt zwischen einer geflochtenen Welle, einem Ordered-Dither-Feld und einer geschichteten Dither-Fläche. Sonst ändert sich nichts.

Diese Einschränkung ist der eigentliche Entwurf. Ein Renderer-Wechsel darf nicht ändern:

  • role="progressbar" am Element
  • aria-valuenow — vorhanden bei bekanntem Wert, abwesend sonst
  • den Wert, die Grenzen, oder wie ein Screenreader das ansagt

Ein Screenreader kann nicht erkennen, welcher Renderer aktiv ist, und soll es auch nicht können. Das Visuelle ist eine Darstellungsentscheidung über einer Komponente, deren Vertrag sich nicht bewegt. Dieselbe Trennung, nach der ich auch Modellarbeit verteile: Entscheiden und Darstellen sind zwei Aufgaben.

Geprüft wird das im Indeterminate-Fall. Ein Zustand unbekannter Dauer muss aria-valuenow vollständig weglassen — eine erfundene Prozentzahl anzusagen ist schlimmer als gar nichts, während der Renderer weiterläuft. Die beiden Belange müssen wirklich getrennt sein, sonst hast du eine geschäftig wirkende Welle und einen Screenreader, der 0 % behauptet.

Die Zahlen, sonst ist das Dekoration

Eine Komponente, die dauerhaft auf einem Canvas animiert, lädt zu einer berechtigten Frage ein: Was kostet das? Im Repository liegen deshalb ein Bundle-Audit und ein Browser-Benchmark, die bei jeder Änderung laufen.

PrüfungErgebnis
Komponenten-JS7.834 Bytes gzip
Komponenten-CSS2.575 Bytes gzip
Gesamt10.409 Bytes gzip
50 Wave-Instanzen120 FPS Mittel
50 Dither-Instanzen120 FPS Mittel
50 Dither-Instanzen mit Assembly-Effekt120 FPS Mittel
50 Indeterminate-Instanzen120 FPS Mittel

Ein Detail am Benchmark wiegt schwerer als die Zahl selbst: Er hält alle fünfzig Instanzen sichtbar und hovert eine davon während der Messung, damit der aktive Geometrie-Morph im Frame-Budget steckt. Ein Benchmark über fünfzig ruhende Komponenten sagt fast nichts — teuer ist der Frame, in dem sich etwas ändert.

React ist die einzige Laufzeit-Abhängigkeit, die die installierte Komponente mitbringt.

Zwei Dinge, die verhindern, dass es mehr kostet als nötig

Instanzen außerhalb des Sichtfelds pausieren. Eine Komponente, die unterhalb des Falzes animiert, verbrennt Frames für niemanden. Phase und Indeterminate-Animation stoppen, sobald die Instanz nicht sichtbar ist — erst das macht fünfzig gleichzeitige Instanzen zu einem Benchmark statt zu einer Warnung.

Reduced Motion ist ein getesteter Pfad, kein Kommentar. Es ist ein Unterschied, ob man prefers-reduced-motion beachtet — die Media Query, oder ob ein Test fehlschlägt, sobald man aufhört, es zu beachten. Im Repository liegt die zweite Variante.

Quellcode statt Abhängigkeit

Installiert wird über die shadcn-Registry, die Dateien landen also in deinem Repository statt hinter einer Versionsnummer:

npx shadcn@latest add https://example.com/r/wave-progress.json

Die Registry ist noch nicht veröffentlicht, die URL ist also illustrativ und nicht etwas, das du heute ausführen kannst. Bei allem Visuellen finde ich dieses Modell inzwischen richtig. Eine Progress Bar ist genau die Art Komponente, an der ein Team schrauben will — die Wellenamplitude passt nicht zur Marke, die Meilenstein-Beschriftung braucht andere Texte, und gegen ein npm-Paket heißt das entweder Fork oder eine Konfigurationsfläche, die endlos wächst.

Gehört dir der Quellcode, ist die Anpassung ein Diff im eigenen Repo. Der Preis: Updates kommen nicht mehr von allein. Bei einer Komponente dieser Größe würde ich den Tausch machen.

Was ich nicht behaupten würde

  • Ich weiß nicht, ob es beim Warten hilft. Die Annahme ist, dass ein lesbarerer Fortschrittsanzeiger das Warten erträglicher macht. Das ist eine Annahme. Ich habe es nicht mit Nutzern getestet, und ein 120-FPS-Benchmark sagt nichts darüber, ob die Sache angenehmer anzusehen ist.
  • Drei Renderer sind Wartungsfläche. Jeder muss den ARIA-Vertrag halten, Reduced Motion überstehen und die Geometrie-Tests nicht brechen. Für eine visuelle Entscheidung sind das laufende Kosten.
  • Die Zahlen sind lokal. Auf meiner Maschine gemessen, bei jeder Änderung neu, und sie bewegen sich mit der Last. Sie sind eine Größenordnung zum Nachdenken, keine Zusage.

Next

Was sich über diese Komponente hinaus verallgemeinern lässt:

  • Wie etwas aussieht, darf nicht ändern, was es ansagt. Kann der Renderer die Semantik verschieben, sitzt die Abstraktion an der falschen Stelle.
  • Benchmarke den teuren Frame. Fünfzig ruhende Instanzen sind keine Messung.
  • Reduced Motion braucht einen Test, der fehlschlägt — nicht eine Media Query und guten Willen.
  • Bei visuellen Komponenten schlägt eigener Quellcode eine Konfigurationsfläche, die endlos wächst.
  • Abnahmekriterien, die durchfallen können, schlagen ein Changelog. Es ist die zweite Sache diesen Monat, die ich gegen eine Messlatte statt gegen einen Termin gebaut habe — die Schrift lief genauso, und in beiden Fällen haben die Kriterien etwas erwischt, das mein Auge schon durchgewunken hatte.

Als Nächstes will ich genau das, von dem ich gerade sagte, dass ich es nicht habe: einen Hinweis darauf, ob ein lesbarerer Anzeiger verändert, wie lang sich eine Wartezeit anfühlt. Das ist eine Nutzerfrage, kein Benchmark, und wie ich sie ehrlich stelle, weiß ich noch nicht.

Falls du eine Standard-Progress-Komponente durch etwas Eigenes ersetzt hast, würde mich interessieren, was dich dazu gebracht hat, und ob es jemandem aufgefallen ist.

Die Zahlen stammen aus dem Bundle-Audit und dem Browser-Benchmark im Repository, lokal gemessen und bei jeder Änderung neu ausgeführt. Sie schwanken mit der Last der Maschine.

Artikel teilen

Baust du an etwas Ähnlichem?

Ich schreibe hier über Setups, die ich selbst benutze. Wenn du an etwas Vergleichbarem arbeitest, würde mich interessieren, wie dein Workflow aussieht.

Schreib mir

Cookie-Einstellungen

Wir nutzen Cookies für Analyse und Verbesserung unserer Website. Datenschutzerklärung