diff --git a/.changeset/mean-roses-dress.md b/.changeset/mean-roses-dress.md
new file mode 100644
index 00000000000..bbd7598c0df
--- /dev/null
+++ b/.changeset/mean-roses-dress.md
@@ -0,0 +1,5 @@
+---
+'@clerk/clerk-js': patch
+---
+
+Fix modal issues by inlining scroll locking mechanism instead of using `` which caused issues in Chromium based browsers
diff --git a/packages/clerk-js/src/ui/elements/Modal.tsx b/packages/clerk-js/src/ui/elements/Modal.tsx
index f0524ab6740..ee4e035aae4 100644
--- a/packages/clerk-js/src/ui/elements/Modal.tsx
+++ b/packages/clerk-js/src/ui/elements/Modal.tsx
@@ -1,9 +1,9 @@
-import { createContextAndHook } from '@clerk/shared/react';
-import { FloatingOverlay } from '@floating-ui/react';
+import { createContextAndHook, useSafeLayoutEffect } from '@clerk/shared/react';
import React, { useRef } from 'react';
import { descriptors, Flex } from '../customizables';
import { usePopover } from '../hooks';
+import { useScrollLock } from '../hooks/useScrollLock';
import type { ThemableCssProp } from '../styledSystem';
import { animations, mqu } from '../styledSystem';
import { withFloatingTree } from './contexts';
@@ -22,6 +22,7 @@ type ModalProps = React.PropsWithChildren<{
}>;
export const Modal = withFloatingTree((props: ModalProps) => {
+ const { disableScrollLock, enableScrollLock } = useScrollLock();
const { handleClose, handleOpen, contentSx, containerSx, canCloseModal, id, style } = props;
const overlayRef = useRef(null);
const { floating, isOpen, context, nodeId, toggle } = usePopover({
@@ -38,62 +39,67 @@ export const Modal = withFloatingTree((props: ModalProps) => {
handleOpen?.();
}
}, [isOpen]);
-
const modalCtx = React.useMemo(() => ({ value: canCloseModal === false ? {} : { toggle } }), [toggle, canCloseModal]);
+ useSafeLayoutEffect(() => {
+ enableScrollLock();
+
+ return () => {
+ disableScrollLock();
+ };
+ }, []);
+
return (
-
-
+
+ ({
+ animation: `${animations.fadeIn} 150ms ${t.transitionTiming.$common}`,
+ zIndex: t.zIndices.$modal,
+ backgroundColor: t.colors.$modalBackdrop,
+ alignItems: 'flex-start',
+ justifyContent: 'center',
+ overflow: 'auto',
+ width: '100vw',
+ height: ['100vh', '-webkit-fill-available'],
+ position: 'fixed',
+ left: 0,
+ top: 0,
+ }),
+ containerSx,
+ ]}
+ >
({
- animation: `${animations.fadeIn} 150ms ${t.transitionTiming.$common}`,
- zIndex: t.zIndices.$modal,
- backgroundColor: t.colors.$modalBackdrop,
- alignItems: 'flex-start',
- justifyContent: 'center',
- overflow: 'auto',
- width: '100vw',
- height: ['100vh', '-webkit-fill-available'],
- position: 'fixed',
- left: 0,
- top: 0,
+ position: 'relative',
+ outline: 0,
+ animation: `${animations.modalSlideAndFade} 180ms ${t.transitionTiming.$easeOut}`,
+ margin: `${t.space.$16} 0`,
+ [mqu.sm]: {
+ margin: `${t.space.$10} 0`,
+ },
}),
- containerSx,
+ contentSx,
]}
>
- ({
- position: 'relative',
- outline: 0,
- animation: `${animations.modalSlideAndFade} 180ms ${t.transitionTiming.$easeOut}`,
- margin: `${t.space.$16} 0`,
- [mqu.sm]: {
- margin: `${t.space.$10} 0`,
- },
- }),
- contentSx,
- ]}
- >
- {props.children}
-
+ {props.children}
-
-
+
+
);
});
diff --git a/packages/clerk-js/src/ui/hooks/useScrollLock.ts b/packages/clerk-js/src/ui/hooks/useScrollLock.ts
new file mode 100644
index 00000000000..3b3549380fc
--- /dev/null
+++ b/packages/clerk-js/src/ui/hooks/useScrollLock.ts
@@ -0,0 +1,85 @@
+// The following code is adapted from Floating UI
+// Source: https://github.com/floating-ui/floating-ui/blob/c09c59d6e594c3527888a52ed0f3e8a2978663c2/packages/react/src/components/FloatingOverlay.tsx
+// Copyright (c) Floating UI contributors
+// SPDX-License-Identifier: MIT
+// Avoid Chrome DevTools blue warning.
+export function getPlatform(): string {
+ const uaData = (navigator as any).userAgentData as { platform: string } | undefined;
+
+ if (uaData?.platform) {
+ return uaData.platform;
+ }
+
+ return navigator.platform;
+}
+
+let lockCount = 0;
+function enableScrollLock() {
+ const isIOS = /iP(hone|ad|od)|iOS/.test(getPlatform());
+ const bodyStyle = document.body.style;
+ // RTL scrollbar
+ const scrollbarX =
+ Math.round(document.documentElement.getBoundingClientRect().left) + document.documentElement.scrollLeft;
+ const paddingProp = scrollbarX ? 'paddingLeft' : 'paddingRight';
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
+ const scrollX = bodyStyle.left ? parseFloat(bodyStyle.left) : window.scrollX;
+ const scrollY = bodyStyle.top ? parseFloat(bodyStyle.top) : window.scrollY;
+
+ bodyStyle.overflow = 'hidden';
+
+ if (scrollbarWidth) {
+ bodyStyle[paddingProp] = `${scrollbarWidth}px`;
+ }
+
+ // Only iOS doesn't respect `overflow: hidden` on document.body, and this
+ // technique has fewer side effects.
+ if (isIOS) {
+ // iOS 12 does not support `visualViewport`.
+ const offsetLeft = window.visualViewport?.offsetLeft || 0;
+ const offsetTop = window.visualViewport?.offsetTop || 0;
+
+ Object.assign(bodyStyle, {
+ position: 'fixed',
+ top: `${-(scrollY - Math.floor(offsetTop))}px`,
+ left: `${-(scrollX - Math.floor(offsetLeft))}px`,
+ right: '0',
+ });
+ }
+
+ return () => {
+ Object.assign(bodyStyle, {
+ overflow: '',
+ [paddingProp]: '',
+ });
+
+ if (isIOS) {
+ Object.assign(bodyStyle, {
+ position: '',
+ top: '',
+ left: '',
+ right: '',
+ });
+ window.scrollTo(scrollX, scrollY);
+ }
+ };
+}
+
+let cleanup = () => {};
+
+export function useScrollLock() {
+ return {
+ enableScrollLock: () => {
+ lockCount++;
+
+ if (lockCount === 1) {
+ cleanup = enableScrollLock();
+ }
+ },
+ disableScrollLock: () => {
+ lockCount--;
+ if (lockCount === 0) {
+ cleanup();
+ }
+ },
+ };
+}