In React 19, the forwardRef
function was deprecated.
This post will show you how to update your Shadcn UI components, so that they work with React 19, but also to simplify the code to pass references to components.
How did Shadcn UI components work before?
Shadcn UI is one of the most popular UI libraries for React. Based on the incredible work of Radix UI, Shadcn UI introduced a set of "open" components styled with Tailwind CSS that took the React ecosystem by storm.
Shadcn UI uses the forwardRef
function to pass references to components. This is a common pattern in React, and it's how we can pass a reference to a component to a child component.
As of React 19, the forwardRef
function is deprecated. Instead, ref
is now a prop that can be passed to a component.
function SuperButton({id, ref}) { return <button id={id} ref={ref} />}//...<SuperButton ref={ref} />
The new approach simplifies component composition by removing the need for forwardRef
. Components can now directly accept and pass refs as props, making the code more straightforward and maintainable.
How to update Shadcn UI components to work with React 19
To update your Shadcn UI components to work with React 19, you need to update the forwardRef
function to ref
and pass the ref as a prop to the component.
Here's an example of how to update the Input
component. Before, it used forwardRef
to pass a reference to the component to the Label
component:
import * as React from 'react';import { cn } from '../lib/utils';export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;const Input = React.forwardRef<HTMLInputElement, InputProps>( ({ className, type = 'text', ...props }, ref) => { return ( <input type={type} className={cn( 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', className, )} ref={ref} {...props} /> ); },);Input.displayName = 'Input';export { Input };
After updating the Input
component to use ref
instead of forwardRef
, you can pass the ref as a prop to the Label
component:
import * as React from 'react';import { cn } from '../lib/utils';export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;const Input: React.FC<InputProps> = ({ className, type = 'text', ...props}) => { return ( <input type={type} className={cn( 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', className, )} {...props} /> );};Input.displayName = 'Input';export { Input };
In the above code, we've updated the Input
component to use ref
instead of forwardRef
. We've also updated the Input
component to accept a type
prop and pass it to the input element.
Because ref
is already a prop, we don't need to specify it because we spread the props
object, and ref
is already a property of props
.
Updating a more complex component in Shadcn UI
Let's take a look at an example of how to update a more complex component in Shadcn UI.
The Popover
component is an example of a component that uses forwardRef
to pass a reference to the component to a child component and extends the PopoverPrimitive.Root
component from Radix UI.
'use client';import * as React from 'react';import * as PopoverPrimitive from '@radix-ui/react-popover';import { cn } from '../lib/utils';const Popover = PopoverPrimitive.Root;const PopoverTrigger = PopoverPrimitive.Trigger;const PopoverAnchor = PopoverPrimitive.Anchor;const PopoverContent = React.forwardRef< React.ElementRef<typeof PopoverPrimitive.Content>, React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (const PopoverContent: React.FC< React.ComponentProps<typeof PopoverPrimitive.Content>> = ({ className, align = 'center', sideOffset = 4, ...props }) => ( <PopoverPrimitive.Portal> <PopoverPrimitive.Content ref={ref} align={align} sideOffset={sideOffset} className={cn( 'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className, )} {...props} /> </PopoverPrimitive.Portal>)););PopoverContent.displayName = PopoverPrimitive.Content.displayName;export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
Insead of using forwardRef
, we can use ref
directly in the PopoverContent
component:
'use client';import * as React from 'react';import * as PopoverPrimitive from '@radix-ui/react-popover';import { cn } from '../lib/utils';const Popover = PopoverPrimitive.Root;const PopoverTrigger = PopoverPrimitive.Trigger;const PopoverAnchor = PopoverPrimitive.Anchor;const PopoverContent: React.FC< React.ComponentProps<typeof PopoverPrimitive.Content>> = ({ className, align = 'center', sideOffset = 4, ...props }) => ( <PopoverPrimitive.Portal> <PopoverPrimitive.Content align={align} sideOffset={sideOffset} className={cn( 'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className, )} {...props} /> </PopoverPrimitive.Portal>);PopoverContent.displayName = PopoverPrimitive.Content.displayName;export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
As you can see from the above snippet, we have updated the component to use ref
instead of forwardRef
. We have also updated the PopoverContent
component to accept a ref
as a prop and pass it to the PopoverPrimitive.Content
component.
To extend a Radix UI Primitive, use the pattern below:
type Props = React.ComponentProps<typeof RadixPrimitive>;const NewComponent = React.FC<Props> = // Your component code...
Where NewComponent
is the name of your component and RadixPrimitive
is the name of the Radix UI Primitive you want to extend.
Conclusion
In this post, we've seen how to update Shadcn UI components to work with React 19, while also writing less code to achieve the same result.
Makerkit, the best Next.js SaaS Starter Kit for building SaaS products, uses Shadcn UI components extensively. We've already updated our components to work with React 19 and ensure compatibility with future versions of React.