Originally created by: matt765
React 19 made ref a regular prop, which means React.forwardRef is technically no longer necessary. The shadcn/ui project has an open proposal to remove it ([shadcn-ui/ui#7268](https://github.com/shadcn-ui/ui/issues/7268)) with a corresponding PR ([shadcn-ui/ui#7278](https://github.com/shadcn-ui/ui/issues/7278)), but neither has been merged yet.
All 23 Shadcn components in src/components/common/shadcn/ currently use the forwardRef pattern. Since we're on React 19, we can start migrating — but it's worth being aware that this is still an evolving area in the ecosystem.
There's a known issue ([shadcn-ui/ui#7926](https://github.com/shadcn-ui/ui/issues/7926)) where removing forwardRef can break Radix UI's asChild pattern — specifically, components like PopoverTrigger wrapping a Button via asChild may fail to mount or position correctly without forwardRef. So each component needs to be tested carefully, especially ones that are commonly used with asChild composition.
.displayName assignments just to make dev tools happyReact.ElementRef with React.ComponentRef ([shadcn-ui/ui#7920](https://github.com/shadcn-ui/ui/issues/7920))Before:
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, ...props }, ref) => {
return <button ref={ref} className={className} {...props} />;
},
);
Button.displayName = "Button";
After:
const Button = ({ className, variant, ref, ...props }: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) => {
return <button ref={ref} className={className} {...props} />;
};
The idea is straightforward — pull ref out of the second argument and into the props object, then drop the forwardRef wrapper and displayName.
All of these live in src/components/common/shadcn/:
alert.tsx, avatar.tsx, breadcrumb.tsx, button.tsx, checkbox.tsx, command.tsx, dialog.tsx, dropdown-menu.tsx, form.tsx, input.tsx, input-group.tsx, label.tsx, popover.tsx, progress.tsx, radio-group.tsx, select.tsx, separator.tsx, slider.tsx, switch.tsx, tabs.tsx, textarea.tsx, tooltip.tsx
Also worth checking: SearchInput.tsx in src/components/layout/navbar/parts/
React.forwardRef calls remain in the codebase.displayName assignmentsReact.ElementRef replaced with React.ComponentRef where applicableref is accepted as a regular prop in all migrated componentsasChild pattern (Button, Dialog, Popover, etc.) are tested and work correctly