Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
.announcementSlot {
height: var(--ory-announcement-height, 4rem);
}

.announcementSlot[data-dismissed="true"] {
height: 0;
}

.announcementContent a {
text-decoration: underline;
text-underline-offset: 0.2rem;
Expand Down
88 changes: 51 additions & 37 deletions src/components/AnnouncementBanner/AnnouncementBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright © 2026 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import React from "react"

import AnnouncementContent from "@site/src/content/announcement-banner.mdx"
Expand Down Expand Up @@ -55,52 +58,63 @@ export default function AnnouncementBanner() {
}
}, [enabled, id])

// When the announcement is disabled or has no id, render nothing at all.
if (!enabled || !id) return null
if (!ready) return null
if (dismissed) return null

const isDismissed = ready && dismissed

return (
<div
role="region"
aria-label="Announcement"
className={[
"border-b",
getLevelClasses(level),
// keep it readable across layouts
"text-base",
].join(" ")}
className={styles.announcementSlot}
data-dismissed={isDismissed ? "true" : "false"}
>
<div className="mx-auto flex max-w-screen-xl items-start gap-3 px-4 py-1">
{!isDismissed && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Flash of banner content for previously-dismissed users.

isDismissed = ready && dismissed is always false on the first render because ready starts false. This means the full banner content renders for one frame before useEffect runs — even for users who previously dismissed it. After the effect, isDismissed becomes true, the content disappears, and the slot collapses from 4rem to 0. This is both a visible content flash and a layout shift for returning users, partially undermining the CLS goal.

Gate content rendering on ready so the slot reserves space without showing content until dismissal state is known:

🛡️ Proposed fix
-      {!isDismissed && (
+      {ready && !isDismissed && (

This also keeps the ready state meaningful — without it, ready is redundant since dismissed already starts false.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{!isDismissed && (
{ready && !isDismissed && (
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AnnouncementBanner/AnnouncementBanner.tsx` at line 71, Prevent
the announcement banner content from flashing before dismissal state is loaded.
In AnnouncementBanner, update the render condition around the banner content to
require ready and not dismissed, while preserving the reserved slot layout
during initialization; keep the existing ready state and dismissal effect
behavior intact.

<div
className={`min-w-0 flex-1 leading-5 pt-1 text-center ${styles.announcementContent}`}
>
<AnnouncementContent />
</div>
<button
type="button"
role="region"
aria-label="Announcement"
className={[
"shrink-0 rounded-md p-1 mt-1",
"border-none bg-transparent cursor-pointer",
"text-current opacity-60 hover:opacity-100 hover:bg-black/5",
"transition-all duration-200",
"focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white",
"focus-visible:ring-purple-600",
"border-b",
getLevelClasses(level),
// keep it readable across layouts
"text-base",
].join(" ")}
aria-label="Dismiss announcement"
onClick={() => {
setDismissed(true)
try {
window.localStorage.setItem(getDismissStorageKey(id), "1")
} catch {
// ignore
}
}}
>
<span aria-hidden="true" className="text-lg font-light leading-none">
</span>
</button>
</div>
<div className="mx-auto flex max-w-screen-xl items-start gap-3 px-4 py-1">
<div
className={`min-w-0 flex-1 leading-5 pt-1 text-center ${styles.announcementContent}`}
>
<AnnouncementContent />
</div>
<button
type="button"
className={[
"shrink-0 rounded-md p-1 mt-1",
"border-none bg-transparent cursor-pointer",
"text-current opacity-60 hover:opacity-100 hover:bg-black/5",
"transition-all duration-200",
"focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white",
"focus-visible:ring-purple-600",
].join(" ")}
aria-label="Dismiss announcement"
onClick={() => {
setDismissed(true)
try {
window.localStorage.setItem(getDismissStorageKey(id), "1")
} catch {
// ignore
}
}}
>
<span
aria-hidden="true"
className="text-lg font-light leading-none"
>
</span>
</button>
</div>
</div>
)}
</div>
)
}
Loading