Viele Apps, ein System
Der typische Fehler: Eine Bibliothek wird für App A entwickelt, App B hat Buttons, die 4px höher und 2px runder sind, also fork App B den Button "nur dieses eine Mal". Fünf Apps später gibt es sechs Buttons, acht Selects und drei Modals, alle mit der gleichen Aufgabe.
Mache die gemeinsame Komponente token-gesteuert: Sie trägt Verhalten, Markup und Zustände, und jedes Theme liefert die Maße. Dann konsolidiere App für App.
Token-gesteuerte Komponenten
Lies jedes Maß, das zwischen Apps variiert, aus einem Theme-Token:
@theme {
--spacing-btn-sm: 1.75rem; --spacing-btn-px-sm: 0.625rem;
--spacing-btn-md: 2.25rem; --spacing-btn-px-md: 0.875rem;
--spacing-field: 2.125rem; /* input / select height */
--text-label: 0.8125rem;
}
md: "h-btn-md px-btn-px-md text-sm",
icon: "size-btn-md",
Dann:
- Eine Suite überschreibt die Tokens einmal in ihrem Theme, und jede App in der Suite erhält ihre eigenen gemessenen Controls.
- Radius, Fills und Schatten sind bereits Tokens (
rounded-field,bg-accent,--shadow-card). - Fülle Farben als Farbe plus eine dekorative Klasse, z. B.
bg-accent accent-fill. Ein Theme, das nur die Dekoration (einen Glanz) definiert, rendert dann nie einen transparenten Button.
Siehe references/button.tsx.
Konsolidierungsdurchgang, pro App
-
Lokale Kopien inventarisieren. Durchsuche jede App nach
export function (Btn|Button|IconBtn|Select|Field|Input|Modal|Dialog|Sheet|Badge|Empty|Pager|Toast|Stat|Status|Spinner). Zähle die Aufrufstellen. -
Ersetzen und Löschen, wo die gemeinsame Komponente nach dem Theming passt. Ordne Variantennamen explizit zu und dokumentiere die Zuordnung:
Lokaler Name Gemeinsame Variante primary,navyaccentsecondaryoutlinequiet,softsubtledanger(solid)danger-solid -
App-spezifische Teile bleiben app-spezifisch:
- Domain-Widgets, Diagramme und App-Chrome;
- Controls, deren Form die Marke ist (iOS-Slabs, Carbon-Flat-Controls, 48px-Pill-CTAs);
- Controls mit Verhalten, das die gemeinsame Komponente nicht hat (Tri-State-Checkbox, entfernbares Chips, ein "Alle"-Reset-Item, Multi-Select-Filter).
Ein dünner Wrapper ist in Ordnung, wenn er eine Sache hinzufügt. Notiere den Grund in einem Kommentar.
-
Entferne native
<select>s und auf Menüs basierende Selects zugunsten eines einheitlichen Selects.- Radix Select reserviert
""als "kein Wert": Verwende einen Sentinel wie"all"und bilde ihn zurück. - Leite
aria-labelledbyundaria-describedbyweiter. - Walkthrough-Skripte, die
selectOption()aufrufen, müssen auf den Combobox klicken, dann auf die Option.
- Radix Select reserviert
-
Überprüfe jede App anhand ihrer eigenen Screenshots (siehe
visual-regression-pass). Wirkliche Unterschiede sind meist beabsichtigt: Größen, Glanz, Press-Skalierung.
tailwind-merge Fallen (cn())
tailwind-merge kennt nur die eingebauten Maße von Tailwind. Benutzerdefinierte Tokens brechen es auf drei stille Arten:
- Unbekannte Spacing-Tokens.
cn("h-btn-md", "h-10")behält beide, da es nicht weiß, dassh-btn-mdeine Höhe ist, und die Stylesheet-Reihenfolge bestimmt den Gewinner. Eineh-10des Aufrufers geht gegen das Token verloren und ein 40px Button rendert bei 36px. Registriere Tokens:extendTailwindMerge({ extend: { theme: { spacing: ["btn-sm","btn-md","field", …] } } }). - Unbekannte Textgrößen.
cn("text-meta", "text-ink")behandelttext-metaals Farbe und wirft es weg. Registriere Größen:theme: { text: ["meta","micro","label"] }. - Benutzerdefinierte Schatten und Farben.
text-14nebentext-t3oder willkürlichesshadow-[var(--x)]neben einem benannten Schatten: dasselbe Problem. Wenn eine Codebasis überall eine geschlossene benutzerdefinierte Skala verwendet, verwende dort einfachesclsx.
Siehe references/cn.ts.
Fokus und Validierung
- Ein
aria-invalid:ring-*Style überschreibtfocus-visible:ring-*und versteckt den Fokusring bei einem fokussierten ungültigen Feld. Schreibearia-invalid:not-focus-visible:ring-…. - Nackte
<button>s in Formularen senden diese ab. Wenn der gemeinsame Button nicht standardmäßig `type="button"" ist, muss jede konvertierte Nicht-Submit-Aufrufstelle ihn setzen.