-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Bug report
We get a hydration error from various components that generate their id or aria-controls using React (or your own internal implementation) of the useId hook. The common factor seems to be the usage of the Slot component. We're using the Next.js app router, which is using the latest version of React canary behind the scenes, as described in the documentation:
Good to know: The App Router uses React canary releases built-in, which include all the stable React 19 changes, as well as newer features being validated in frameworks.
The issue is happening starting from Next.js 15.5.0 which seems to use a newer version of React canary. It's also present on the latest version of Next.js.
Downgrading to Next.js 15.4.7 seems to resolve the issue (possibly because of the version using an older version of React).
Could be relevant: React has updated the default prefix of useId in React 19.2
Current Behavior
Hydration error, some examples follow:
Example 1
A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
...
<MenuProvider scope={{Menu:[...], ...}} open={false} onOpenChange={function} content={null} ...>
<MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>
<DropdownMenuTrigger asChild={true}>
<DropdownMenuTrigger asChild={true}>
<MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>
<PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>
<Primitive.div asChild={true} ref={function}>
<Primitive.div.Slot ref={function}>
<Primitive.div.SlotClone ref={function}>
<Primitive.button type="button" id="radix-_R_6..." aria-haspopup="menu" aria-expanded={false} ...>
<Primitive.button.Slot type="button" id="radix-_R_6..." aria-haspopup="menu" ...>
<Primitive.button.SlotClone type="button" id="radix-_R_6..." aria-haspopup="menu" ...>
<Button className="h-fit" variant="nav" size="none" aria-label="Open navig..." type="button" ...>
<button
className="cursor-pointer rounded-md ring-offset-background focus-visible:outline-hidd..."
ref={function}
aria-label="Open navigation menu"
type="button"
+ id="radix-_R_66itmdl5rlb_"
- id="radix-_R_166itmdl5rlb_"
aria-haspopup="menu"
aria-expanded={false}
aria-controls={undefined}
data-state="closed"
data-disabled={undefined}
disabled={false}
onPointerDown={function handleEvent}
onKeyDown={function handleEvent}
>
Example 2
A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
...
<Header>
<header className="clamp-[px-...">
...
<div className="flex">
<MatrixLink isLoggedIn={false} t={{title:"Mat...", ...}} className="xs:flex hi...">
<Dialog open={false} onOpenChange={function bound dispatchSetState}>
<Dialog data-slot="dialog" open={false} onOpenChange={function bound dispatchSetState}>
<DialogProvider scope={undefined} triggerRef={{current:null}} contentRef={{current:null}} ...>
<DialogTrigger asChild={true}>
<DialogTrigger data-slot="dialog-tri..." asChild={true}>
<Primitive.button type="button" aria-haspopup="dialog" aria-expanded={false} ...>
<Primitive.button.Slot type="button" aria-haspopup="dialog" aria-expanded={false} ...>
<Primitive.button.SlotClone type="button" aria-haspopup="dialog" aria-expanded={false} ...>
<Button className="xs:flex hi..." variant="ghost" size="icon" title="Matrix Log..." ...>
<button
className="cursor-pointer rounded-md ring-offset-background transition-colors focus-..."
ref={function}
title="Matrix Login Information"
aria-label="Matrix Login Information"
type="button"
aria-haspopup="dialog"
aria-expanded={false}
+ aria-controls="radix-_R_a6itmdl5rlb_"
- aria-controls="radix-_R_q6itmdl5rlb_"
data-state="closed"
data-slot="dialog-trigger"
onClick={function handleEvent}
>
Expected behavior
No hydration error caused by mismatching id, aria-controls or other ARIA attributes generated by randomized id.
Your environment
| Software | Name(s) | Version |
|---|---|---|
| Radix Package(s) | @radix-ui/react-dialog@1.1.15, @radix-ui/react-slot@1.2.3 | |
| React | n/a | 19.2.0 |
| Browser | Firefox 144.0, Chrome 141.0.7390.108 | |
| Assistive tech | ||
| Node | n/a | 22.14.0 |
| bun | 1.3 |