Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/radix-ui/primitives/llms.txt

Use this file to discover all available pages before exploring further.

This guide covers strategies for upgrading Radix UI Primitives and handling breaking changes between versions.

Version Strategy

Radix UI follows semantic versioning:
  • Major versions (1.x → 2.x): Breaking changes that may require code updates
  • Minor versions (1.1 → 1.2): New features, backward compatible
  • Patch versions (1.1.1 → 1.1.2): Bug fixes, backward compatible
Radix Primitives is currently in v1.x and maintains a strong commitment to stability. Breaking changes are rare and well-documented.

General Upgrade Process

1

Check the changelog

Review the changelog for the component you’re upgrading:
# View changelogs in the repository
https://github.com/radix-ui/primitives/blob/main/packages/react/[component]/CHANGELOG.md
2

Update dependencies

Update to the latest version:
npm install @radix-ui/react-dialog@latest
# or update multiple packages
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-popover
3

Run TypeScript checks

TypeScript will catch many breaking changes:
npm run typecheck
4

Test your components

Run your test suite and manually test components:
npm test
npm run dev

Common Migration Patterns

Updating Multiple Packages

When upgrading, it’s often best to update all Radix packages together since they share internal dependencies:
# Update all @radix-ui packages to latest
npm update @radix-ui/*

# Or use a tool like npm-check-updates
npx npm-check-updates "/@radix-ui/" -u
npm install

Handling Breaking Changes

API Prop Changes

If a prop is renamed or removed:
// Before (v1.0)
<Dialog.Root open={open} onOpenChange={setOpen}>
  {/* content */}
</Dialog.Root>

// After (hypothetical v2.0 with renamed prop)
<Dialog.Root isOpen={open} onIsOpenChange={setOpen}>
  {/* content */}
</Dialog.Root>
Create a wrapper to ease migration:
import * as DialogPrimitive from '@radix-ui/react-dialog';

interface DialogProps {
  open?: boolean; // Old API
  onOpenChange?: (open: boolean) => void; // Old API
  children: React.ReactNode;
}

// Adapter component for gradual migration
function Dialog({ open, onOpenChange, children }: DialogProps) {
  return (
    <DialogPrimitive.Root isOpen={open} onIsOpenChange={onOpenChange}>
      {children}
    </DialogPrimitive.Root>
  );
}

Component Structure Changes

If component structure changes:
// Before: Content includes Overlay
<Dialog.Root>
  <Dialog.Content>
    {/* content */}
  </Dialog.Content>
</Dialog.Root>

// After: Overlay is separate
<Dialog.Root>
  <Dialog.Portal>
    <Dialog.Overlay />
    <Dialog.Content>
      {/* content */}
    </Dialog.Content>
  </Dialog.Portal>
</Dialog.Root>

Type Changes

TypeScript types may change between versions:
import { ComponentPropsWithoutRef, ElementRef } from 'react';
import * as Dialog from '@radix-ui/react-dialog';

// Use utility types to adapt to changes
type DialogContentProps = ComponentPropsWithoutRef<typeof Dialog.Content>;
type DialogContentElement = ElementRef<typeof Dialog.Content>;

// Your wrapper component will automatically adapt
const MyDialogContent = React.forwardRef<
  DialogContentElement,
  DialogContentProps
>((props, ref) => <Dialog.Content ref={ref} {...props} />);

Migration Checklist

When upgrading major versions:
1

Read release notes

Check the release notes and changelog:
  • Breaking changes
  • Deprecated features
  • New features
  • Bug fixes
2

Update peer dependencies

Ensure React version compatibility:
{
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
3

Search for deprecated usage

# Search your codebase for deprecated patterns
grep -r "deprecated-prop" src/
4

Update styles

Check if CSS selectors or data attributes have changed:
/* Update data-* attribute selectors if needed */
[data-state="open"] { /* ... */ }
[data-radix-dialog-content] { /* ... */ }
5

Test accessibility

Ensure accessibility features still work:
  • Keyboard navigation
  • Screen reader announcements
  • Focus management
  • ARIA attributes

Backwards Compatibility

Create compatibility layers for gradual migration:
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react';

// V1 compatible wrapper
export const Dialog = DialogPrimitive.Root;
export const DialogTrigger = DialogPrimitive.Trigger;

// Automatically include Portal and Overlay for v1 behavior
export const DialogContent = forwardRef<
  ElementRef<typeof DialogPrimitive.Content>,
  ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>((props, ref) => (
  <DialogPrimitive.Portal>
    <DialogPrimitive.Overlay className="dialog-overlay" />
    <DialogPrimitive.Content ref={ref} {...props} />
  </DialogPrimitive.Portal>
));

DialogContent.displayName = 'DialogContent';
Use this wrapper during migration:
// Import from your compatibility layer instead of directly from Radix
import { Dialog, DialogTrigger, DialogContent } from '@/components/compat/dialog';

function MyDialog() {
  return (
    <Dialog>
      <DialogTrigger>Open</DialogTrigger>
      <DialogContent>
        {/* Works with v1 code style */}
      </DialogContent>
    </Dialog>
  );
}

Codemods

For large codebases, consider creating codemods to automate migrations:
// Example codemod using jscodeshift
module.exports = function transformer(file, api) {
  const j = api.jscodeshift;
  const root = j(file.source);

  // Find all Dialog.Content and wrap with Portal
  root
    .find(j.JSXElement, {
      openingElement: {
        name: { object: { name: 'Dialog' }, property: { name: 'Content' } },
      },
    })
    .forEach((path) => {
      // Transformation logic here
    });

  return root.toSource();
};

Handling Deprecation Warnings

Radix may mark features as deprecated before removing them:
import * as Dialog from '@radix-ui/react-dialog';

// If you see deprecation warnings in console
function MyDialog() {
  return (
    <Dialog.Root>
      {/* ⚠️ Deprecated prop usage */}
      <Dialog.Content deprecatedProp="value">
        Content
      </Dialog.Content>
    </Dialog.Root>
  );
}

// Fix by using the new API
function MyDialogFixed() {
  return (
    <Dialog.Root>
      <Dialog.Content newProp="value">
        Content
      </Dialog.Content>
    </Dialog.Root>
  );
}
Address deprecation warnings as soon as possible. Deprecated features will be removed in the next major version.

Testing After Migration

Unit Tests

import { render, screen } from '@testing-library/react';
import { Dialog } from './dialog';

test('dialog opens and closes', () => {
  render(
    <Dialog>
      <Dialog.Trigger>Open</Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Title>Title</Dialog.Title>
      </Dialog.Content>
    </Dialog>
  );

  // Test component behavior
  const trigger = screen.getByText('Open');
  expect(trigger).toBeInTheDocument();
});

Visual Regression Tests

Use tools like Playwright or Chromatic to catch visual changes:
import { test, expect } from '@playwright/test';

test('dialog appears correctly', async ({ page }) => {
  await page.goto('/dialog-example');
  await page.click('button:has-text("Open")');
  
  // Visual snapshot
  await expect(page).toHaveScreenshot('dialog-open.png');
});

Getting Help

If you encounter issues during migration:
1

Check documentation

Review the component documentation at https://radix-ui.com/primitives
2

Search GitHub issues

3

Ask the community

Join discussions on GitHub Discussions or Discord
4

Report bugs

If you find a bug, report it with a minimal reproduction

Version-Specific Guides

Upgrading from v0.x to v1.x

Major changes in v1.0:
  • Improved TypeScript types
  • Better SSR support
  • Refined API surface
  • Enhanced accessibility

Staying Current

To stay informed about updates:
  • Watch the GitHub repository
  • Follow release notes
  • Check the changelog regularly
  • Subscribe to the Radix blog
# Enable GitHub notifications for releases only
# Go to: https://github.com/radix-ui/primitives
# Click "Watch" → "Custom" → "Releases"