radix-ui

SlanyCukr's avatarfrom SlanyCukr

Build accessible, unstyled React UI components with Radix Primitives

⭐0stars🔀0forks📁View on GitHub🕐Updated Nov 29, 2025

When & Why to Use This Skill

This Claude skill empowers developers to build high-quality, accessible React user interfaces using Radix UI Primitives. It focuses on providing low-level, unstyled components that handle the complexities of accessibility, keyboard navigation, and focus management. By leveraging this skill, users can create custom design systems and sophisticated UI patterns that are fully WCAG-compliant while maintaining complete control over the visual styling.

Use Cases

  • Design System Development: Building a library of reusable, accessible UI components like Modals, Popovers, and Tabs tailored to specific brand requirements.
  • Accessibility Compliance: Implementing complex web widgets that require strict adherence to WAI-ARIA design patterns and keyboard interaction standards.
  • Headless UI Integration: Creating interactive elements where the logic and behavior are handled by Radix, while the styling is managed via Tailwind CSS, CSS Modules, or Styled Components.
  • Rapid Prototyping: Quickly generating functional and interactive React component structures for complex features like nested dropdown menus or multi-step dialogs.
nameradix-ui
descriptionBuild accessible, unstyled React UI components with Radix Primitives
when_to_useWhen you need accessible, composable UI components that give you full control over styling while ensuring WCAG compliance

Radix UI Primitives Skill

Radix UI Primitives provides low-level, unstyled React components with built-in accessibility, keyboard navigation, and focus management. Perfect for building design systems and custom UI components.

Quick Start

Basic Dialog Example

import * as Dialog from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";

function BasicDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger asChild>
        <button className="btn-primary">Edit profile</button>
      </Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay className="dialog-overlay" />
        <Dialog.Content className="dialog-content">
          <Dialog.Title className="dialog-title">Edit profile</Dialog.Title>
          <Dialog.Description className="dialog-description">
            Make changes to your profile here.
          </Dialog.Description>

          <div className="dialog-fields">
            <input placeholder="Name" defaultValue="John Doe" />
            <input placeholder="Username" defaultValue="@johndoe" />
          </div>

          <div className="dialog-actions">
            <Dialog.Close asChild>
              <button className="btn-primary">Save changes</button>
            </Dialog.Close>
          </div>

          <Dialog.Close asChild>
            <button className="dialog-close" aria-label="Close">
              <Cross2Icon />
            </button>
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { HamburgerMenuIcon, CheckIcon } from "@radix-ui/react-icons";

function UserMenu() {
  const [showBookmarks, setShowBookmarks] = React.useState(true);

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <button className="icon-btn" aria-label="Menu">
          <HamburgerMenuIcon />
        </button>
      </DropdownMenu.Trigger>

      <DropdownMenu.Portal>
        <DropdownMenu.Content className="dropdown-content" sideOffset={5}>
          <DropdownMenu.Item className="dropdown-item">
            New Tab <div className="shortcut">⌘+T</div>
          </DropdownMenu.Item>
          <DropdownMenu.Item className="dropdown-item">
            New Window <div className="shortcut">⌘+N</div>
          </DropdownMenu.Item>

          <DropdownMenu.Separator className="dropdown-separator" />

          <DropdownMenu.CheckboxItem
            className="dropdown-item"
            checked={showBookmarks}
            onCheckedChange={setShowBookmarks}
          >
            <DropdownMenu.ItemIndicator className="item-indicator">
              <CheckIcon />
            </DropdownMenu.ItemIndicator>
            Show Bookmarks
          </DropdownMenu.CheckboxItem>

          <DropdownMenu.Arrow className="dropdown-arrow" />
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  );
}

Common Patterns

Primitive Components

Radix provides 30+ primitive components for common UI patterns:

// Accordion with animation
import * as Accordion from "@radix-ui/react-accordion";

function FAQAccordion() {
  return (
    <Accordion.Root type="single" collapsible className="accordion">
      <Accordion.Item value="item-1" className="accordion-item">
        <Accordion.Header>
          <Accordion.Trigger className="accordion-trigger">
            Is it accessible?
            <ChevronDownIcon className="accordion-chevron" />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content className="accordion-content">
          Yes. It adheres to WAI-ARIA design patterns.
        </Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
}

// Tooltip with delay
import * as Tooltip from "@radix-ui/react-tooltip";

function TooltipExample() {
  return (
    <Tooltip.Provider delayDuration={800}>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <button className="icon-btn">
            <InfoIcon />
          </button>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content className="tooltip" sideOffset={5}>
            Additional information
            <Tooltip.Arrow className="tooltip-arrow" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
}

Accessibility Features

Radix automatically handles accessibility:

// All components include proper ARIA attributes
// Focus management is handled automatically
// Keyboard navigation works out of the box
// Screen reader support is built-in

// Example: Select with proper labeling
import * as Select from "@radix-ui/react-select";

function AccessibleSelect() {
  return (
    <Select.Root>
      <Select.Trigger aria-label="Select a fruit">
        <Select.Value placeholder="Choose a fruit…" />
        <Select.Icon>
          <ChevronDownIcon />
        </Select.Icon>
      </Select.Trigger>

      <Select.Portal>
        <Select.Content>
          <Select.Viewport>
            <Select.Group>
              <Select.Label>Fruits</Select.Label>
              <Select.Item value="apple">
                <Select.ItemText>Apple</Select.ItemText>
                <Select.ItemIndicator>
                  <CheckIcon />
                </Select.ItemIndicator>
              </Select.Item>
            </Select.Group>
          </Select.Viewport>
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  );
}

Composition with asChild

Compose Radix primitives with your own components:

// Use your existing button styles
import { Dialog, Tooltip } from "@radix-ui/react-dialog";
import { Button } from "./your-design-system";

function ComposedDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger asChild>
        <Button variant="primary">Open Dialog</Button>
      </Dialog.Trigger>

      <Dialog.Portal>
        <Dialog.Overlay />
        <Dialog.Content>
          <Dialog.Title>Dialog Title</Dialog.Title>
          <Dialog.Close asChild>
            <Button variant="secondary">Close</Button>
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

// Multiple primitives on one element
function TooltipDialogButton() {
  return (
    <Dialog.Root>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <Dialog.Trigger asChild>
            <Button variant="primary">Open Dialog</Button>
          </Dialog.Trigger>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content>Opens a modal dialog</Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>

      {/* Dialog content */}
    </Dialog.Root>
  );
}

Custom Component Abstractions

Create your own simplified APIs:

// Custom Dialog wrapper
import * as DialogPrimitive from "@radix-ui/react-dialog";

export const CustomDialog = ({ children, trigger, title }) => {
  return (
    <DialogPrimitive.Root>
      <DialogPrimitive.Trigger asChild>{trigger}</DialogPrimitive.Trigger>
      <DialogPrimitive.Portal>
        <DialogPrimitive.Overlay className="overlay" />
        <DialogPrimitive.Content className="content">
          <DialogPrimitive.Title>{title}</DialogPrimitive.Title>
          {children}
          <DialogPrimitive.Close asChild>
            <button className="close-btn">×</button>
          </DialogPrimitive.Close>
        </DialogPrimitive.Content>
      </DialogPrimitive.Portal>
    </DialogPrimitive.Root>
  );
};

// Usage
<CustomDialog title="Settings" trigger={<Button>Open Settings</Button>}>
  <div>Settings content here</div>
</CustomDialog>;

Styling with Data Attributes

Style components based on their state:

/* Accordion animations */
.accordion-content[data-state="open"] {
  animation: slideDown 300ms ease-out;
}

.accordion-content[data-state="closed"] {
  animation: slideUp 300ms ease-out;
}

/* Dropdown positioning */
.dropdown-content {
  transform-origin: var(--radix-dropdown-menu-content-transform-origin);
}

.dropdown-content[data-side="top"] {
  animation: slideUp 0.3s ease-out;
}

.dropdown-content[data-side="bottom"] {
  animation: slideDown 0.3s ease-out;
}

/* Focus states */
.dropdown-item[data-highlighted] {
  background: #f0f0f0;
}

.dropdown-item[data-state="checked"] {
  background: #e0e0e0;
}

Advanced Patterns

// Controlled components for async operations
function AsyncDialog() {
  const [open, setOpen] = useState(false);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);

    await submitForm();
    setLoading(false);
    setOpen(false);
  };

  return (
    <Dialog.Root open={open} onOpenChange={setOpen}>
      <Dialog.Trigger asChild>
        <Button>Open Form</Button>
      </Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Content>
          <form onSubmit={handleSubmit}>
            <input name="email" type="email" required />
            <Button type="submit" disabled={loading}>
              {loading ? "Submitting…" : "Submit"}
            </Button>
          </form>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

// Popover with collision detection
function SmartPopover() {
  return (
    <Popover.Root>
      <Popover.Trigger asChild>
        <Button>Click me</Button>
      </Popover.Trigger>
      <Popover.Portal>
        <Popover.Content
          className="popover"
          sideOffset={10}
          collisionPadding={20}
        >
          Content that avoids screen edges
          <Popover.Arrow className="popover-arrow" />
        </Popover.Content>
      </Popover.Portal>
    </Popover.Root>
  );
}