Files
Hayden Bleasel 55fae375a4 Update docs, internationalization (#35)
* Update Geistdocs

* Update geistdocs.tsx

* Run translation script

* Delete toc.tsx

* Update route.ts

* Update route.ts
2025-12-04 23:57:35 -08:00

280 lines
9.2 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Styling
description: Bedingtes und komponierbares Styling mit Tailwind-Klassen.
---
Moderne Komponentenbibliotheken benötigen flexible Styling-Systeme, die komplexe Anforderungen bewältigen können, ohne die Entwicklererfahrung zu beeinträchtigen. Die Kombination von Tailwind CSS mit intelligenter Zusammenführung von Klassen hat sich als leistungsfähiges Muster zum Erstellen anpassbarer Komponenten etabliert.
Dieser Ansatz löst die grundlegende Spannung zwischen dem Bereitstellen sinnvoller Voreinstellungen und der Ermöglichung vollständiger Anpassung eine Herausforderung, die Komponentenbibliotheken seit Jahren belastet.
## Das Problem mit traditionellem Styling
Traditionelle CSS-Ansätze führen häufig zu Spezifitätskonflikten, Stilüberschreibungen und unvorhersehbaren Ergebnissen. Wenn Sie `className="bg-blue-500"` an eine Komponente übergeben, die bereits `bg-red-500` enthält, welche Klasse gewinnt?
Ohne richtige Handhabung treffen beide Klassen zu und das Ergebnis hängt von vielen Faktoren ab CSS-Quellreihenfolge, der Spezifität der Klassen, dem Klassenzusammenführungsalgorithmus des Bundlers usw.
## Intelligentes Zusammenführen von Klassen
Die Bibliothek `tailwind-merge` löst dieses Problem, indem sie Tailwinds Klassenstruktur versteht und Konflikte intelligent auflöst. Wenn zwei Klassen dieselbe CSS-Eigenschaft ansprechen, behält sie nur die zuletzt auftretende bei.
```tsx title="Ohne tailwind-merge"
// Both bg-red-500 and bg-blue-500 apply - unpredictable result
<Button className="bg-blue-500" />
// Renders: className="bg-red-500 bg-blue-500"
```
```tsx title="Mit tailwind-merge"
import { twMerge } from 'tailwind-merge';
// bg-blue-500 wins as it comes last
const className = twMerge('bg-red-500', 'bg-blue-500');
// Returns: "bg-blue-500"
```
Das funktioniert für alle Tailwind-Utilities:
```tsx
twMerge('px-4 py-2', 'px-8'); // Returns: "py-2 px-8"
twMerge('text-sm', 'text-lg'); // Returns: "text-lg"
twMerge('hover:bg-red-500', 'hover:bg-blue-500'); // Returns: "hover:bg-blue-500"
```
Die Bibliothek versteht auch Tailwinds Modifier-System:
```tsx
// Modifiers are handled correctly
twMerge('hover:bg-red-500 focus:bg-red-500', 'hover:bg-blue-500');
// Returns: "focus:bg-red-500 hover:bg-blue-500"
```
## Bedingte Klassen
Oft müssen Klassen bedingt auf Basis von Props oder State angewendet werden. Die Bibliothek `clsx` bietet dafür eine saubere API:
```tsx title="Verwendung von clsx"
import clsx from 'clsx';
// Basic conditionals
clsx('base', isActive && 'active');
// Returns: "base active" (if isActive is true)
// Object syntax
clsx('base', {
'active': isActive,
'disabled': isDisabled,
});
// Arrays
clsx(['base', isLarge ? 'text-lg' : 'text-sm']);
// Mixed
clsx(
'base',
['array-item'],
{ 'object-conditional': true },
isActive && 'conditional'
);
```
Ein gängiges Muster ist es, eine Standardmenge an Klassen mit eingehenden Props sowie beliebiger eigener Logik zu verschmelzen:
```tsx title="component.tsx"
const Component = ({ className, ...props }: ComponentProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={cn(
"rounded-lg border bg-white shadow-sm",
isOpen && "bg-blue-500",
className
)}
{...props}
/>
);
};
```
## Die `cn` Utility-Funktion
Die `cn`-Funktion, popularisiert durch [shadcn/ui](https://ui.shadcn.com/), kombiniert `clsx` und `tailwind-merge`, um Ihnen sowohl bedingte Logik als auch intelligentes Zusammenführen zu bieten:
```tsx title="lib/utils.ts"
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
Die Stärke ergibt sich aus der Reihenfolge Basisstile zuerst, Bedingungsklassen zweit, Benutzerüberschreibungen zuletzt. Das sorgt für vorhersehbares Verhalten bei gleichzeitiger vollständiger Anpassbarkeit.
## Class Variance Authority (CVA)
Bei komplexen Komponenten mit vielen Varianten wird die manuelle Verwaltung bedingter Klassen schnell unübersichtlich. [Class Variance Authority (CVA)](https://cva.style/docs) bietet eine deklarative API zur Definition von Komponentenvarianten.
Zum Beispiel ein Auszug aus der [Button](https://ui.shadcn.com/docs/components/button)-Komponente von shadcn/ui:
```tsx title="@/components/ui/button.tsx"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
```
## Bewährte Verfahren
### 1. Reihenfolge ist wichtig
Wenden Sie Klassen immer in dieser Reihenfolge an:
1. Basisstile (immer angewendet)
2. Variantenstile (basierend auf Props)
3. Bedingte Stile (basierend auf State)
4. Benutzerüberschreibungen (className-Prop)
```tsx
className={cn(
'base-styles', // 1. Base
variant && variantStyles, // 2. Variants
isActive && 'active', // 3. Conditionals
className // 4. User overrides
)}
```
### 2. Dokumentieren Sie Ihre Varianten
Verwenden Sie TypeScript und JSDoc, um zu dokumentieren, was jede Variante bewirkt:
```tsx
type ButtonProps = {
/**
* The visual style of the button
* @default "primary"
*/
variant?: 'primary' | 'secondary' | 'destructive' | 'ghost';
/**
* The size of the button
* @default "md"
*/
size?: 'sm' | 'md' | 'lg';
};
```
### 3. Wiederholte Muster auslagern
Wenn Sie feststellen, dass Sie dieselbe bedingte Logik wiederholt schreiben, lagern Sie sie aus:
```tsx title="utils/styles.ts"
export const focusRing = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500';
export const disabled = 'disabled:pointer-events-none disabled:opacity-50';
// Use in components
className={cn(focusRing, disabled, className)}
```
## Migrationsleitfaden
Wenn Sie von einem anderen Styling-Ansatz migrieren, hier einige Hinweise zur Anpassung gängiger Muster:
### Von CSS Modules
```tsx title="Vorher - CSS Modules"
import styles from './Button.module.css';
<button className={`${styles.button} ${styles[variant]} ${className}`} />
```
```tsx title="Nachher - cn + Tailwind"
import { cn } from '@/lib/utils';
<button className={cn(
'px-4 py-2 rounded-lg',
variant === 'primary' && 'bg-blue-500 text-white',
className
)} />
```
### Von styled-components
```tsx title="Vorher - styled-components"
const Button = styled.button<{ $primary?: boolean }>`
padding: 8px 16px;
background: ${props => props.$primary ? 'blue' : 'gray'};
`;
```
```tsx title="Nachher - cn + Tailwind"
function Button({ primary, className, ...props }) {
return (
<button
className={cn(
'px-4 py-2',
primary ? 'bg-blue-500' : 'bg-gray-500',
className
)}
{...props}
/>
);
}
```
## Leistungsaspekte
Sowohl `clsx` als auch `tailwind-merge` sind stark optimiert, aber beachten Sie diese Tipps:
1. **Definieren Sie Varianten außerhalb von Komponenten** - CVA-Varianten sollten außerhalb der Komponente definiert werden, um eine Neuerstellung bei jedem Rendern zu vermeiden.
2. **Memoisieren Sie komplexe Berechnungen** - Wenn Sie teure bedingte Logik haben, überlegen Sie, diese zu memoisieren:
```tsx
const className = useMemo(
() => cn(
baseStyles,
expensiveComputation(props),
className
),
[props, className]
);
```
3. **Verwenden Sie CSS-Variablen für dynamische Werte** - Statt Klassen dynamisch zu generieren, verwenden Sie CSS-Variablen:
```tsx title="CSS-Variablen bevorzugen"
// Good
<div
className="bg-[var(--color)]"
style={{ '--color': dynamicColor } as React.CSSProperties}
/>
// Avoid
<div className={`bg-[${dynamicColor}]`} />
```
Die Kombination aus Tailwind CSS, intelligenter Klassen-Zusammenführung und Varianten-APIs bietet eine robuste Grundlage für das Styling von Komponenten. Dieser Ansatz skaliert von einfachen Buttons bis hin zu komplexen Designsystemen, während er Vorhersagbarkeit und eine gute Entwicklererfahrung beibehält.