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
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
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
Run TypeScript checks
TypeScript will catch many breaking changes: Test your components
Run your test suite and manually test components:
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:
Read release notes
Check the release notes and changelog:
- Breaking changes
- Deprecated features
- New features
- Bug fixes
Update peer dependencies
Ensure React version compatibility:{
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
Search for deprecated usage
# Search your codebase for deprecated patterns
grep -r "deprecated-prop" src/
Update styles
Check if CSS selectors or data attributes have changed:/* Update data-* attribute selectors if needed */
[data-state="open"] { /* ... */ }
[data-radix-dialog-content] { /* ... */ }
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:
Ask the community
Join discussions on GitHub Discussions or Discord
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"