Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,589 @@
|
||||
# Radix UI: Accessible Headless Components für React
|
||||
|
||||
**Meta-Description:** Radix UI Primitives für barrierefreie React-Komponenten. Headless Architecture, WAI-ARIA Compliance und shadcn/ui Integration.
|
||||
|
||||
**Keywords:** Radix UI, Headless Components, Accessibility, WAI-ARIA, React Components, shadcn/ui, Unstyled Components
|
||||
|
||||
---
|
||||
|
||||
## Einführung
|
||||
|
||||
Radix UI ist eine **Headless Component Library** für React. Statt vorgestylter Komponenten liefert sie zugängliche, ungestylte Bausteine – perfekte Grundlage für Design Systems mit voller Accessibility out-of-the-box.
|
||||
|
||||
---
|
||||
|
||||
## Warum Radix UI?
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ RADIX UI VORTEILE │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Accessibility (A11Y): │
|
||||
│ ├── WAI-ARIA Design Patterns │
|
||||
│ ├── Keyboard Navigation │
|
||||
│ ├── Focus Management │
|
||||
│ ├── Screen Reader Support │
|
||||
│ └── Reduced Motion Support │
|
||||
│ │
|
||||
│ Headless Architecture: │
|
||||
│ ├── Zero Styles (volle Kontrolle) │
|
||||
│ ├── Jedes Styling möglich (CSS, Tailwind, CSS-in-JS) │
|
||||
│ ├── Kein UI Lock-in │
|
||||
│ └── Perfekt für Design Systems │
|
||||
│ │
|
||||
│ Developer Experience: │
|
||||
│ ├── Composable APIs │
|
||||
│ ├── TypeScript Support │
|
||||
│ ├── SSR Compatible │
|
||||
│ └── Tree-Shakeable │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Einzelne Primitives installieren
|
||||
npm install @radix-ui/react-dialog
|
||||
npm install @radix-ui/react-dropdown-menu
|
||||
npm install @radix-ui/react-tabs
|
||||
npm install @radix-ui/react-tooltip
|
||||
|
||||
# Oder alle Primitives
|
||||
npm install @radix-ui/primitives
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dialog (Modal)
|
||||
|
||||
```typescript
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
function ConfirmDialog({
|
||||
trigger,
|
||||
title,
|
||||
description,
|
||||
onConfirm
|
||||
}: {
|
||||
trigger: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
onConfirm: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger asChild>
|
||||
{trigger}
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black/50 animate-fade-in" />
|
||||
|
||||
<Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 w-[90vw] max-w-md shadow-xl animate-scale-in">
|
||||
<Dialog.Title className="text-lg font-semibold">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
|
||||
<Dialog.Description className="text-gray-500 mt-2">
|
||||
{description}
|
||||
</Dialog.Description>
|
||||
|
||||
<div className="flex justify-end gap-3 mt-6">
|
||||
<Dialog.Close asChild>
|
||||
<button className="px-4 py-2 rounded border hover:bg-gray-100">
|
||||
Abbrechen
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 rounded bg-red-500 text-white hover:bg-red-600"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
className="absolute top-4 right-4 p-1 rounded-full hover:bg-gray-100"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
|
||||
// Verwendung
|
||||
<ConfirmDialog
|
||||
trigger={<button>Löschen</button>}
|
||||
title="Eintrag löschen?"
|
||||
description="Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
onConfirm={() => deleteItem(id)}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dropdown Menu
|
||||
|
||||
```typescript
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { Check, ChevronRight, Circle } from 'lucide-react';
|
||||
|
||||
function UserMenu({ user }: { user: User }) {
|
||||
const [bookmarksChecked, setBookmarksChecked] = useState(true);
|
||||
const [person, setPerson] = useState('pedro');
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="flex items-center gap-2 p-2 rounded-full hover:bg-gray-100">
|
||||
<img
|
||||
src={user.avatar}
|
||||
alt={user.name}
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="min-w-[220px] bg-white rounded-md shadow-lg p-1 animate-slide-down"
|
||||
sideOffset={5}
|
||||
>
|
||||
{/* Regular Items */}
|
||||
<DropdownMenu.Item className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none">
|
||||
Profil
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none">
|
||||
Einstellungen
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
|
||||
|
||||
{/* Checkbox Item */}
|
||||
<DropdownMenu.CheckboxItem
|
||||
className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none flex items-center"
|
||||
checked={bookmarksChecked}
|
||||
onCheckedChange={setBookmarksChecked}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator className="mr-2">
|
||||
<Check className="w-4 h-4" />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
Lesezeichen anzeigen
|
||||
</DropdownMenu.CheckboxItem>
|
||||
|
||||
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
|
||||
|
||||
{/* Radio Group */}
|
||||
<DropdownMenu.Label className="px-3 py-1 text-xs text-gray-500">
|
||||
Personen
|
||||
</DropdownMenu.Label>
|
||||
|
||||
<DropdownMenu.RadioGroup value={person} onValueChange={setPerson}>
|
||||
<DropdownMenu.RadioItem
|
||||
className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none flex items-center"
|
||||
value="pedro"
|
||||
>
|
||||
<DropdownMenu.ItemIndicator className="mr-2">
|
||||
<Circle className="w-2 h-2 fill-current" />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
Pedro
|
||||
</DropdownMenu.RadioItem>
|
||||
|
||||
<DropdownMenu.RadioItem
|
||||
className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none flex items-center"
|
||||
value="maria"
|
||||
>
|
||||
<DropdownMenu.ItemIndicator className="mr-2">
|
||||
<Circle className="w-2 h-2 fill-current" />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
Maria
|
||||
</DropdownMenu.RadioItem>
|
||||
</DropdownMenu.RadioGroup>
|
||||
|
||||
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
|
||||
|
||||
{/* Sub Menu */}
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none flex items-center justify-between">
|
||||
Mehr
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.SubContent
|
||||
className="min-w-[180px] bg-white rounded-md shadow-lg p-1"
|
||||
sideOffset={2}
|
||||
>
|
||||
<DropdownMenu.Item className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none">
|
||||
Hilfe
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item className="px-3 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none">
|
||||
Über uns
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Sub>
|
||||
|
||||
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
|
||||
|
||||
{/* Destructive Item */}
|
||||
<DropdownMenu.Item className="px-3 py-2 rounded cursor-pointer hover:bg-red-100 text-red-600 outline-none">
|
||||
Abmelden
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tabs
|
||||
|
||||
```typescript
|
||||
import * as Tabs from '@radix-ui/react-tabs';
|
||||
|
||||
function SettingsTabs() {
|
||||
return (
|
||||
<Tabs.Root defaultValue="account" className="w-full max-w-lg">
|
||||
<Tabs.List className="flex border-b" aria-label="Einstellungen">
|
||||
<Tabs.Trigger
|
||||
value="account"
|
||||
className="px-4 py-2 border-b-2 border-transparent data-[state=active]:border-blue-500 data-[state=active]:text-blue-600 hover:text-gray-700"
|
||||
>
|
||||
Account
|
||||
</Tabs.Trigger>
|
||||
|
||||
<Tabs.Trigger
|
||||
value="password"
|
||||
className="px-4 py-2 border-b-2 border-transparent data-[state=active]:border-blue-500 data-[state=active]:text-blue-600 hover:text-gray-700"
|
||||
>
|
||||
Passwort
|
||||
</Tabs.Trigger>
|
||||
|
||||
<Tabs.Trigger
|
||||
value="notifications"
|
||||
className="px-4 py-2 border-b-2 border-transparent data-[state=active]:border-blue-500 data-[state=active]:text-blue-600 hover:text-gray-700"
|
||||
>
|
||||
Benachrichtigungen
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="account" className="p-4">
|
||||
<h3 className="font-semibold mb-4">Account-Einstellungen</h3>
|
||||
<form>
|
||||
<label className="block mb-2">
|
||||
Name
|
||||
<input type="text" className="w-full border rounded px-3 py-2 mt-1" />
|
||||
</label>
|
||||
<label className="block mb-4">
|
||||
E-Mail
|
||||
<input type="email" className="w-full border rounded px-3 py-2 mt-1" />
|
||||
</label>
|
||||
<button className="px-4 py-2 bg-blue-500 text-white rounded">
|
||||
Speichern
|
||||
</button>
|
||||
</form>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="password" className="p-4">
|
||||
<h3 className="font-semibold mb-4">Passwort ändern</h3>
|
||||
{/* Passwort Form */}
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="notifications" className="p-4">
|
||||
<h3 className="font-semibold mb-4">Benachrichtigungen</h3>
|
||||
{/* Notification Settings */}
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tooltip
|
||||
|
||||
```typescript
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
function IconButton({
|
||||
icon,
|
||||
label,
|
||||
onClick
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Tooltip.Provider delayDuration={200}>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="p-2 rounded-full hover:bg-gray-100"
|
||||
aria-label={label}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="bg-gray-900 text-white px-3 py-1.5 rounded text-sm animate-fade-in"
|
||||
sideOffset={5}
|
||||
>
|
||||
{label}
|
||||
<Tooltip.Arrow className="fill-gray-900" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Verwendung
|
||||
<IconButton
|
||||
icon={<Settings className="w-5 h-5" />}
|
||||
label="Einstellungen"
|
||||
onClick={() => openSettings()}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accordion
|
||||
|
||||
```typescript
|
||||
import * as Accordion from '@radix-ui/react-accordion';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
interface FAQItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
function FAQ({ items }: { items: FAQItem[] }) {
|
||||
return (
|
||||
<Accordion.Root type="single" collapsible className="w-full max-w-lg">
|
||||
{items.map((item, index) => (
|
||||
<Accordion.Item
|
||||
key={index}
|
||||
value={`item-${index}`}
|
||||
className="border-b"
|
||||
>
|
||||
<Accordion.Header>
|
||||
<Accordion.Trigger className="flex items-center justify-between w-full py-4 text-left hover:underline group">
|
||||
{item.question}
|
||||
<ChevronDown className="w-4 h-4 transition-transform group-data-[state=open]:rotate-180" />
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
|
||||
<Accordion.Content className="overflow-hidden data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up">
|
||||
<div className="pb-4 text-gray-600">
|
||||
{item.answer}
|
||||
</div>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
))}
|
||||
</Accordion.Root>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Select
|
||||
|
||||
```typescript
|
||||
import * as Select from '@radix-ui/react-select';
|
||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
function CustomSelect({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
placeholder = 'Auswählen...'
|
||||
}: {
|
||||
options: Option[];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
return (
|
||||
<Select.Root value={value} onValueChange={onChange}>
|
||||
<Select.Trigger className="inline-flex items-center justify-between px-4 py-2 border rounded-md bg-white min-w-[180px] hover:bg-gray-50">
|
||||
<Select.Value placeholder={placeholder} />
|
||||
<Select.Icon>
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
</Select.Icon>
|
||||
</Select.Trigger>
|
||||
|
||||
<Select.Portal>
|
||||
<Select.Content className="bg-white rounded-md shadow-lg border overflow-hidden">
|
||||
<Select.ScrollUpButton className="flex items-center justify-center h-6 bg-white cursor-default">
|
||||
<ChevronUp className="w-4 h-4" />
|
||||
</Select.ScrollUpButton>
|
||||
|
||||
<Select.Viewport className="p-1">
|
||||
{options.map((option) => (
|
||||
<Select.Item
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className="flex items-center px-8 py-2 rounded cursor-pointer hover:bg-gray-100 outline-none relative"
|
||||
>
|
||||
<Select.ItemIndicator className="absolute left-2">
|
||||
<Check className="w-4 h-4" />
|
||||
</Select.ItemIndicator>
|
||||
<Select.ItemText>{option.label}</Select.ItemText>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Viewport>
|
||||
|
||||
<Select.ScrollDownButton className="flex items-center justify-center h-6 bg-white cursor-default">
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
</Select.ScrollDownButton>
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## shadcn/ui Integration
|
||||
|
||||
shadcn/ui baut auf Radix Primitives und Tailwind CSS:
|
||||
|
||||
```bash
|
||||
# shadcn/ui initialisieren
|
||||
npx shadcn@latest init
|
||||
|
||||
# Komponenten hinzufügen
|
||||
npx shadcn@latest add button
|
||||
npx shadcn@latest add dialog
|
||||
npx shadcn@latest add dropdown-menu
|
||||
```
|
||||
|
||||
```typescript
|
||||
// components/ui/dialog.tsx (generiert von shadcn/ui)
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPrimitive.Portal>
|
||||
<DialogPrimitive.Overlay className="fixed inset-0 z-50 bg-black/80" />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]',
|
||||
'w-full max-w-lg rounded-lg bg-white p-6 shadow-lg',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPrimitive.Portal>
|
||||
));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Best Practices
|
||||
|
||||
```typescript
|
||||
// 1. Keyboard Navigation
|
||||
// Radix handled automatisch:
|
||||
// - Tab: Fokus zwischen interaktiven Elementen
|
||||
// - Enter/Space: Aktivierung
|
||||
// - Escape: Schließen von Overlays
|
||||
// - Arrow Keys: Navigation in Listen/Menüs
|
||||
|
||||
// 2. ARIA Attributes
|
||||
// Automatisch gesetzt von Radix:
|
||||
// - aria-expanded
|
||||
// - aria-controls
|
||||
// - aria-labelledby
|
||||
// - role="dialog", role="menu", etc.
|
||||
|
||||
// 3. Focus Management
|
||||
// Radix handled:
|
||||
// - Focus Trap in Modals
|
||||
// - Focus Restore beim Schließen
|
||||
// - Focus auf erstes interaktives Element
|
||||
|
||||
// 4. Reduced Motion
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
|
||||
// CSS mit prefers-reduced-motion
|
||||
const styles = `
|
||||
.dialog-content {
|
||||
animation: slideIn 200ms ease-out;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dialog-content {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
Radix UI bietet:
|
||||
|
||||
1. **Volle Accessibility**: WAI-ARIA compliant out-of-the-box
|
||||
2. **Styling-Freiheit**: Headless Architecture, kein UI Lock-in
|
||||
3. **Composable**: Flexible, zusammensetzbare APIs
|
||||
4. **shadcn/ui Basis**: Foundation für moderne Design Systems
|
||||
|
||||
Für barrierefreie React-Anwendungen ist Radix UI die beste Grundlage.
|
||||
|
||||
---
|
||||
|
||||
## Bildprompts
|
||||
|
||||
1. "Accessibility symbols surrounding React components, inclusive design concept"
|
||||
2. "Unstyled building blocks transforming into styled components, headless architecture"
|
||||
3. "Keyboard navigation flow through UI components, accessibility visualization"
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [Radix UI Documentation](https://www.radix-ui.com/)
|
||||
- [Radix Primitives](https://www.radix-ui.com/primitives)
|
||||
- [shadcn/ui](https://ui.shadcn.com/)
|
||||
- [WAI-ARIA Design Patterns](https://www.w3.org/WAI/ARIA/apg/patterns/)
|
||||
Reference in New Issue
Block a user