# shadcn Komponenten updaten, ohne Änderungen zu verlieren

> shadcn Komponenten updaten, ohne Änderungen zu verlieren: was shadcn diff und add --diff zeigen, was ein 2-Wege-Diff verschweigt und wie der 3-Wege-Merge geht.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 9. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 10 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/update-shadcn-components-without-losing-edits
- Englische Fassung: https://wingo-ui.com/blog/update-shadcn-components-without-losing-edits

## Kurz gesagt

Das shadcn CLI kann nicht mergen: add --diff listet jeden Unterschied zwischen deiner Kopie und der Registry, ohne zu sagen, von welcher Seite er stammt, und add --overwrite ersetzt die Datei samt deinen Änderungen. Damit deine Änderungen bleiben, führst du pro Datei einen 3-Wege-Merge mit git merge-file aus (die Kopie wie installiert, deine Kopie und das neue Upstream) und hältst die installierten Kopien auf einem Git-Branch. Das Wingo UI CLI speichert die installierte Kopie selbst. Deshalb erledigt npx wingo-ui@latest update diesen Merge und hält nur dort an, wo beide Seiten dieselben oder benachbarte Zeilen geändert haben.

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](https://ui.shadcn.com/docs/changelog) importiert jede shadcn-Komponente `cn` jetzt aus dem Paket `cn`. Die Import-Zeile hat sich in jeder Komponente geändert.
- **März 2025:** neue Dark-Mode-Farben. Die [Tailwind-v4-Seite](https://ui.shadcn.com/docs/tailwind-v4) rät bestehenden Projekten, `add --all --overwrite` auszuführen und danach eigene Änderungen an den Komponenten zu prüfen und neu einzubauen.
- **Bugfixes**, die genauso ankommen. Die Wingo UI [Data Table](https://wingo-ui.com/components/data-table) 1.0.1 hat Header-Zellen repariert, die bei fixiertem Header über die erste Zeile rutschten. Der Fix war ein einziger `className` in 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](https://ui.shadcn.com/docs/changelog/2026-03-cli-v4), 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`:

```diff
@@ -1,9 +1,8 @@
 import * as React from "react"
 import { cva, type VariantProps } from "class-variance-authority"
+import { cn } from "cn"
 import { Slot } from "radix-ui"

-import { cn } from "@/lib/utils"
-
@@ -19,10 +18,9 @@
         link: "text-primary underline-offset-4 hover:underline",
-        brand: "bg-orange-600 text-white hover:bg-orange-600/90",
       },
       size: {
-        default: "h-10 px-4 py-2 has-[>svg]:px-3",
+        default: "h-9 px-4 py-2 has-[>svg]:px-3",
```

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](https://github.com/shadcn-ui/ui/blob/main/skills/shadcn/SKILL.md) 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:

```bash
for f in components/ui/*.tsx; do
  first=$(git log --diff-filter=A --format=%H -- "$f" | tail -1)
  git diff --quiet "$first" -- "$f" && echo "unverändert: $f"
done
```

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:

```bash
git switch -c shadcn-upstream
for f in components/ui/*.tsx; do
  first=$(git log --diff-filter=A --format=%H -- "$f" | tail -1)
  git show "$first:$f" > "$f"
done
git commit -am "shadcn: Komponenten wie installiert"
git switch -
```

### 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:

```bash
git switch shadcn-upstream
npx shadcn@latest add button dialog --overwrite --yes
git add -A && git commit -m "shadcn: upstream $(date +%F)"
git switch -

for f in $(git diff --name-only shadcn-upstream~1 shadcn-upstream -- components/ui); do
  # eine Datei, die upstream neu hinzugekommen ist: unverändert übernehmen
  if [ ! -f "$f" ]; then git show "shadcn-upstream:$f" > "$f"; continue; fi
  git show "shadcn-upstream~1:$f" > /tmp/base.tsx
  git show "shadcn-upstream:$f" > /tmp/upstream.tsx
  git merge-file -L yours -L installed -L upstream "$f" /tmp/base.tsx /tmp/upstream.tsx \
    || echo "Konflikte auflösen in $f"
done
```

`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:

```bash
git diff shadcn-upstream~1 shadcn-upstream -- package.json app/globals.css
```

### 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:

```bash
npx wingo-ui@latest outdated               # installiert vs. neueste, Versionssprung, lokale Änderungen
npx wingo-ui@latest diff data-table --base # nur deine Änderungen seit der Installation
npx wingo-ui@latest update --dry-run       # was gemergt würde, nichts wird geschrieben
npx wingo-ui@latest update                 # 3-Wege-Merge in jedes installierte Item
```

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:

```tsx
<<<<<<< local
      className={cn("group/head relative select-none", reorder && "cursor-grab active:cursor-grabbing", isDragging && "opacity-40", className)}
=======
      className={cn(
        // eine sticky Header-Zelle ist schon ein Containing Block; ein schlichtes "relative" würde in cn gegen "sticky" gewinnen und
        // den sticky top-Offset auf einer relativen Zelle lassen, was den Header über die erste Zeile schiebt
        "group/head [&:not(.sticky)]:relative",
        reorder && "cursor-grab active:cursor-grabbing",
        isDragging && "opacity-40",
        className,
      )}
>>>>>>> wingo-ui 1.0.1
```

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.

> Live-Demo (Data Table): Scrolle in der Tabelle: Die Kopfzeile bleibt oben in der Box fixiert, während die Zeilen darunter durchlaufen. Unter 768px werden die Zeilen zu Karten. Probier sie unter [Data Table](https://wingo-ui.com/components/data-table) aus und installier sie mit `npx wingo-ui@latest add data-table`.

Außerdem haben wir das veröffentlichte CLI komplett am kostenlosen [Field](https://wingo-ui.com/components/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](https://wingo-ui.com/docs/mcp) 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](https://wingo-ui.com/docs/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](https://wingo-ui.com/components/ui-config). 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](https://wingo-ui.com/components/button) einbindet, liegt in deinem Code und erzeugt nie Konflikte.
- **Halte deinen Formatter von kopierten Dateien fern.** Trag `components/ui` in `.prettierignore` ein, 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](https://wingo-ui.com/components/dialog), der auf Smartphones eine zentrierte Karte bleibt, alles ohne eine Komponentendatei anzufassen:

```tsx
// app/providers.tsx
"use client";

import type { ReactNode } from "react";
import { UIProvider } from "@/lib/ui-config";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <UIProvider
      defaults={{
        button: { radius: "full" },
        dialog: { responsive: false },
        "data-table": { defaultPageSize: 50 },
      }}
    >
      {children}
    </UIProvider>
  );
}
```

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:

> Live-Demo (UIProvider): Der erste Button nutzt seine eingebauten Defaults; die zwei darunter bekommen radius full, size lg und tone brand von einem UIProvider, und der Outline-Button behält die Variante, die er selbst übergibt. Probier sie unter [UIProvider](https://wingo-ui.com/components/ui-config) aus und installier sie mit `npx wingo-ui@latest add ui-config`.

Für einzelne Komponenten schau in die [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components), am besten zuerst in den [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide).

## 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](https://wingo-ui.com/de/pricing). Der Artikel zu [shadcn Alternativen](https://wingo-ui.com/de/blog/shadcn-alternatives) vergleicht, wie andere Bibliotheken mit Updates umgehen.

## Komponenten in diesem Artikel

- [Button](https://wingo-ui.com/components/button) (Free): The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps. Installation: `npx wingo-ui@latest add button`
- [Dialog](https://wingo-ui.com/components/dialog) (Free): A React modal dialog for focused tasks: a spring-scaled card on desktop and a bottom sheet on phones, with nesting and scrolling. Installation: `npx wingo-ui@latest add dialog`
- [Data Table](https://wingo-ui.com/components/data-table) (Pro): A React data table on TanStack Table: search, filters, sorting, bulk actions, pinning, resizing, virtualization and cards on phones. Installation: `npx wingo-ui@latest add data-table`
- [Field](https://wingo-ui.com/components/field) (Free): A React form field wrapper every control shares: label, hint, animated error and counter, fieldsets, and the box recipe all inputs use. Installation: `npx wingo-ui@latest add field`
- [UIProvider](https://wingo-ui.com/components/ui-config) (Free): A React config provider for Wingo UI: app-wide default props for every component, the Intl locale and a reduced-motion policy. Installation: `npx wingo-ui@latest add ui-config`

## Häufige Fragen

### Wie update ich shadcn Komponenten?

Mit npx shadcn@latest add <name> --diff siehst du, wie deine Datei von der Registry abweicht, mit npx shadcn@latest add <name> --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 <component> --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.

---

Quelle: https://wingo-ui.com/de/blog/update-shadcn-components-without-losing-edits
