Skip to main content
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"