shadcn Komponenten updaten, ohne Änderungen zu verlieren

Du hast vor ein paar Monaten einen shadcn/ui-Button installiert, ihm eine brand-Variante und eine höhere Standardgröße gegeben, und jetzt gibt es upstream Änderungen, die du haben willst. Wenn du shadcn Komponenten updaten willst, die du angepasst hast, gibt dir das CLI zwei Werkzeuge: add --overwrite ersetzt die Datei und mit ihr deine Änderungen, add --diff listet jeden Unterschied zwischen deiner Kopie und der Registry, ohne zu sagen, von wem er stammt. Dieses Tutorial zeigt, warum Kopien vom Original abdriften, was shadcn diff dir sagen kann und was nicht, und wie ein 3-Wege-Merge Fixes von upstream in Dateien bringt, die du schon geändert hast: zuerst mit einem Git-Skript für normales shadcn/ui, dann mit einem CLI, das es für dich erledigt.
Warum weichen kopierte Komponenten von upstream ab?
Weil sich die Kopie nicht mehr ändert, sobald das CLI sie geschrieben hat, upstream aber weiterentwickelt wird. Eine shadcn-Komponente ist eine Datei in deinem Repo, ohne Versionsangabe. Stand Oktober 2026 (shadcn 4.21.4) speichert components.json Einstellungen wie deinen Style, Tailwind, Aliase, Icon-Bibliothek und Registries, aber nichts darüber, welche Komponenten du wann installiert hast. Upstream liefert derweil Änderungen wie diese:
- September 2026: Laut Changelog von shadcn (opens in a new tab) importiert jede shadcn-Komponente
cnjetzt aus dem Paketcn. Die Import-Zeile hat sich in jeder Komponente geändert. - März 2025: neue Dark-Mode-Farben. Die Tailwind-v4-Seite (opens in a new tab) rät bestehenden Projekten,
add --all --overwriteauszuführen und danach eigene Änderungen an den Komponenten zu prüfen und neu einzubauen. - Bugfixes, die genauso ankommen. Die Wingo UI Data Table 1.0.1 hat Header-Zellen repariert, die bei fixiertem Header über die erste Zeile rutschten. Der Fix war ein einziger
classNamein einer Datei mit fast 4.000 Zeilen.
Was zeigt shadcn diff und was entgeht ihm?
Er zeigt einen 2-Wege-Diff: deine Datei gegen die aktuelle Version in der Registry, nachdem das CLI deine Aliase und deine Icon-Bibliothek eingesetzt hat. Der eigenständige Befehl ist veraltet. In shadcn 4.21.4 ist er im Hilfetext als [DEPRECATED] markiert und verweist auf add [component] --diff. Das Flag kam im März 2026 mit shadcn CLI v4 (opens in a new tab), zusammen mit --dry-run und --view, und ohne Pfad zeigt es die ersten 5 Dateien.
Wir haben es in einem frischen Next.js-Testprojekt ausprobiert: der shadcn-Button im Stand von März 2026, plus zwei lokale Änderungen, eine brand-Variante und eine Standardgröße von 40px. Das ist die gekürzte Ausgabe von npx shadcn@4.21.4 add button --diff:
Der erste Hunk ist die Änderung von upstream. Die anderen beiden sind unsere Änderungen, angezeigt als Zeilen, die --overwrite rückgängig machen würde. Nichts in der Ausgabe unterscheidet sie, denn ein 2-Wege-Diff kennt nur zwei Dateien. Um zu wissen, welche Seite eine Zeile geändert hat, brauchst du eine dritte: die Komponente, wie sie installiert wurde. Die hebt shadcn nicht auf. Im selben Projekt meldete der veraltete Befehl npx shadcn@4.21.4 diff button nur No updates found for button., also bleib beim Flag.
Für geänderte Dateien schickt dich shadcn zu deinem Coding-Agenten. Der Beitrag zu CLI v4 schlägt vor, ihn zu bitten, nach Updates von @shadcn zu suchen und sie mit deinen lokalen Änderungen zu mergen. Der offizielle Skill (opens in a new tab) sagt dem Agenten, er soll die lokale Datei lesen, den Diff analysieren und die Updates von upstream einspielen, ohne lokale Anpassungen zu verlieren. Der Agent sieht aber denselben 2-Wege-Diff. Solange er die installierte Kopie nicht aus der Git-Historie holt, muss er raten, welche Seite welche Änderung gemacht hat. Das Skript unten holt diese Kopie aus Git, für dich oder für den Agenten.
Wie update ich alle shadcn Komponenten auf einmal?
Du fügst sie mit --overwrite neu hinzu, und das ist nur für Dateien sicher, die du nie geändert hast. Ein eigenes shadcn update gibt es nicht, denn shadcn 4.21.4 hat keinen update-Befehl. „shadcn alle Komponenten updaten“ läuft also auf npx shadcn@latest add --all --overwrite hinaus. Das fügt jede Komponente der Registry neu hinzu, auch solche, die du nie installiert hast, und ersetzt deine Dateien. Ein erneutes init --reinstall bietet zuerst an, components.json zu überschreiben, und installiert dann die Komponenten neu, die du schon hast, mit dem Hinweis, dass sie neu installiert und überschrieben werden. Such zuerst die Dateien, die du nie angefasst hast:
Eine Datei, die noch dem Commit entspricht, mit dem sie hinzugekommen ist, kannst du gefahrlos überschreiben. Gib also nur diese Namen an npx shadcn@latest add <name...> --overwrite. Den Rest mergst du.
Wie update ich shadcn Komponenten, die ich geändert habe?
Du mergst drei Versionen jeder Datei: die Kopie, die das CLI installiert hat (die Basis), deine Kopie und das neue Upstream. Wo nur eine Seite einen Bereich geändert hat, übernimmt der Merge diese Seite. Wo beide dieselben Zeilen geändert haben, schreibt er Konfliktmarker. Genau diesen Schritt überspringt die shadcn-Doku, wenn du shadcn Komponenten aktualisieren willst: Sie endet damit, dass du deine Änderungen von Hand neu einbaust oder einen Agenten fragst. git merge-file erledigt den Merge. Die eigentliche Arbeit ist, Basis und Upstream als Dateien bereitzustellen, und ein Branch, der nur unveränderte CLI-Ausgabe enthält, liefert beides.
1. Einen Branch mit den Dateien wie installiert anlegen
Das machst du einmal. Das Skript stellt jede Komponente aus dem Commit wieder her, mit dem sie hinzugekommen ist. Das ist die Kopie, die das CLI geschrieben hat, falls du vor dem Ändern committet hast:
2. Das neue Upstream auf diesen Branch schreiben und mergen
Das machst du bei jedem Update. Gib die Komponenten an, die du hast, denn --all würde auch die installieren, die dir fehlen:
git merge-file schreibt das Ergebnis in deine Datei und beendet sich mit der Anzahl der Konflikte als Exit-Code. So nennt die Schleife jede Datei, die du dir ansehen musst. Meldet der Commit auf dem Upstream-Branch, dass es nichts zu committen gibt, hat sich upstream nicht geändert, und es gibt nichts zu mergen. Andernfalls enthält der Branch jetzt das Upstream, das du gerade gemergt hast, und das nächste Update nimmt es als Basis.
Wenn deine Komponenten Hooks importieren (das use-mobile.ts der Sidebar), nimm hooks/*.ts in die Schleife aus Schritt 1 auf und hooks in den git diff aus Schritt 2. Und wenn du später eine Komponente hinzufügst, führ dasselbe add auf dem Branch shadcn-upstream aus und committe sie auch dort. Sonst findet die Schleife keine Basis für die Datei, und jede Zeile, die von upstream abweicht, wird zum Konflikt.
3. Prüfen, was sich außerhalb der Komponenten geändert hat
Die Schleife fasst nur components/ui an. Auf dem Upstream-Branch kann das CLI auch npm-Pakete und CSS-Variablen hinzugefügt haben:
Was beim Testlauf passiert ist
Wir haben das Skript mit shadcn 4.21.4 im Testprojekt von oben laufen lassen. Beide Änderungen blieben erhalten, der Button bekam import { cn } from "cn", und der unangetastete Dialog wurde ersetzt. Schritt 3 zeigte, dass "cn": "^0.4.0" in package.json dazugekommen war. Solange cn nicht in der package.json deines Branches steht, scheitert der gemergte Button bei einer frischen Installation. Für genau diese Änderung führst du zusätzlich npx shadcn@latest migrate cn aus. In einem Testprojekt hat der Befehl cn installiert und lib/utils.ts in export { cn } from "cn" umgewandelt, sodass jeder @/lib/utils-Import in deinem eigenen Code weiter funktioniert.
Zwei Einschränkungen. Wenn du eine Komponente vor ihrem ersten Commit geändert hast, hält Schritt 1 deine Änderung als installierte Kopie fest, und der erste Merge macht sie ohne Warnung rückgängig. Lies also nach jedem Merge git diff. Und wenn ein Formatter eine Komponente umgeschrieben hat, lass ihn vor dem Merge auch über die Basis- und die Upstream-Datei laufen. Bei einer mit Prettier umformatierten Kopie der Data Table stoppte der Merge mit einem Konflikt, genau dort, wo upstream die Datei geändert hatte. Als alle drei Dateien gleich formatiert waren, lief der Merge sauber durch.
Kann ein CLI den 3-Wege-Merge für dich ausführen?
Ja, wenn es die Basis selbst aufbewahrt. Wenn npx wingo-ui@latest add eine Komponente installiert, speichert es die Version und einen Hash pro Datei in wingo-ui.json und legt die unveränderte Kopie unter .wingo-ui/base/ ab. Committe beides. Ab dann gilt, Stand CLI 1.0.3:
Für jede Datei vergleicht update deine Kopie, die Basis und das neue Release. Eine unangetastete Datei wird ersetzt. In eine geänderte Datei werden die Änderungen von upstream gemergt, und ihre Basis rückt fürs nächste Mal auf die neue Version vor. Es zeigt die Release Notes jeder übersprungenen Version, installiert neue Abhängigkeiten und meldet Dateien, die ein Release nicht mehr mitliefert, ohne sie zu löschen.
Wir haben den echten Fix der Data Table 1.0.1 durch den Merge-Code des CLI geschickt, wobei defaultPageSize in unserer Kopie von 25 auf 50 geändert war. Der Merge lief sauber: Der Fix kam an, und die 50 blieb. Dann haben wir zusätzlich select-none in die Klassen der Header-Zelle geschrieben, genau in die Zeile, die der Fix umgeschrieben hat, und bekamen einen Konflikt:
Behalte den Block von upstream, ergänz select-none in seinem ersten String und lösch die Marker. Oder löse alle Konflikte auf einmal mit update --theirs (das Release übernehmen) oder --ours (deine Version behalten). Beide räumen auch Marker auf, die ein früherer Lauf hinterlassen hat. git merge-file lieferte in beiden Fällen denselben Merge, bis auf das Label der Marker. In beiden Werkzeugen führte auch eine Änderung in der Zeile direkt neben einer Änderung von upstream zum Konflikt, während eine einzige unangetastete Zeile dazwischen für einen sauberen Merge reichte.
Die Tabelle unten hat maxHeight gesetzt und scrollt deshalb in ihrer eigenen Box. Genau diesen Fall hat der Fix repariert.
$ npx wingo-ui@latest add data-tableAußerdem haben wir das veröffentlichte CLI komplett am kostenlosen Field getestet. Dessen Fix in 1.0.1 sorgt dafür, dass der Fokus im Eingabefeld bleibt, wenn die Fehlermeldung verschwindet. Vor dem Update hatten wir in unserer Kopie den Standardwert von optionalLabel geändert. update gab merged components/ui/field.tsx mit der Release Note aus, und die Änderung blieb. Zwei Leitplanken: Ohne Basiskopie (etwa wenn .wingo-ui/ nie committet wurde und jemand im Team das Update ausführt) rät das CLI nicht, sondern fragt nach --theirs oder --ours. Und solange Konflikte übrig sind, endet es mit Exit-Code 1, sodass ein CI-Schritt fehlschlägt, statt Marker auszuliefern. KI-Agenten können denselben Versionscheck über das Tool check_updates des MCP-Servers ausführen. Es vergleicht wingo-ui.json mit dem neuesten Release und listet die Release Notes. Die vollständige Referenz steht in der Doku zu Updates.
Wie bleibt das nächste Update konfliktfrei?
Pass in jeder Komponente möglichst wenige Zeilen selbst an. Konflikte gibt es nur dort, wo du und upstream dieselben oder benachbarte Zeilen ändern.
- Defaults von außen setzen. Jede Wingo UI Komponente liest app-weite Defaults über UIProvider. Ein Radius, eine Seitengröße oder das Smartphone-Layout eines Dialogs braucht also keine Änderung an der Datei. Explizite Props gewinnen, dann der Provider, dann die eigenen Defaults der Komponente.
- Verhalten per Wrapper ergänzen. Ein
SubmitButton, der den Button einbindet, liegt in deinem Code und erzeugt nie Konflikte. - Halte deinen Formatter von kopierten Dateien fern. Trag
components/uiin.prettierignoreein, sonst wird jede Änderung von upstream neben einer umformatierten Zeile zum Konflikt. - Geh pro Update nur ein oder zwei Releases weiter, damit jeder Merge klein genug bleibt, um ihn zu prüfen.
Die Seitengröße 50 statt 25 aus dem Test oben, ein pillenförmiger Button und ein Dialog, der auf Smartphones eine zentrierte Karte bleibt, alles ohne eine Komponentendatei anzufassen:
Pack {children} in app/layout.tsx in <Providers>, und jeder Button, Dialog und jede Data Table darunter startet mit diesen Defaults. Die Demo zeigt denselben Mechanismus an zwei Buttons:
$ npx wingo-ui@latest add ui-configFür einzelne Komponenten schau in die Komponenten-Guides, am besten zuerst in den Guide zu React-Formularkomponenten.
Was solltest du als Nächstes tun?
Bei normalem shadcn/ui legst du den Branch shadcn-upstream jetzt an, solange deine Historie noch zeigt, was das CLI geschrieben hat. Wenn du den Merge eingebaut haben willst, installier den kostenlosen Button und Dialog mit npx wingo-ui@latest add button dialog, committe wingo-ui.json und .wingo-ui/ und führ nach dem nächsten Release npx wingo-ui@latest update --dry-run aus. Die Data Table gehört zu Wingo UI Pro. Der Artikel zu shadcn Alternativen vergleicht, wie andere Bibliotheken mit Updates umgehen.
Häufige Fragen
Wie update ich shadcn Komponenten?
Mit npx shadcn@latest add --diff siehst du, wie deine Datei von der Registry abweicht, mit npx shadcn@latest add --overwrite ersetzt du sie. Overwrite verwirft deine Änderungen. Bei Dateien, die du angepasst hast, mergst du deshalb stattdessen drei Versionen (wie installiert, deine und upstream) mit git merge-file.
Ist der Befehl shadcn diff veraltet?
Ja. Stand shadcn 4.21.4 (Oktober 2026) verweist sein Hilfetext stattdessen auf add --diff. Das Flag kam im März 2026 mit shadcn CLI v4, zusammen mit --dry-run und --view.
Wie update ich alle shadcn Komponenten auf einmal?
npx shadcn@latest add --all --overwrite fügt jede Komponente der Registry neu hinzu und ersetzt deine Dateien samt Änderungen. Überschreib nur die Dateien, die noch dem Commit entsprechen, mit dem sie hinzugekommen sind, und merge die, die du geändert hast.
Warum behält ein 3-Wege-Merge meine Änderungen, ein Diff aber nicht?
Ein Diff vergleicht zwei Dateien, deshalb sehen deine Änderungen und die von upstream gleich aus. Ein 3-Wege-Merge liest zusätzlich die Datei, wie sie installiert wurde. So weiß er, welche Seite jede Zeile geändert hat, und hält nur dort an, wo beide Seiten dieselben oder benachbarte Zeilen geändert haben.
Kann das Wingo UI CLI Komponenten updaten, die ich mit shadcn installiert habe?
Nein. wingo-ui update mergt nur Items, die es selbst installiert hat, denn es braucht die Basiskopie, die es bei der Installation in .wingo-ui/base gespeichert hat. Für Dateien aus dem normalen shadcn/ui nimmst du das git merge-file Skript aus diesem Artikel.
- shadcn/ui
- CLI
- Git
- Updates
- React


