[BUG] - Toast: `useMediaQuery` for action button position causes SSR hydration mismatch
HeroUI Version
3.0.4
Describe the bug
In packages/react/src/components/toast/toast.tsx, the ToastProvider uses useMediaQuery to conditionally render the action button in different positions based on viewport width:
const isMobile = useMediaQuery("(max-width: 768px)");
// ...
// Inside getDefaultChildren:
{isMobile && actionProps?.children ? (
<ToastActionButton ... /> // renders INSIDE ToastContent on mobile
) : null}
// ...
{!isMobile && actionProps?.children ? (
<ToastActionButton ... /> // renders OUTSIDE ToastContent on desktop
) : null}During SSR (or any server-rendered environment like Next.js App Router), useMediaQuery returns false because window.matchMedia is not available. On a mobile client, the first client render returns true. This creates a hydration mismatch — the server HTML has the action button outside ToastContent, but the client expects it inside.
Root cause
useMediaQuery is not SSR-safe when used to conditionally change the structure of the React tree (different DOM positions). It's safe for styling/classes but not for conditional rendering that changes element hierarchy.
Suggested fix
Option A: Always render both positions, use CSS to show/hide:
<ToastContent>
{!!title && <ToastTitle>{title}</ToastTitle>}
{!!description && <ToastDescription>{description}</ToastDescription>}
{actionProps?.children ? (
<ToastActionButton className="sm:hidden" {...actionProps}>{actionProps.children}</ToastActionButton>
) : null}
</ToastContent>
{actionProps?.children ? (
<ToastActionButton className="hidden sm:flex" {...actionProps}>{actionProps.children}</ToastActionButton>
) : null}Option B: Use a useEffect-based approach that defaults to the server-rendered layout and only switches on the client after hydration:
const [isMobile, setIsMobile] = useState(false); // always false on server
useEffect(() => {
setIsMobile(window.matchMedia("(max-width: 768px)").matches);
}, []);Option A is preferred as it avoids layout shift.
Impact
- SSR: React hydration mismatch warning in Next.js / Remix / any SSR framework
- Visual: Potential layout flash on mobile as the action button moves positions after hydration
- Severity: Affects all SSR apps using
Toast.Providerwith action buttons on mobile viewports
Your Example Website or App
No response
Steps to Reproduce the Bug or Issue
- Use Next.js App Router with
<Toast.Provider />in the root layout - Trigger a toast with
actionProps(e.g., an "Undo" button) - View the page on a mobile viewport (< 768px)
- Check the browser console for hydration mismatch warnings
- Observe the action button briefly appearing in the wrong position before settling
Expected behavior
Toast layout should be consistent between server and client renders, with no hydration mismatch.
Operating System Version
macOS
Browser
Chrome (mobile viewport emulation)
Source: heroui-inc/heroui