Radix UI mit Claude Code: Barrierefreie UI-Komponenten 2026

Radix UI liefert unstyled, zugängliche Primitives die WAI-ARIA vollständig implementieren — Dialog, DropdownMenu, Tabs, Toast und viele mehr. Claude Code kennt jeden Primitive und generiert barrierefreie Komponenten ohne Kompromisse beim Styling.

Was ist Radix UI? Headless vs. Opinionated

Die meisten UI-Bibliotheken kommen mit vorgefertigten Styles — praktisch, aber einschränkend. Radix UI geht einen anderen Weg: Headless Primitives die alle Accessibility-Anforderungen erfüllen, ohne eine einzige CSS-Regel vorzuschreiben.

HeadlessRadix vs. opinionated Libraries im Vergleich

EigenschaftRadix UIMUI / Chakra UIshadcn/ui
Eigene Styles✗ Keine✓ Material / Custom✓ Tailwind (kopierbar)
WAI-ARIA komplett✓ Built-in✓ Meist✓ Via Radix
Keyboard Navigation✓ Komplett✓ Meist✓ Via Radix
Bundle Size (Tree-Shaking)✓ Per-Paket✗ Groß✓ Nur was du nutzt
Styling-Freiheit✓ Vollständig✗ Eingeschränkt✓ Vollständig
Focus Trapping✓ Automatisch✓ Meist✓ Via Radix
shadcn/ui Tipp: shadcn/ui ist kein Package — es ist eine Sammlung von Radix-basierten Komponenten die du direkt in dein Projekt kopierst. Claude Code kann shadcn-Komponenten generieren, anpassen und erweitern — vollständig Radix-basiert.

WAI-ARIAWas Radix automatisch implementiert

# Prompt: "Erkläre was Radix UI automatisch für Accessibility bereitstellt" // Was du schreibst: <Dialog.Root> <Dialog.Trigger>Öffnen</Dialog.Trigger> <Dialog.Content> <Dialog.Title>Mein Dialog</Dialog.Title> <Dialog.Description>Beschreibung</Dialog.Description> </Dialog.Content> </Dialog.Root> // Was Radix im DOM erzeugt: <button aria-haspopup="dialog" aria-expanded="false" aria-controls="dialog-content-id" >Öffnen</button> <div role="dialog" id="dialog-content-id" aria-modal="true" aria-labelledby="dialog-title-id" aria-describedby="dialog-desc-id" tabindex="-1" > <h2 id="dialog-title-id">Mein Dialog</h2> <p id="dialog-desc-id">Beschreibung</p> </div> // Automatisch dabei: // → Focus wird auf Dialog-Content gesetzt beim Öffnen // → Focus-Trap: Tab bleibt im Dialog // → Escape schließt den Dialog // → Scroll-Lock auf Body // → Focus kehrt zum Trigger zurück beim Schließen // → Screen Reader-Ankündigung via aria-live

InstallationRadix Pakete und Projektsetup

# Prompt: "Installiere Radix UI Primitives für mein React + Tailwind Projekt" # Einzelne Pakete — nur was du brauchst: npm install @radix-ui/react-dialog npm install @radix-ui/react-dropdown-menu npm install @radix-ui/react-tabs npm install @radix-ui/react-accordion npm install @radix-ui/react-toast npm install @radix-ui/react-popover npm install @radix-ui/react-select npm install @radix-ui/react-checkbox npm install @radix-ui/react-switch npm install @radix-ui/react-tooltip # Oder alle auf einmal (für größere Projekte): npm install @radix-ui/react-{dialog,dropdown-menu,tabs,accordion,toast,popover,select,checkbox,switch,tooltip,label,separator,slot} # Hilfspakete für Animationen: npm install class-variance-authority clsx tailwind-merge npm install tailwindcss-animate # Für Tailwind-Animationen
Tree-Shaking: Jeder Radix Primitive ist ein eigenes npm-Paket. Du zahlst im Bundle nur für das, was du tatsächlich importierst. Dialog allein sind ~8 KB gzipped — deutlich weniger als eine komplette UI-Bibliothek.

Dialog und AlertDialog: Modals korrekt umgesetzt

Der Dialog ist der komplexeste UI-Primitive überhaupt — Focus Trapping, ARIA-Rollen, Escape-Key, Portal-Rendering, Scroll-Lock. Radix löst all das. Claude Code weiß genau, wann Dialog vs. AlertDialog angemessen ist.

PortalControlled Dialog mit Tailwind-Styling

// components/ui/Dialog.tsx // Prompt: "Erstelle einen vollständig gestylten Radix Dialog mit Tailwind" import * as DialogPrimitive from '@radix-ui/react-dialog' import { cn } from '@/lib/utils' // Overlay: Hintergrund-Dimmer mit Fade-Animation const DialogOverlay = React.forwardRef<...>(({ className, ...props }, ref) => ( <DialogPrimitive.Overlay ref={ref} className={cn( "fixed inset-0 z-50 bg-black/60 backdrop-blur-sm", "data-[state=open]:animate-in data-[state=open]:fade-in-0", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0", className )} {...props} /> )) // Content: Der eigentliche Dialog mit Slide-Animation const DialogContent = React.forwardRef<...>(({ className, children, ...props }, ref) => ( <DialogPrimitive.Portal> <DialogOverlay /> <DialogPrimitive.Content ref={ref} className={cn( "fixed left-[50%] top-[50%] z-50 w-full max-w-lg", "translate-x-[-50%] translate-y-[-50%]", "bg-white rounded-xl shadow-2xl p-6", "data-[state=open]:animate-in data-[state=open]:fade-in-0", "data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-top-4", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0", "data-[state=closed]:zoom-out-95", className )} {...props} > {children} // Schließen-Button — Radix setzt bereits Escape-Key-Handling! <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-violet-500" aria-label="Schließen" > <X className="h-4 w-4" /> </DialogPrimitive.Close> </DialogPrimitive.Content> </DialogPrimitive.Portal> )) // Nutzung — controlled state: const [open, setOpen] = useState(false) <Dialog.Root open={open} onOpenChange={setOpen}> <Dialog.Trigger asChild> <button className="bg-violet-600 text-white px-4 py-2 rounded-lg"> Dialog öffnen </button> </Dialog.Trigger> <DialogContent> <Dialog.Title className="text-lg font-semibold mb-2"> Einstellungen </Dialog.Title> <Dialog.Description className="text-sm text-gray-500 mb-4"> Passe dein Profil und deine Präferenzen an. </Dialog.Description> </* ... Form-Content ... */> </DialogContent> </Dialog.Root>
asChild Pattern: asChild übergibt alle Props (inklusive ARIA) an das Kind-Element statt ein eigenes DOM-Element zu rendern. So bleibt dein HTML-Markup sauber und du hast volle Kontrolle über den gerenderten Tag.

AlertDialogDestruktive Aktionen korrekt absichern

// AlertDialog: Für irreversible Aktionen (Löschen, Abbrechen etc.) // Prompt: "AlertDialog für Bestätigung einer Lösch-Aktion" import * as AlertDialog from '@radix-ui/react-alert-dialog' function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) { return ( <AlertDialog.Root> <AlertDialog.Trigger asChild> <button className="text-red-600 hover:text-red-700"> Projekt löschen </button> </AlertDialog.Trigger> <AlertDialog.Portal> <AlertDialog.Overlay className="fixed inset-0 bg-black/50" /> <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl p-6 max-w-md shadow-2xl"> <AlertDialog.Title className="text-lg font-semibold text-red-600"> Projekt unwiderruflich löschen? </AlertDialog.Title> <AlertDialog.Description className="text-sm text-gray-600 mt-2 mb-6"> Diese Aktion kann nicht rückgängig gemacht werden. Alle Daten, Dateien und Konfigurationen werden permanent gelöscht. </AlertDialog.Description> <div className="flex gap-3 justify-end"> <// Cancel: Kein autofocus, Nutzer muss aktiv bestätigen> <AlertDialog.Cancel className="px-4 py-2 border rounded-lg hover:bg-gray-50"> Abbrechen </AlertDialog.Cancel> <AlertDialog.Action onClick={onConfirm} className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700" > Ja, endgültig löschen </AlertDialog.Action> </div> </AlertDialog.Content> </AlertDialog.Portal> </AlertDialog.Root> ) } // Unterschied Dialog vs AlertDialog: // Dialog: Escape schließt, Klick auf Overlay schließt — für nicht-destruktive Aktionen // AlertDialog: KEIN Escape-Close, KEIN Overlay-Close — Nutzer MUSS wählen
Wichtiger Unterschied: AlertDialog verhindert das Schließen per Escape oder Overlay-Klick. Das ist kein Bug — WAI-ARIA schreibt vor, dass destruktive Bestätigungen eine explizite Entscheidung erfordern. Claude Code setzt das automatisch korrekt um.

DropdownMenu und ContextMenu: Keyboard-Navigation inklusive

Menüs sind überraschend komplex: Sub-Menus, Checkboxen, Radio-Gruppen, Keyboard-Navigation mit Pfeiltasten und Buchstabensuche — Radix implementiert den gesamten ARIA Authoring Practices Guide.

KeyboardDropdownMenu mit Sub-Menus und Checkboxen

// Prompt: "Dropdown mit Sub-Menu, Checkboxen und Radio-Gruppe in Tailwind" import * as DropdownMenu from '@radix-ui/react-dropdown-menu' const menuItemClass = cn( "flex items-center gap-2 px-3 py-1.5 text-sm rounded-md cursor-default", "select-none outline-none", "focus:bg-violet-50 focus:text-violet-900", "data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed" ) function ActionsMenu() { const [showGrid, setShowGrid] = useState(true) const [theme, setTheme] = useState("system") return ( <DropdownMenu.Root> <DropdownMenu.Trigger asChild> <button className="flex items-center gap-1 px-3 py-2 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-violet-500"> Aktionen <ChevronDown className="h-4 w-4" /> </button> </DropdownMenu.Trigger> <DropdownMenu.Portal> <DropdownMenu.Content className="z-50 min-w-48 bg-white rounded-xl shadow-lg p-1 border border-gray-200 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2" sideOffset={5} align="end" > <// Normale Items> <DropdownMenu.Item className={menuItemClass} onSelect={() => handleEdit()}> <Pencil className="h-4 w-4" /> Bearbeiten </DropdownMenu.Item> <DropdownMenu.Separator className="h-px bg-gray-200 my-1" /> <// Checkbox-Item> <DropdownMenu.CheckboxItem className={menuItemClass} checked={showGrid} onCheckedChange={setShowGrid} > <DropdownMenu.ItemIndicator> <Check className="h-4 w-4 text-violet-600" /> </DropdownMenu.ItemIndicator> Gitter anzeigen </DropdownMenu.CheckboxItem> <DropdownMenu.Separator className="h-px bg-gray-200 my-1" /> <// Sub-Menu> <DropdownMenu.Sub> <DropdownMenu.SubTrigger className={cn(menuItemClass, "justify-between")}> <span>Farbschema</span> <ChevronRight className="h-4 w-4" /> </DropdownMenu.SubTrigger> <DropdownMenu.SubContent className="..." sideOffset={2}> <DropdownMenu.RadioGroup value={theme} onValueChange={setTheme}> {["light", "dark", "system"].map(t => ( <DropdownMenu.RadioItem key={t} value={t} className={menuItemClass}> <DropdownMenu.ItemIndicator> <Circle className="h-2 w-2 fill-violet-600" /> </DropdownMenu.ItemIndicator> {t === "light" ? "Hell" : t === "dark" ? "Dunkel" : "System"} </DropdownMenu.RadioItem> ))} </DropdownMenu.RadioGroup> </DropdownMenu.SubContent> </DropdownMenu.Sub> </DropdownMenu.Content> </DropdownMenu.Portal> </DropdownMenu.Root> ) }
Keyboard-Navigation automatisch: Pfeiltasten navigieren zwischen Items, Buchstaben springen zum ersten passenden Item, Enter/Space aktivieren, Escape schließt, Tab verlässt das Menü. Alles ohne eine Zeile eigenen Event-Handler-Code.

ContextMenuRechtsklick-Menü für Desktop-UIs

// Prompt: "Rechtsklick-Menü für File-Explorer mit Radix ContextMenu" import * as ContextMenu from '@radix-ui/react-context-menu' function FileItem({ file }: { file: File }) { return ( <ContextMenu.Root> <ContextMenu.Trigger asChild> <div className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 cursor-pointer" // Long-Press auf Touch-Geräten = Context-Menü > <FileIcon /> {file.name} </div> </ContextMenu.Trigger> <ContextMenu.Portal> <ContextMenu.Content className="min-w-48 bg-white rounded-xl shadow-xl p-1 border border-gray-200 animate-in fade-in-0 zoom-in-95" > <ContextMenu.Item className={menuItemClass}> <Eye className="h-4 w-4" /> Vorschau </ContextMenu.Item> <ContextMenu.Item className={menuItemClass}> <Download className="h-4 w-4" /> Herunterladen </ContextMenu.Item> <ContextMenu.Item className={menuItemClass}> <Share className="h-4 w-4" /> Teilen... </ContextMenu.Item> <ContextMenu.Separator className="h-px bg-gray-200 my-1" /> <ContextMenu.Item className={cn(menuItemClass, "text-red-600 focus:bg-red-50 focus:text-red-700")} > <Trash className="h-4 w-4" /> Löschen </ContextMenu.Item> </ContextMenu.Content> </ContextMenu.Portal> </ContextMenu.Root> ) } // API ist identisch mit DropdownMenu! // → Selbe Item-Typen: Item, CheckboxItem, RadioItem, Sub // → Trigger unterschied: DropdownMenu = Klick, ContextMenu = Rechtsklick // → Touch: Long-Press löst ContextMenu.Trigger aus

Tabs und Accordion: Animiert mit Tailwind

Tabs und Accordion — zwei der meistgenutzten UI-Patterns. Radix implementiert beide nach dem ARIA Tabs Pattern und ARIA Accordion Pattern, inklusive korrekter Keyboard-Navigation und dynamischem Content-Aufbau.

AnimateTabs mit CSS Animationen und data-state

// Prompt: "Radix Tabs mit animiertem Tab-Indicator und Content-Transition" import * as Tabs from '@radix-ui/react-tabs' const tabs = [ { value: "overview", label: "Übersicht" }, { value: "analytics", label: "Analytik" }, { value: "settings", label: "Einstellungen" }, ] function DashboardTabs() { return ( <Tabs.Root defaultValue="overview" className="w-full"> <Tabs.List className="flex gap-1 border-b border-gray-200 mb-6" aria-label="Dashboard Abschnitte" > {tabs.map(tab => ( <Tabs.Trigger key={tab.value} value={tab.value} className="px-4 py-2 text-sm font-medium rounded-t-lg -mb-px text-gray-500 hover:text-gray-700 data-[state=active]:text-violet-700 data-[state=active]:border-b-2 data-[state=active]:border-violet-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 transition-colors duration-150" > {tab.label} </Tabs.Trigger> ))} </Tabs.List> // Tab-Content mit Fade-In-Animation <Tabs.Content value="overview" className="data-[state=active]:animate-in data-[state=active]:fade-in-0 data-[state=active]:slide-in-from-bottom-2 duration-200 outline-none" > <OverviewPanel /> </Tabs.Content> <Tabs.Content value="analytics" className="..."> <AnalyticsPanel /> </Tabs.Content> <Tabs.Content value="settings" className="..."> <SettingsPanel /> </Tabs.Content> </Tabs.Root> ) } // Keyboard-Navigation automatisch: // → ArrowLeft/Right: zwischen Tabs navigieren // → Home: erster Tab // → End: letzter Tab // → Automatic activation OR Manual (activationMode="manual")
activationMode="manual": Standardmäßig aktiviert der Fokus den Tab sofort (automatic). Mit activationMode="manual" navigierst du per Pfeiltaste und aktivierst mit Enter/Space. Für Tabs mit lazy-geladenem Content empfohlen.

AccordionAnimated Accordion mit CSS Height-Transition

// Prompt: "FAQ-Accordion mit smooth height animation via Tailwind" import * as Accordion from '@radix-ui/react-accordion' const faqItems = [ { value: "item-1", question: "Was kostet Claude Code im Vergleich zu Copilot?", answer: "Claude Code berechnet per Token auf API-Basis..." }, // weitere Items... ] function FAQAccordion() { return ( <Accordion.Root type="single" // "single" = nur eines offen | "multiple" = mehrere collapsible // Erlaubt Schließen des aktuell offenen Items className="w-full divide-y divide-gray-200 border border-gray-200 rounded-xl overflow-hidden" > {faqItems.map(item => ( <Accordion.Item key={item.value} value={item.value}> <Accordion.Header> <Accordion.Trigger className="flex w-full items-center justify-between p-4 text-left font-medium text-gray-900 hover:bg-gray-50 data-[state=open]:bg-violet-50 data-[state=open]:text-violet-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-violet-500 transition-colors group" > {item.question} <ChevronDown className="h-4 w-4 text-gray-400 transition-transform duration-200 group-data-[state=open]:rotate-180" /> </Accordion.Trigger> </Accordion.Header> <Accordion.Content className="overflow-hidden text-sm text-gray-600 data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up" > <div className="p-4 pt-0">{item.answer}</div> </Accordion.Content> </Accordion.Item> ))} </Accordion.Root> ) } // tailwind.config.js — Accordion Height Animation: module.exports = { theme: { extend: { keyframes: { "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" } }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" } }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }}} }
CSS Custom Properties: Radix setzt --radix-accordion-content-height und --radix-accordion-content-width dynamisch. Du kannst diese in Tailwind-Animationen nutzen um smooth height transitions ohne JavaScript zu bauen.

Barrierefreie UIs mit KI-Unterstützung bauen

Claude Code kennt alle Radix Primitives und generiert vollständig barrierefreie Komponenten nach WAI-ARIA Standard — inklusive Keyboard-Navigation, Focus Management und Screen Reader Support.

14 Tage kostenlos testen →

Toast / Sonner: Benachrichtigungen mit Swipe-to-Dismiss

Toasts sind temporäre Benachrichtigungen die im Hintergrund erscheinen ohne den Nutzer zu unterbrechen. Radix Toast implementiert das korrekte ARIA Live Region Pattern — Screen Reader werden korrekt informiert ohne den Fokus zu unterbrechen.

ARIA LiveRadix Toast: Vollständige Implementation

// Prompt: "Toast-System mit Provider, Hook und animiertem Viewport" import * as Toast from '@radix-ui/react-toast' import { useState, useCallback } from 'react' // 1. Provider + Viewport im Root-Layout function App() { return ( <Toast.Provider swipeDirection="right" duration={4000}> <MyApp /> // Viewport: wo Toasts erscheinen (aria-live="polite" automatisch) <Toast.Viewport className="fixed bottom-4 right-4 flex flex-col gap-2 z-[100] max-w-[420px] w-full m-0 list-none outline-none" /> </Toast.Provider> ) } // 2. Custom Hook für imperatives API function useToast() { const [toasts, setToasts] = useState<ToastItem[]>([]) const toast = useCallback(({ title, description, variant = "default" }) => { setToasts(prev => [...prev, { id: Date.now(), title, description, variant, open: true }]) }, []) return { toast, toasts, setToasts } } // 3. Toast-Komponente mit Swipe-Animation function ToastItem({ title, description, variant, open, onOpenChange }) { return ( <Toast.Root open={open} onOpenChange={onOpenChange} className={cn( "bg-white rounded-xl shadow-lg border p-4 flex items-start gap-3", "data-[state=open]:animate-in data-[state=open]:fade-in-0", "data-[state=open]:slide-in-from-bottom-4", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0", "data-[state=closed]:slide-out-to-right-full", "data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)]", "data-[swipe=end]:animate-out data-[swipe=end]:slide-out-to-right-full", variant === "success" && "border-green-200 bg-green-50", variant === "error" && "border-red-200 bg-red-50", )} > <div className="flex-1 min-w-0"> <Toast.Title className="font-semibold text-sm text-gray-900"> {title} </Toast.Title> {description && ( <Toast.Description className="text-xs text-gray-500 mt-0.5"> {description} </Toast.Description> )} </div> <Toast.Close className="text-gray-400 hover:text-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-violet-500" aria-label="Toast schließen" > <X className="h-4 w-4" /> </Toast.Close> </Toast.Root> ) } // 4. Nutzung im Code: const { toast } = useToast() async function handleSave() { try { await saveData() toast({ title: "Gespeichert!", variant: "success", description: "Änderungen wurden übernommen." }) } catch { toast({ title: "Fehler", variant: "error", description: "Speichern fehlgeschlagen." }) } }
Sonner als Alternative: sonner ist eine kompakte Toast-Bibliothek (Emil Kowalski) die intern Radix-Patterns nutzt. Für einfachere Use-Cases reicht import { toast } from 'sonner'. Für volle Kontrolle und ARIA-Compliance ist Radix Toast empfohlen.

Styling-Patterns: Tailwind CSS + Radix Data Attributes

Das Herzstück des Radix-Stylings sind data-state-Attribute. Jeder Primitive setzt diese automatisch — du reagierst mit Tailwind-Klassen oder CSS-Selektoren darauf.

data-stateAlle data-* Attribute im Überblick

// Radix setzt data-Attribute automatisch — du stylst darauf: // data-state (fast alle Primitives) "data-[state=open]:opacity-100" // Sichtbar wenn geöffnet "data-[state=closed]:opacity-0" // Unsichtbar wenn geschlossen "data-[state=active]:border-violet-600" // Aktiver Tab/Accordion "data-[state=checked]:bg-violet-600" // Checkbox/Switch checked "data-[state=indeterminate]:bg-gray-400" // Checkbox indeterminate "data-[state=on]:bg-violet-100" // Toggle gedrückt // data-side (Popover, Tooltip, DropdownMenu) "data-[side=top]:slide-in-from-bottom-2" // Erscheint oben "data-[side=bottom]:slide-in-from-top-2" // Erscheint unten "data-[side=left]:slide-in-from-right-2" // Erscheint links "data-[side=right]:slide-in-from-left-2" // Erscheint rechts // data-disabled, data-highlighted (Menu Items) "data-[disabled]:opacity-50" "data-[disabled]:cursor-not-allowed" "data-[highlighted]:bg-violet-50" // data-swipe (Toast) "data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)]" "data-[swipe=end]:slide-out-to-right-full" "data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-transform" // CSS Custom Properties (Radix setzt diese per JS): "--radix-accordion-content-height" // → für height-Animationen "--radix-accordion-content-width" // → für width-Animationen "--radix-tooltip-content-transform-origin" // → für scale-Animationen "--radix-toast-swipe-move-x" // → live Swipe-Position

CVAclass-variance-authority für Varianten-Management

// Prompt: "Button-Komponente mit CVA und Radix Slot" import { cva, type VariantProps } from 'class-variance-authority' import { Slot } from '@radix-ui/react-slot' const buttonVariants = cva( // Base classes (immer aktiv): "inline-flex items-center justify-center rounded-lg font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 disabled:opacity-50 disabled:cursor-not-allowed select-none", { variants: { variant: { default: "bg-violet-600 text-white hover:bg-violet-700 shadow-sm", outline: "border border-gray-300 bg-white hover:bg-gray-50 text-gray-700", ghost: "hover:bg-gray-100 text-gray-700", destructive: "bg-red-600 text-white hover:bg-red-700", }, size: { sm: "h-8 px-3 text-xs", md: "h-10 px-4 text-sm", lg: "h-12 px-6 text-base", icon: "h-10 w-10 p-0", }, }, defaultVariants: { variant: "default", size: "md" }, } ) interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean // Radix Slot Pattern } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return <Comp ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} /> } ) // Nutzung: <Button>Standard</Button> <Button variant="outline" size="sm">Outline Small</Button> <Button variant="destructive">Löschen</Button> <Button asChild><Link href="/signup">Registrieren</Link></Button>
Radix Slot: <Slot> aus @radix-ui/react-slot ist das gleiche wie asChild — es mergt alle Props auf das Kind-Element. Damit kannst du Button-Styles auf einen <Link> oder <a> anwenden ohne die Semantik zu verlieren.

FocusGlobale Focus-Styles für barrierefreie UIs

// Prompt: "Globale Accessibility-Styles für Radix-Komponenten" /* globals.css — Konsistente Focus-Sichtbarkeit */ *:focus-visible { outline: 2px solid rgb(124 58 237); /* violet-700 */ outline-offset: 2px; } *:focus:not(:focus-visible) { outline: none; /* Kein Focus-Ring bei Maus-Klick */ } /* Radix Portale immer über allem */ [data-radix-popper-content-wrapper] { z-index: 50 !important; } /* Prevent body scroll wenn Dialog offen */ body[data-scroll-locked] { overflow: hidden; padding-right: 15px; /* Scrollbar-Kompensation */ } /* Skip-to-Content Link (WCAG 2.1 Pflicht) */ .skip-link { position: absolute; top: -100%; left: 50%; transform: translateX(-50%); background: #7c3aed; color: white; padding: 8px 16px; border-radius: 0 0 8px 8px; z-index: 9999; font-weight: 600; } .skip-link:focus { top: 0; /* Sichtbar bei Tastatur-Navigation */ } // tailwind.config.js — tailwindcss-animate Plugin: const { default: flattenColorPalette } = require("tailwindcss/lib/util/flattenColorPalette") module.exports = { plugins: [require("tailwindcss-animate")], // Stellt animate-in, animate-out, fade-in-0, zoom-in-95, etc. bereit }
WCAG 2.1 Pflicht-Check: Auch mit Radix musst du sicherstellen: (1) Farbkontrast min. 4.5:1, (2) Skip-to-Content Link vorhanden, (3) Bilder haben alt-Text, (4) Formulare haben Labels. Radix löst die Interaktions-Accessibility — visuelles Design liegt bei dir.

PopoverPopover und Tooltip: Positioning Engine

// Prompt: "Popover mit Arrow und automatischer Kollisions-Erkennung" import * as Popover from '@radix-ui/react-popover' // Radix nutzt Floating UI intern für Positioning <Popover.Root> <Popover.Trigger asChild> <Button variant="outline">Weitere Infos</Button> </Popover.Trigger> <Popover.Portal> <Popover.Content side="bottom" // Bevorzugte Seite (kollision = auto-flip) sideOffset={8} // Abstand zum Trigger align="start" // start | center | end avoidCollisions // Viewport-Grenzen respektieren (Standard: true) className="w-80 rounded-xl bg-white shadow-xl border border-gray-200 p-4 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2" > <Popover.Arrow className="fill-white drop-shadow-sm" /> <div className="text-sm text-gray-600"> <h4 className="font-semibold text-gray-900 mb-1">Kontextinfo</h4> <p>Radix Floating UI passt die Position automatisch an den Viewport an.</p> </div> <Popover.Close className="absolute top-2 right-2 rounded-sm opacity-70 hover:opacity-100"> <X className="h-4 w-4" /> </Popover.Close> </Popover.Content> </Popover.Portal> </Popover.Root> // Tooltip (einfacher, kein Close-Button nötig): import * as Tooltip from '@radix-ui/react-tooltip' <Tooltip.Provider delayDuration={400}> <Tooltip.Root> <Tooltip.Trigger asChild><Button variant="ghost" size="icon"><Info /></Button></Tooltip.Trigger> <Tooltip.Portal> <Tooltip.Content className="bg-gray-900 text-white text-xs rounded-lg px-3 py-1.5 animate-in fade-in-0 zoom-in-95" sideOffset={4} > Hilfetext für dieses Feature <Tooltip.Arrow className="fill-gray-900" /> </Tooltip.Content> </Tooltip.Portal> </Tooltip.Root> </Tooltip.Provider>
Claude Code Prompt-Tipp: "Erstelle eine vollständig barrierefreie [Komponente] mit Radix UI, Tailwind CSS, WAI-ARIA Labels, Keyboard Navigation und Focus Management. Nutze CVA für Varianten und asChild für das Slot-Pattern."

Barrierefreie React-Apps schneller entwickeln

Claude Code kennt alle Radix Primitives, WAI-ARIA Patterns und Tailwind-Animationen. Generiere vollständig barrierefreie UI-Komponenten in Sekunden — keine Accessibility-Kenntnisse erforderlich.

Jetzt kostenlos starten →