Warum visuelle Editor mit den von shadcn UI bereitgestellten Komponenten zu kämpfen haben
Visuelle Editor haben Schwierigkeiten mit Shadcn UI, weil die CVA-Karten im Repository gespeichert sind und Tailwind eine Kompilierung erfordert. Das Sammeln von DOM-Klassen verbindet den Design-Modus in React.
Quellen: shadcn/ui says the code is yours. So why can’t anything edit it?
shadcn/ui positioniert sich als Lösung mit „Copy-Paste-Eigentum“: Man führt die CLI aus, fügt components/ui/button.tsx zum Repository hinzu und behandelt die Datei als eigenes Codeelement. Es gibt weder ein Upgrade-System noch Konflikte mit Wrapper-Lösungen oder Styling-Konkurrenz mit entfernten Paketen.
Diese Darstellung wirft eine praktische Frage auf: Wenn das Komponenten-File bereits im Projekt vorhanden ist, warum bedeutet seine Änderung dann immer noch das Manuell Öffnen der Datei?
Die Antwort ist technischer Natur und entspricht nicht der ersten Vermutung, die die meisten Teams haben.
Visuelle Builder erwarten ein Prop-Katalog. Bei eigenem CVA-Code ist das anders.
Visuelle React-Bauwerke gehen in der Regel davon aus, dass Komponenten aus einem Paket stammen, das über eine stabile, dokumentierte Eigenschaftsstruktur verfügt. Material UI ist hier das klassische Beispiel: Eine Schaltfläche stellt Enums wie variant, size und color zur Verfügung. Ein Tool kann diese Typen lesen, Dropdown-Menüs anzeigen und wiederum Strings zurückgeben. Die Komponente bleibt dabei eine Black Box mit einer beschrifteten Steuerkonsole an der Vorderseite.
Offnen Sie stattdessen eine shadcn-Schaltfläche.
const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ...", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground ...", outline: "border border-input bg-background hover:bg-accent ...", }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8" }, }, defaultVariants: { variant: "default", size: "default" }, } )
Es gibt kein externes Eigenschaftskatalog. Es existiert lediglich eine Variante-Karte – reiner TypeScript in einer vom Projekt verwalteten Datei.
Häufig wird angenommen, dass dies für Entwicklungstools nachteilig ist: Es gibt nichts aus node_modules zu analysieren, keine ausgegrabenen Dokumentationsseiten, und jeder Fork entwickelt sich eigenständig weiter. Paketbasierte Editor können tatsächlich nicht diese Struktur anstreben.
Die stärkere Interpretation geht in die andere Richtung. Eine CVA-Karte enthält mehr Informationen als eine Eigenschaftstabelle, und diese Informationen befinden sich bereits auf der Festplatte.
Eine Eigenschaftstabelle gibt an, dass variant "contained" akzeptiert. Sie sagt jedoch nicht, was "contained" darstellt. Diese Zuordnung wird innerhalb der Bibliothek kompiliert. Um sie zu erlernen, muss man die Dokumentation lesen, raten oder es direkt ausprobieren.
Die CVA-Karte besagt, dass variant: "destructive" ist bg-destructive text-destructive-foreground. Die Beziehung ist als Datenquelle unter lokaler Kontrolle vorhanden. Nichts wird versteckt, weil nichts separat verpackt wurde.
Daher ist die Behauptung begrenzt:
Bei der strukturierten visuellen Bearbeitung muss man wissen, was eine Steuerelemente leistet – nicht nur, dass sie existiert. Paketkomponenten verbergen dieses Wissen in kompilierten Interna. shadcn hingegen speichert es als echtes Objekt im Repository. Nach diesem Maßstab sollte shadcn die einfachste Bibliothek für visuelle Bearbeitung sein – und nicht die schwierigste.
Was noch fehlt, ist eine strukturierte Variantebearbeitung, die durch Ihre eigene CVA-Karte in Ihrem eigenen Baum gesteuert wird. Es gibt zwar Tailwind-Visualisierer sowie shadcn-Themeneditor, doch diese Tools schreiben CSS-Variablen um. Dadurch ändert sich zwar, worauf bg-primary verweist; es ändert sich jedoch nicht, welche Variante ein Button verwendet oder welche Hilfsklassen diese Variante enthält. Der Hinderungsgrund für die zweite Aufgabe liegt eine Ebene unterhalb der Varianten.
Die eigentliche Hürde: Hilfsklassen werden erst nach einer Scannung zu CSS
MUI wendet den Stil zur Laufzeit an. Ein sx={{ p: 2 }}-Objekt wird während der Renderung der Komponente zu CSS – es muss nichts im Voraus berechnet oder gescannt werden. Die Verwendung von MUI in einem Browser ohne lokale Installation ist weiterhin schwierig, doch die Herausforderungen liegen in der Modulauflösung, den Singleton-Themen und -Instanzen sowie kohärenten Paketversionen – nicht in der Stilgenerierung.
Tailwind kehrt dieses Modell um. Es scannt den Quelltext nach Namen von Utility-Classes und gibt nur diese Regeln aus. Kein Scannen bedeutet keinen CSS-Code. Das Scannen des Quelltextes ist ein Teil des Build-Prozesses.
Bei einer Umgebung, die einen Anwendungsbaum ohne npm-Installation oder lokalen Watcher startet, stoßen auf Utility-Class-Komponenten, die vollständig kontrolliert werden, auf Hindernisse, mit denen MUI nie zu kämpfen hatte. button.tsx kann sauber transpiliert werden, erzeugt jedoch weiterhin einen ungestylten Steuerelement. Die Klassentoken erscheinen im DOM, bedeuten aber nichts, da kein Pipeline passende Regeln erzeugt hat.
Deshalb stockt die visuelle Entwicklungstooling-Technologie an dieser Schwelle. Die Varianten sind nicht die Ursache – das Styling schon.
Es gibt eine präzisere Beschreibung desselben Problems. Tailswinds Scanner liest die Quelltextzeichenketten. shadcn-Komponenten schreiben diese Zeichenketten an der Aufrufstelle oft gar nicht nieder – stattdessen rufen sie eine Funktion auf. Auf dieser Zeile darf kein "bg-destructive"-Literal erscheinen; die Laufzeit-Komposition verbindet dabei das CVA-Map, die eingehenden Props sowie die von außen vorgenommenen Überwrites.
Eine normale Build-Prozess handhabt das gut: Das Varianten-Map-Datei wird durchsucht, dort befinden sich die vollständigen Literale, und der Extraktor erkennt sie. Tailswinds eigentliche Regel lautet „Erstelle keine Klassenzeichenketten wie text-${color}-500.“ CVA-Maps enthalten vollständige Literale. shadcn ist das empfohlene Vorgehen, kein Workaround – und erfordert in einem ordnungsgemäßen Build keine Safelist.
Ohne eine lokale Pipeline wird nichts die Dateien durchsuchen, um Utilities zu finden – daher hat die Komposition keinen Ort, an dem sie sich registrieren kann. Deshalb ist das „no-install“-Konzept strenger als die allgemeine Erinnerung, dass Tailwind eine Kompilierung erwartet.
Lösung: Utilities aus dem gerenderten Markup extrahieren
Die praktikable Lösung besteht darin, das Scannen der Quelldateien aufzugeben.
Das Canvas steuert Tailwinds echten Laufzeitumgebung im Browser – den Quellcode, nicht einen selbstgebauten Parser. Die Wahl des Engines hängt von der deklarierten Major-Version ab: v3-Anwendungen laden eine in Studio bereitgestellte Play CDN-Kompilierung; v4+ laden auf dieselbe Weise @tailwindcss/browser. Das Herunterladen über das Netzwerk wird für Major-Versionen reserviert, bei denen keine gebundene Version vorhanden ist – man denke an ein hypothetisches v5-Projekt. Die tailwind.config-Datei des Projekts wird an den Engine übergeben, damit Theme-Erweiterungen weiterhin gültig bleiben.
Warum nicht den Extractor von Tailwind im Browser ausführen und trotzdem die Quelldateien scannen? Die Messungen machten die Kosten offensichtlich: Schon eine einzige Darstellung löst etwa 40 bis 340 Lesevorgänge der Host-Dateien aus, und bei großen Templates kann ein einziger Lesevorgang 5–8 ms in Anspruch nehmen, ohne dass durch Parallelisierung Vorteile entstehen. Das Scannen des Repos bedeutet, jede Datei zu lesen – nicht nur diejenigen, die tatsächlich dargestellt wurden. Trotz dieser Kosten bleiben die zur Laufzeit generierten Klassennamen weiterhin unberücksichtigt. Die Sammlung des DOMs bedeutet im Grunde den Austausch zwischen „das gesamte Baumstruktur lesen“ und „das bereits dargestellte Ergebnis nur einmal lesen“.
Der Betrieb des Engines ist erst die halbe Geschichte. Dann tritt die unangenehme Einschränkung in Erscheinung.
Der Engine ist auf das Dokument beschränkt und beobachtet nur den normalen DOM – sie ruft observe(document.documentElement) auf und tritt niemals in Schattenwurzeln ein. Im Design-Modus wird das Template zur Isolierung innerhalb einer Schattenwurzel dargestellt. Daher betrachtet die Engine das äußere Dokument, erkennt keine der Klassennamen der Komponenten und gibt nichts aus. Richtige Engine, richtige Konfiguration, leere Stylesheet.
Die Brücke, die sie wieder miteinander verbindet:
- Nach der Darstellung wird der Schatten-DOM durchsucht, um alle auf den echten Elementen vorhandenen Klassentoken zu sammeln.
- Die gesammelten Token werden in einen versteckten Node im normalen DOM kopiert, den der Beobachter sehen darf.
- Die Engine erstellt anschließend Regeln für genau die verwendeten Funktionen.
- Die resultierende Stylesheet wird wieder in die Schattenwurzel aufgenommen.
MutationObserver an der Schattenwurzel – dem Unterbaum – hinzu mit attributeFilter: ['class'], damit spätere Routenänderungen, verzögert geladener Inhalt sowie bedingte Benutzeroberflächen weiterhin den Empfänger aktualisieren.Ein Detail war von entscheidender Bedeutung: Der Empfänger muss mit dem nativen appendChild eingefügt werden. Das Canvas ersetzt document.body.appendChild, um Portale in die Schattenwurzel umzuleiten; wenn der modifizierte Pfad verwendet wird, bleibt der Empfänger innerhalb des Schattenbaums versteckt, wo das Engine-System ihn nicht sehen kann. Dadurch versagt die Verbindung schweigend.
Der Empfänger bleibt ein echter Light-DOM-Node und sammelt weiterhin Tokens während der Navigation – neue Routen liefern die benötigten Funktionen, ohne das Canvas neu laden zu müssen.
Ein weiteres Problem weist die gleiche Fehlerstruktur auf, obwohl es nicht spezifisch für shadcn ist. Viele Tailwind v3 Next-Vorlagen setzen important: '#__next', wodurch alle Utility-Elemente unter dieser Wurzel-ID abgegrenzt werden. Das Canvas verfügt nicht über , weshalb die Utility-Elemente nicht mehr übereinstimmen. Der Konfigurations-Selektor muss auf die Wurzel des Live-Renderings umgeleitet werden, und der Ersatz muss weiterhin ein ID-Selektor bleiben, da die Spezifität von (1,0,0) gegenüber dem eigenen CSS der Vorlage ausschlaggebend ist.
Der Vorteil des Abrufens aus dem DOM:
Jeder zur Laufzeit erstellte Klassenname funktioniert. cn(), clsx, twMerge, CVA, ternäre Operatoren, Abfrachtabellen oder die Verkettung aus Props – der Engine muss diese Formate niemals analysieren. Zum Zeitpunkt der Sammlung hat die Komposition bereits stattgefunden und das Ergebnis befindet sich im class-Attribut. Templates wie shadcn-admin rendern korrekt nicht wegen eines speziellen Falls bei shadcn, sondern weil der Ansatz unabhängig davon ist, wie der String erstellt wurde.
Die echte Grenze: Nur Klassen, die tatsächlich dargestellt werden, erhalten CSS. Eine normale Tailwind-Kompilierung durchsucht die Quelle und gibt beide Ausprägungen von cond ? 'bg-red-500' : 'bg-blue-500' aus, selbst wenn nur eine sichtbar sein kann. Die nicht verwendete Ausprägung bleibt bis zur Darstellung ungestylt. Der MutationObserver füllt sie auf, wenn sich der Zustand ändert – doch der erste Frame dieses Zustandswechsels kann ungestylt erscheinen. In der Praxis zeigt sich das als kurzer leerer Bereich beim ersten Aktivieren einer bisher nie gesehenen Variante.
Der Kompromiss wirkt sich in beide Richtungen aus. Betrachten Sie statusColors[res.status], wobei der Schlüssel von einer API stammt. Im Quellcode kann es keine vollständigen Utility-Literale geben, weshalb ein Scannings-Verfahren nichts ausgibt – Safelisten dienen dazu, diesen Fall abzudecken. Die DOM-Sammlung kümmert sich darum nicht; sobald die Klasse auf dem Element vorhanden ist, wird sie gesammelt. Die Quellcode-Scannung umfasst Zweige, die noch nicht ausgeführt wurden. Die DOM-Sammlung umfasst Klassennamen, die niemals als Literale geschrieben wurden. In einem Browser ohne Build wird die zweite Option größtenteils erzwungen.
Was diese Grundlage ermöglicht – und was nicht
Mit dieser Brücke kann ein shadcn-admin-stilisiertes Template – mit Klassennamen, CVA-Varianten und cn()-Kompositionen – ohne Installation, ohne Entwicklungsserver, mit dem echten Tailwind-Engine und der tatsächlichen Konfiguration des Projekts rendern.
Der Vorteil besteht nicht darin, dass die visuelle Bearbeitung abgeschlossen ist. Es geht vielmehr darum, eine andere Art von „Steuer“ zu beseitigen.
Das Öffnen eines unbekannten Admin-Vorlagenformats bedeutet in der Regel Klonen, Installieren, Warten, Ausführen, einige Minuten lang Klicks ausführen und anschließend Beenden. Der größte Teil dieser Zeit wird vom Rechner in Anspruch genommen. Dasselbe Problem tritt auf, wenn man einem Teamkollegen drei Zustände eines Komponentenelements zeigt, überprüft, ob der Dunkelmodus fertig ist, oder vor dem Vorschlag einer Änderung die Abstände eines Elements prüft. Solche Überprüfungen dauern nur Sekunden, sobald etwas auf dem Bildschirm angezeigt wird.
Genau dieser Aufwand durch Installieren und Warten verschwindet. Ein Shadcn-Projekt kann in Sekundenbruchteilen mit seinen eigentlichen Klassen arbeiten, danach funktionieren die auf Code ausgerichteten Studio-Funktionen unabhängig vom Stilsystem: Zweiwege-Selektion zwischen Code und Darstellungsbereich, Einblick in eine Komponente, die in mehreren Dateien definiert ist, sowie die Überprüfung, was passiert, wenn eine bestimmte Datei gelöscht wird. Diese Funktionen arbeiten mit der Quellstruktur und nicht damit, wie das CSS erzeugt wurde. Zunächst war lediglich eine erfolgreiche Darstellung erforderlich.
Was noch fehlt, ist der Styling-Inspektor: className wird weiterhin als rohe Zeichenkette bearbeitet, nichts liest die CVA-Karte zur Erstellung von Variante-Steuerelementen aus, und tailwind.config-Token erreichen den Engine-Teil, aber nicht das Panel. Die MUI-Tools verfügen bereits über Themen- und strukturierte sx-Bearbeitungsfunktionen; die Tailwind-Version hat das noch nicht. Diesen Unterschied klar zu benennen, ist besser, als zuzulassen, dass eine Demo etwas anderes vermuten lässt.
Die Reihenfolge ist der eigentliche Punkt, und von außen ist es leicht, diesen zu übersehen:
Weil shadcn mit Utilities statt mit Style-Attributen arbeitet, muss die Einbindung von zur Laufzeit generierten Klassenzuständen in die Seite mithilfe echten CSS vor dem Zeitpunkt erfolgen, an dem ein strukturierter Inspektor sie bearbeiten kann. Das Im Browser durchzuführen ist umständlich: Der offizielle Engine beobachtet das leichte DOM, während der Design-Modus innerhalb einer Shadow Root arbeitet. Die Lücke wird geschlossen, indem Klassen aus dem aktiven DOM – und nicht aus Dateien – abgerufen werden. Diese Grundlage steht endlich zur Verfügung.
Das darauf aufbauende System ist kleiner als die Grundlage, aber keineswegs einfach. Ein CVA-Map ist ein reales Objekt in einer lokalen Datei; es zu lesen und einen Variant-Selector darzustellen ist gewöhnliche UI-Arbeit. Eine Rückübertragung der Änderungen hingegen nicht. Ein Klassennamen kann gleichzeitig von mehreren Kompositionsorten stammen:
className={cn("border-b", isActive && "bg-red-500", buttonVariants({ variant }))}
Falls ein Panel einen Abstand von 6 festlegt, muss jemand entscheiden, welchen Textwert geändert werden soll – den Basisstring, die bedingte Variante oder die gemeinsame Variantendefinition, die von allen anderen Schaltern verwendet wird. Manchmal gibt es keine eindeutig richtige Antwort, nur Richtlinien. Dieses Problem bei der Rückschreibung ist der nächste schwierige Aspekt, und er ist auf eine andere Weise schwierig als die Styling-Brücke.
Vor Beginn der Arbeiten wäre es einfach gewesen zu erraten, welche Hälfte schwieriger ist – und dabei falschzuliegen.
Teams, die in shadcn arbeiten und sich darum kümmern, wie diese Steueroberfläche funktionieren sollte – Variandendropdowns gesteuert durch CVA, Abstandskontrollen, die Utility-Funktionen statt inline-Styles überschreiben, sowie Regeln dafür, welchen Textwert eine Rückschreibung betreffen soll – sind die richtige Zielgruppe, um diese Schicht bereits vor der Veröffentlichung zu gestalten und nicht erst danach.