Design System Builder
Dies ist keine generische Checkliste mit Best Practices. Jede Regel unten existiert, weil ihre Nichterstellung zu einem echten, ausgelieferten Bug geführt hat – die meisten stumm (kein Fehler, kein fehlgeschlagener Build, nur falsch im Browser). references/bug-catalog.md enthält den konkreten Vorfall hinter jeder Regel. Lies es einmal, bevor du ein System startest, das groß genug ist, um wichtig zu sein.
Der rote Faden: Eine Regel, die nur in einem Kommentar oder einem Dokument lebt, wird irgendwann versehentlich verletzt, und die Verletzung wird keinen Fehler auslösen. Jede Phase endet mit der Frage: „Wie wird dies automatisch durchgesetzt?“ – wenn die Antwort „jemand erinnert sich“ lautet, ist die Phase nicht abgeschlossen.
Zwei Einstiegspunkte:
- Neu bauen → Phasen 1–7 der Reihe nach.
- Vorhandenes System auditieren oder weiterentwickeln → Phase 8, die bei Bedarf auf Phasen 1–7 verweist.
Phase 1 – Entdecken
Fragen vor dem Bauen; Raten führt zu einem System, das niemand will, und wenn sichtbar wird, ist es teuer, zurückzurudern.
- Referenzmaterial als wörtliche Beispiele, nicht als Adjektive. „Modern und sauber“ spezifiziert nichts; drei Screenshots tun das.
- Trägt Farbe Bedeutung, oder ist sie nur ästhetisch? Wenn eine Palette kategoriale Werte hat (Element, Status, Markenlinie), entscheide vorab, ob jeder Wert auch ein Verhalten ist (Bewegung, Form, Gewicht) oder rein visuell. Nachträglich Bedeutung auf eine aus ästhetischen Gründen gewählte Farbe zu projizieren, ist viel schwieriger, als dies zuerst zu entscheiden.
- Worauf liegt der Inhalt? Gehe nicht davon aus, dass ein Seitenhintergrund und ein Panelhintergrund „ungefähr gleich“ sind. Miss Kandidatenkombinationen vor der Festlegung – siehe Phase 2.3.
- Umfang: Neubau, Verfeinerung eines bestehenden Systems oder Vergleich mit einem vorherigen/verwandten System? Führt zu Phase 8, wenn es kein reiner Neubau ist.
- Verteilung: Ein Projekt oder mehrere Projekte? Diese eine Antwort entscheidet, ob Phase 6 (Verpacken) überhaupt relevant ist. Falschließen führt entweder zu Überengineering für ein Einzelprojekt oder zu Unterengineering für etwas, das bald über drei Repositories driftet.
Phase 2 – Tokens erstellen
In Prioritätsreihenfolge – Fehler hier vererben eine falsche Grundlage auf alles, was darauf aufbaut.
2.1 – Farben werden einmal als rohe Kanaltripletts erstellt, nie als bloßes Hex.
--fire-rgb: 170 0 0; /* der einzige erstellte Wert */
--fire: rgb(var(--fire-rgb)); /* abgeleitet */
--fire-soft: rgb(var(--fire-rgb) / 0.18); /* abgeleitet */
Das ist nicht stilistisch. Tailwinds (und der meisten Utility-Systeme) Opazitätsmodifikatoren (bg-fire/40) können keinen Alphakanal für einen undurchsichtigen var(--x) Hex-Wert zusammensetzen – die Klasse löst sich stumm in nichts auf, kein Fehler, keine Warnung. Genau dieser Bug hat den Härtungspass ausgelöst, von dem dieses Muster stammt. Eine erstellte Zahl pro Farbe bedeutet auch, dass es nur einen Ort gibt, sie zu ändern.
2.2 – Jede Farbe, die als Text dargestellt werden kann, erhält eine -text Variante, und Glyphen verwenden diese, niemals die Basis.
Überprüfe den Kontrast durch Berechnung der WCAG-Relativluminanz (Formel in assets/check-tokens.template.mjs), nicht mit dem Auge. Eine Tönung, die 4,5:1 gegen eine Oberfläche erreicht, kann gegen eine andere scheitern – weshalb 2.3 wichtig ist.
2.3 – Wenn es mehr als eine Oberflächenebene gibt (Seite / Panel / vertieft), definiere sie als echte Höhenskala und miss den Abstand zwischen den Ebenen vor der Festlegung. „Etwas heller“ ist keine Entscheidung, sondern eine Schätzung. Ein reales System hat ein ganzes Höhenmodell (Tinte-als-Seite → Leere-als-Seite) nach der Auslieferung umgekehrt, nur weil jemand endlich den tatsächlichen zusammengesetzten Kontrast berechnet hat: 1,04:1 in eine Richtung, 1,19:1 in die andere. Führe diese Berechnung vor dem Bauen auf der Grundlage der Entscheidung durch, nicht nachdem ein Stakeholder sagt: „Die Karten wirken flach.“
Sobald eine Skala existiert, muss jeder textsichere Token auf Kontrast gegen jede Ebene, auf der er erscheinen kann, geprüft werden, nicht nur gegen die, für die er entworfen wurde.
2.4 – Bewegung: eine einzige Beschleunigungskurve, ein kleiner fester Satz von Dauern.
Wenn JS-Animationen (Framer Motion, GSAP, manuelles requestAnimationFrame) diese konsumieren, exportiere die Kurve und Dauern als JS-Konstanten zusammen mit den CSS-Werten – eine CSS-Variable ist für JavaScript unsichtbar, und eine handschriftliche Bezier oder Dauer driftet sofort und unsichtbar. Halte die CSS- und JS-Versionen gegeneinander geprüft (Phase 4).
2.5 – Numerische Obergrenzen für alles Leuchtende/Verlaufartige müssen echte Zahlen sein, geprüft gegen den tatsächlich ausgelieferten Wert – einschließlich Verlaufsstopps, nicht nur flache Einzelfarben mit Alpha. „Subtiles Leuchten“ ohne erzwungene Obergrenze ist, wie ganzseitiges Umgebungsleuchten zurückkommt, eine Komponente nach der anderen, jede für sich einzeln vernünftig aussehend.
2.6 – Jeder Token erhält einen einzeiligen Kommentar, der sagt, wann man ihn verwenden soll, nicht nur, wie er dargestellt wird.
Phase 3 – Primitive bauen
- Komponenten referenzieren ausschließlich Tokens. Kein Hex, keine willkürlichen Werte, keine
white/10-artigen Literale – genau das sucht der Wächter aus Phase 4, also schreibe sie jetzt nur, um den Check später zu bestehen. - Jede Verwendung von Opazitätsmodifikatoren (
bg-x/10) muss auf einen Token abzielen, der tatsächlich alpha-komponierbar gemäß 2.1 ist – überprüfbar, also überprüfe es, nicht nur anschauen. - Harte keine Variante in die Basisklassen einer Komponente ein, wenn eine Eigenschaft sie überschreiben soll.
className="shadow-lg"in jede Karte eingebacken, mit einer Eigenschaft, dieshadow-root-eartheinsetzen soll, erzeugt zwei Box-Shadow-Utilities mit gleicher Spezifität – der Gewinner wird durch die Quellreihenfolge im generierten Stylesheet bestimmt, stumm. Mache die Variante zu einer Nachschlagetabelle, die nach der Eigenschaft schlüsselt (Record<Variant, string>), niemals eine Basisklasse plus eine erwartete Überschreibung. - Eine Styling-Eigenschaft ist kein Ersatz für ein echtes interaktives Element. Eine Karte, die durch eine
interactive-Eigenschaft klickbar aussieht (cursor-pointer, Hover-Lift), aber ein<div>ist, gibt einem Mausbenutzer eine Affordanz, die ein Tastaturbenutzer nie erhält – kein Fokusring, keine Rolle, keine Aktivierung. Wenn etwas angeklickt werden soll, rendere ein<a>oder<button>(einCardLink/CardButton-Zwillingspaar), keinen Styling-Umschalter auf der passiven Version. - Für wirklich komplexe interaktive Komponenten (Modal, Tabs, Toast): Verlasse dich auf die Plattform. Natives
<dialog>bietet einen Fokus-Falle und Escape-zum-Schließen kostenlos; handgemachte Versionen machen zuverlässig einen davon falsch. Roving Tabindex für Tabs ist der häufigste Fehler in handgemachten Tab-Implementierungen (jeder inaktive Tab bleibt ein Tab-Stopp) – überprüfe dies explizit (Tabindexes bewegen sich[0,-1,-1,-1] → [-1,0,-1,-1]bei Pfeiltastennavigation), nimm es nicht an. - Wenn das System benutzerdefinierte Icon/Mark-Assets benötigt, lege die Zeichenregeln vorab fest (Raster, Strichstärke, Caps-Typ) und setze sie genauso wie Farbregeln durch – das ist ebenfalls Aufgabe von Phase 4.
Phase 4 – Absichern
Schreibe ein kleines, idealerweise dependencies-freies, Prüfskript. Dies ist das einzelne Artefakt mit dem höchsten Hebel im gesamten Prozess – jeder Bug in references/bug-catalog.md war eine Regel, die nur in Prosa existierte, bis jemand sie hier kodiert hat.
Minimale Abdeckung (passe assets/check-tokens.template.mjs an):
- Jeder Token, den eine Komponente oder ein Preset referenziert, existiert tatsächlich.
- Keine handschriftlichen Hex- oder Opazitätsfarbwörter in Komponentenquellen.
- WCAG-Kontrast für jeden textsicheren Token gegen jede Oberflächenebene, auf der er erscheinen kann (2.3).
- Dokumentierte numerische Obergrenzen als Zahlen auf den tatsächlich ausgelieferten Wert erzwungen – einschließlich des hellsten Stopps eines Verlaufs, nicht nur einfacher Farben (2.5).
- JS-Bewegungskonstanten stimmen mit ihren CSS-Token-Gegenstücken überein (2.4).
- Strukturelle Regeln, die leicht versehentlich verletzt werden können – z.B. „kein ganzseitiger Hintergrundglanz“: prüfen, dass eine Glanz-Utility nie auf ein Seitenebenen-Element angewendet wird, nicht nur, dass der Alpha-Wert des Tokens begrenzt ist.
- Beschränke jede Regel, die auf gewöhnliche Prosa passen könnte (z.B. eine Regel über „eine zweite Beschleunigungskurve“), auf tatsächliche Code-Kontexte – Klassenattribute, CSS-Werte – nicht auf beliebigen Text. Ein Linter, der die eigene Dokumentation markiert, wird deaktiviert, was den Zweck vereitelt.
Die Prüfung muss letztendlich gegen konsumierende Projekte laufen, nicht nur gegen das eigene Repository des Systems. Dies ist die einzelne größte Lehre hier: In einem realen System ist jeder Regelverstoß, der jemals tatsächlich passiert ist – elf Schriftgrößen unter 24px, drei verschiedene <h2>-Größen auf einer Website, fünf konkurrierende Buchstabenabstände, eine zweite Beschleunigungskurve, erfundene Dauern – in einem Projekt passiert, das das System konsumierte, und die interne Prüfung hat dort nie hingesehen. Sie hat lange Zeit Code bewacht, der nie eine Regel gebrochen hatte. Liefere den Prüfer so aus, dass er gegen beliebige Quellen ausführbar ist (npx your-check ./src), und gib ihm einen abgegrenzten Ausstieg für echte Ausnahmen (eine your-check-allow: <regel> — <grund> Kommentarkonvention), anstatt die Leute zu zwingen, die ganze Prüfung zu deaktivieren.
Negativteste jeden Check, bevor du ihm vertraust. Brich jede Regel absichtlich in einer Testkopie und bestätige, dass das Skript tatsächlich darauf fehlschlägt. Ein Check, der noch nie fehlgeschlagen beobachtet wurde, ist unverifiziert – du weißt nicht, ob er funktioniert oder stumm inaktiv ist.
Phase 5 – Dokumentieren
Drei Dateien, nach Zielgruppe getrennt – widerstehe dem Drang, eine vierte hinzuzufügen. Ein viertes Dokument (ein aggregiertes „design.md“, ein zweiter Styleguide als Dokument) ist ein zweiter Ort für dieselben Werte, um zu driften; drei, die jeweils eine eigene Frage besitzen (warum / was-tun / was-entschieden), reichen aus.
philosophy.md– das Warum, die Identität, die Bedeutung hinter Entscheidungen. Einmal lesen, selten wieder lesen.foundations.md– die operativen Regeln. Was jemand prüft, bevor er etwas Neues baut. Sollte grob 1:1 dem entsprechen, was der Wächter aus Phase 4 durchsetzt – wenn eine Regel in einem, aber nicht im anderen ist, entscheide, welches fehlt, und behebe es.decisions.md– ein laufendes Protokoll, neuester Eintrag zuerst, über spezifische Ermessensentscheidungen und warum. Lösche niemals einen ersetzten Eintrag. Markiere ihn als ersetzt und erkläre, was sich geändert hat und warum. Eine reale Entscheidung (ein Höhenmodell, eine Akzenthierarchie) ohne Papierspur rückgängig zu machen, zwingt die nächste Person – oft dein zukünftiges Ich – die Begründung von Grund auf neu abzuleiten oder, schlimmer, stillschweigend das zu brechen, was die ursprüngliche Entscheidung repariert hat.- Eine lebendige Präsentation, die die echten Tokens und Komponenten rendert – nicht eine Beschreibung von ihnen. Wenn sie das Paket nicht buchstäblich
importkann (z.B. ein statisches einseitiges HTML-Dokument), trägt sie eine zweite eingebettete Kopie jedes Werts, und diese Kopie wird driften: Die Präsentation eines realen Systems ist stillschweigend zwei Versionen hinter seinen eigenen Tokens zurückgefallen, bevor jemand sie als etwas, das das tatsächliche Paket importiert, neu gebaut hat. Der schnellste Fix, wenn eine statische Präsentation für eine Weile unvermeidbar ist, ist, dass Phase 4 die eingebettete Kopie gegen die reale Quelle überwacht – aber plane, dies zu beenden, sobald das System stabil genug ist, um einen echten Konsumenten zu haben. - Wenn die Präsentation deploybar ist, deploye sie auf eine echte URL und betrachte sie bei einer echten, schmalen Viewport. Ein Layout-Bug, der nur auftritt, wenn ein bestimmter Abschnitt schmaler als sein Inhalt ist (ein absichtlich überlaufendes dekoratives Element, das das gesamte Dokument verbreitert), ist unsichtbar für jede statische Prüfung – keine Tokenverletzung, kein Kontrastfehler, kein Lint-Fehler. Er existiert nur in einem echten Browser bei einer echten Breite. Ein solcher Bug wurde ausgeliefert und überlebte drei Versionen, weil der einzige Ort, an dem das System jemals angesehen wurde, eine lokale Datei war.
- Schreibe eine Versionierungsrichtlinie und die Reihenfolge, in der die Dokumente gelesen werden sollen, sobald es mehr als ein Dokument gibt. Kostet einen Absatz, erspart jemandem, sie neu abzuleiten.
Phase 6 – Verpacken und verteilen
Nur wenn Phase 1 sagte: „wiederverwendet über Projekte hinweg“.
- Liefere als installierbares Paket (
package.jsonmit einerexports-Map). Nie „kopiere diese Dateien in dein Projekt“ – Kopieren hat keinen Pfad für einen nachgelagerten Fix, um stromaufwärts zurückzufließen, was dazu führt, dass die eigene Wahrheitsquelle des Systems leise zur defekteren von zwei Kopien wird. Dies war die Ursache des allerersten Bugs in dieser gesamten Abstammungslinie. - Wenn ein Tailwind-Preset verteilt wird: Das
content-Array eines Presets wird NICHT von Tailwind zusammengeführt. Das eigenecontent-Array eines Konsumenten ersetzt das des Presets vollständig. Dies ist der zweitteuerste Bug im Katalog, und der „offensichtliche“ Fix – die Globs im Preset deklarieren und annehmen, sie kombinieren – funktioniert nicht; der Build kommt byte-identisch zurück, was der Hinweis ist. Das Preset muss seine Globs exportieren, und jedes Onboarding (README, ein Scaffold-Befehl falls vorhanden) muss den Konsumenten zeigen, wie sie sie explizit spreeden:content: [...preset.content, ...yourGlobs]. Überprüfe dies mit der eigenen Konfigurationsauflösungs-API des Bundlers, nimm es nicht an – ein unverändertes Build-Artefakt nach einem „Fix“ ist ein Fehlersignal, kein Zufall. - Versionieren mit Tags, nicht nur Branches. Ein Branch-Pin verschiebt sich still unter einem Konsumenten, sobald der Quell-Branch des Quell-Repositorys fortschreitet – genau so kann ein Konsument eine ungetestete Version ausliefern, ohne dass irgendjemand das entschieden hat. Wenn ein Konsument zu einem neuen Tag wechseln muss, denke daran, dass Lockfiles den exakten aufgelösten Commit unabhängig davon, was der Tag in
package.jsonsagt, pinnen können – erzwinge eine Neuauflösung und überprüfe, obnode_modulestatsächlich die neue Version meldet, bevor du einem Neubuild vertraust. - Wenn das Paket einen Scaffold/Init-Befehl für neue Konsumenten anbietet, lass es von Anfang an die korrekte, gespreadete Inhaltskonfiguration ausgeben – verlasse dich nicht auf die Disziplin jedes Konsumenten, das README zu lesen, bei einer Fußangel, die ernst genug ist, um einen eigenen Entscheidungslogeintrag zu haben.
Phase 7 – Verifizieren
Gilt kontinuierlich, nicht nur einmal am Ende.
- Teste gegen eine frische Installation oder einen Clone, nicht das Arbeitsverzeichnis. Ein Arbeitsverzeichnis versteckt fehlende Exporte, nicht begangene Fixes und veraltete Caches, auf die ein echter Konsument am ersten Tag treffen wird.
- Ein unverändertes Build-Artefakt nach einem „Fix“ ist ein Signal, dass der Fix nicht funktioniert hat, kein Zufall. Vergleiche die tatsächliche Ausgabe, bevor du glaubst, dass sich etwas geändert hat.
- Typüberprüfung ist keine Verifikation. Der schwerwiegendste Bug in diesem Katalog – eine Schriftart, die in völlig der falschen Schriftart dargestellt wird – hat jeden verfügbaren statischen Check bestanden und wurde nur entdeckt, indem die Komponente in einem echten Konsumenten gerendert und angesehen wurde. Rendere alles mindestens einmal in etwas Echtem, bevor du es als fertig bezeichnest.
- Misstraue bequem aussehenden Plattform-APIs für Verifikationsbehauptungen.
document.fonts.check()sieht aus wie das genau richtige Werkzeug für „enthält diese Schriftart tatsächlich diese Glyphe“ und lügt (es meldet av
[truncated for translation – remainder omitted]
Phase 8 – Auditieren oder weiterentwickeln eines bestehenden Systems
(Diese Phase ist in der Übersetzung nicht vollständig, ab dem Punkt „av...“ im Original abgeschnitten. Der vollständige Inhalt sollte ähnlich der beschriebenen Methode übersetzt werden.)
Führe die unten stehenden Schritte aus, wobei du für jede gefundene Lücke auf die entsprechende Phase in 1–7 verweist.
- Bestandsaufnahme – Sammle alle vorhandenen Tokens, Komponenten, Stylesheets und Dokumentation.
- Gegen die Regeln aus Phase 1–7 abgleichen – Verwende die Prüfer aus Phase 4 auf den existierenden Quellen. Notiere harte (automatisch geprüfte) und weiche (durch Menschen zu beurteilende) Verstöße.
- Härten – Wende für jeden harten Verstoß den Fix aus der entsprechenden Phase an (z.B. Tokens aus Phase 2 für nicht standardisierte Farben). Dokumentiere in
decisions.md. - Phase 4 Prüfer auf den Konsumenten ausweiten – Stelle sicher, dass der Prüfer gegen die Projekte läuft, die das System verwenden (siehe Lektion in Phase 4).
- Phase 6 für Verteilung – Wenn das System noch kein Paket ist, erstelle eines. Wenn es eines ist, prüfe auf die in Phase 6 genannten Fußangeln (Tailwind content, Branch-Pins).
- Dokumentation aktualisieren – Erstelle oder überarbeite die drei Dokumente aus Phase 5 basierend auf den gefundenen Lücken.
- Verifizieren – Siehe Phase 7, insbesondere die Warnung vor nicht verifizierten Prüfern und statischen Build-Artefakten.
Ende der SKILL.md