diff --git a/services/web/frontend/js/shared/components/notification-scrolled-to.tsx b/services/web/frontend/js/shared/components/notification-scrolled-to.tsx
new file mode 100644
index 0000000000..9d6e77f200
--- /dev/null
+++ b/services/web/frontend/js/shared/components/notification-scrolled-to.tsx
@@ -0,0 +1,46 @@
+import Notification, {
+ NotificationProps,
+} from '@/shared/components/notification'
+import { useEffect } from 'react'
+
+function elementIsInView(el: HTMLElement) {
+ const scroll = window.scrollY
+ const boundsTop = el.getBoundingClientRect().top + scroll
+
+ const viewport = {
+ top: scroll,
+ bottom: scroll + window.innerHeight,
+ }
+
+ const bounds = {
+ top: boundsTop,
+ bottom: boundsTop + el.clientHeight,
+ }
+
+ return (
+ (bounds.bottom >= viewport.top && bounds.bottom <= viewport.bottom) ||
+ (bounds.top <= viewport.bottom && bounds.top >= viewport.top)
+ )
+}
+
+function NotificationScrolledTo({ ...props }: NotificationProps) {
+ useEffect(() => {
+ if (props.id) {
+ const alert = document.getElementById(props.id)
+ if (alert && !elementIsInView(alert)) {
+ alert.scrollIntoView({ behavior: 'smooth' })
+ }
+ }
+ }, [props])
+
+ const notificationProps = { ...props }
+
+ if (!notificationProps.className) {
+ notificationProps.className = ''
+ }
+
+ notificationProps.className = `${notificationProps.className} notification-with-scroll-margin`
+
+ return
+}
+export default NotificationScrolledTo
diff --git a/services/web/frontend/js/shared/components/notification.tsx b/services/web/frontend/js/shared/components/notification.tsx
index c8f15be406..574a7c44d7 100644
--- a/services/web/frontend/js/shared/components/notification.tsx
+++ b/services/web/frontend/js/shared/components/notification.tsx
@@ -10,7 +10,7 @@ export type NotificationType =
| 'error'
| 'offer'
-type NotificationProps = {
+export type NotificationProps = {
action?: React.ReactElement
ariaLive?: 'polite' | 'off' | 'assertive'
className?: string
@@ -21,6 +21,7 @@ type NotificationProps = {
onDismiss?: () => void
title?: string
type: NotificationType
+ id?: string
}
function NotificationIcon({
@@ -57,6 +58,7 @@ function Notification({
onDismiss,
title,
type,
+ id,
}: NotificationProps) {
type = type || 'info'
const { t } = useTranslation()
@@ -83,6 +85,7 @@ function Notification({
className={notificationClassName}
aria-live={ariaLive || 'off'}
role="alert"
+ id={id}
>
diff --git a/services/web/frontend/stylesheets/components/forms.less b/services/web/frontend/stylesheets/components/forms.less
index 001f46a52a..5a04165714 100755
--- a/services/web/frontend/stylesheets/components/forms.less
+++ b/services/web/frontend/stylesheets/components/forms.less
@@ -192,15 +192,6 @@ input[type='date'] {
margin-bottom: @form-group-margin-bottom;
}
-// Form alert
-//
-// adds top padding/mergin so that element has spacing when scrolled into
-// view, but no vertical spacing is applied to layout
-.form-group#form-alert {
- padding-top: @form-group-margin-bottom;
- margin-top: -@form-group-margin-bottom;
-}
-
// Checkboxes and radios
//
// Indent the labels to position radios/checkboxes as hanging controls.
diff --git a/services/web/frontend/stylesheets/components/notifications.less b/services/web/frontend/stylesheets/components/notifications.less
index dd86eb061c..b46fa9d910 100644
--- a/services/web/frontend/stylesheets/components/notifications.less
+++ b/services/web/frontend/stylesheets/components/notifications.less
@@ -180,6 +180,10 @@
}
}
+.notification-with-scroll-margin {
+ scroll-margin: 16px;
+}
+
.notification-list {
.notification {
margin-bottom: @margin-md;