diff --git a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx
index 2366a4ea46..840178a50f 100644
--- a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx
+++ b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx
@@ -3,7 +3,6 @@ import {
SuggestionMenu,
SuggestionMenuOptions,
} from "@blocknote/core/extensions";
-import { autoPlacement, offset, shift, size } from "@floating-ui/react";
import { FC, useEffect, useMemo } from "react";
import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js";
@@ -14,6 +13,7 @@ import {
} from "../../../hooks/useExtension.js";
import { PortalElementOverride } from "../../../editor/PortalElementOverride.js";
import { FloatingUIOptions } from "../../Popovers/FloatingUIOptions.js";
+import { suggestionMenuMiddleware } from "../suggestionMenuMiddleware.js";
import {
GenericPopover,
GenericPopoverReference,
@@ -135,22 +135,7 @@ export function GridSuggestionMenuController<
}
},
placement: "bottom-start",
- middleware: [
- offset(10),
- // Flips the menu placement to maximize the space available, and prevents
- // the menu from being cut off by the confines of the screen.
- autoPlacement({
- allowedPlacements: ["bottom-start", "top-start"],
- padding: 10,
- }),
- shift(),
- size({
- apply({ elements, availableHeight }) {
- elements.floating.style.maxHeight = `${Math.max(0, availableHeight)}px`;
- },
- padding: 10,
- }),
- ],
+ middleware: suggestionMenuMiddleware,
...props.floatingUIOptions?.useFloatingOptions,
},
focusManagerProps: {
diff --git a/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx b/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx
index d2ac15b22e..6df8677288 100644
--- a/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx
+++ b/packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx
@@ -4,7 +4,6 @@ import {
SuggestionMenuOptions,
filterSuggestionItems,
} from "@blocknote/core/extensions";
-import { autoPlacement, offset, shift, size } from "@floating-ui/react";
import { FC, useEffect, useMemo } from "react";
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
@@ -12,6 +11,7 @@ import { useEditorDOMElement } from "../../hooks/useEditorDomElement.js";
import { useExtension, useExtensionState } from "../../hooks/useExtension.js";
import { PortalElementOverride } from "../../editor/PortalElementOverride.js";
import { FloatingUIOptions } from "../Popovers/FloatingUIOptions.js";
+import { suggestionMenuMiddleware } from "./suggestionMenuMiddleware.js";
import {
GenericPopover,
GenericPopoverReference,
@@ -128,22 +128,7 @@ export function SuggestionMenuController<
}
},
placement: "bottom-start",
- middleware: [
- offset(10),
- // Flips the menu placement to maximize the space available, and prevents
- // the menu from being cut off by the confines of the screen.
- autoPlacement({
- allowedPlacements: ["bottom-start", "top-start"],
- padding: 10,
- }),
- shift(),
- size({
- apply({ elements, availableHeight }) {
- elements.floating.style.maxHeight = `${Math.max(0, availableHeight)}px`;
- },
- padding: 10,
- }),
- ],
+ middleware: suggestionMenuMiddleware,
...props.floatingUIOptions?.useFloatingOptions,
},
focusManagerProps: {
diff --git a/packages/react/src/components/SuggestionMenu/suggestionMenuMiddleware.ts b/packages/react/src/components/SuggestionMenu/suggestionMenuMiddleware.ts
new file mode 100644
index 0000000000..f367b9767f
--- /dev/null
+++ b/packages/react/src/components/SuggestionMenu/suggestionMenuMiddleware.ts
@@ -0,0 +1,111 @@
+import { Middleware, offset, Placement, shift, size } from "@floating-ui/react";
+
+/** The gap (px) between the menu and its trigger character. */
+const OFFSET = 10;
+
+/** The space (px) kept between the menu and the edge of the screen. */
+const PADDING = 10;
+
+/**
+ * The most height (px) the menu takes, even with more room: a longer list
+ * scrolls, or the user filters it.
+ */
+const MAX_HEIGHT = 600;
+
+/**
+ * The least room (px) the menu needs below its trigger. With less room, the
+ * menu goes above instead, if there is more room there.
+ */
+const MIN_HEIGHT = 200;
+
+/**
+ * Places the menu below its trigger, unless there is less than `MIN_HEIGHT`
+ * of room there and more room above. Replaces Floating UI's `autoPlacement`.
+ *
+ * The decision uses a fixed height, not the menu's own (as `autoPlacement`
+ * does): `size` caps the menu
+ * to the room on its side (so a capped menu always fits where it is), and
+ * the items load asynchronously (so a menu that just opened can still be
+ * nearly empty). Thus the decision depends only on the trigger and the
+ * visible area: while the user filters the items, the menu keeps its side.
+ * When the trigger moves (e.g. the page scrolls) or the window is resized,
+ * Floating UI runs the middleware again and the side can change.
+ */
+const placeBelowUnlessCramped: Middleware = {
+ name: "placeBelowUnlessCramped",
+ async fn(state) {
+ const { rects, placement, platform } = state;
+ // A menu of `MIN_HEIGHT`, at the position of each side.
+ const floating = { ...rects.floating, height: MIN_HEIGHT };
+ const below = await platform.detectOverflow(
+ {
+ ...state,
+ rects: { ...rects, floating },
+ y: rects.reference.y + rects.reference.height + OFFSET,
+ },
+ { padding: PADDING },
+ );
+
+ let wanted: Placement = "bottom-start";
+ if (below.bottom > 0) {
+ // The menu does not fit below: it goes above if there is more room
+ // there.
+ const above = await platform.detectOverflow(
+ {
+ ...state,
+ rects: { ...rects, floating },
+ y: rects.reference.y - OFFSET - MIN_HEIGHT,
+ },
+ { padding: PADDING },
+ );
+ if (above.top < below.bottom) {
+ wanted = "top-start";
+ }
+ }
+
+ // The menu is already on that side: nothing to change.
+ if (wanted === placement) {
+ return {};
+ }
+ // Floating UI runs the middleware again with the menu on that side.
+ return { reset: { placement: wanted } };
+ },
+};
+
+/**
+ * Keeps the menu at the widest it has been since it opened. The menu is as
+ * wide as its widest item, so without this it would shrink and grow while
+ * the user filters the items. It can still grow, e.g. when items load
+ * asynchronously. Each time the menu opens, it is a new floating element, so
+ * it starts at its own width again.
+ *
+ * The minimum width is on the floating element, which wraps the menu: the
+ * menu grows to fill it (see `.bn-suggestion-menu` in `styles.css`).
+ */
+function keepWidestWidth(floating: HTMLElement) {
+ const width = floating.getBoundingClientRect().width;
+ // The current width includes the minimum set before, so the minimum only
+ // grows. It changes only by whole pixels, so rounding cannot loop.
+ if (width >= (parseFloat(floating.style.minWidth) || 0) + 1) {
+ floating.style.minWidth = `${Math.ceil(width)}px`;
+ }
+}
+
+/**
+ * The Floating UI middleware of the suggestion menus: below the trigger
+ * unless that is too cramped, within the screen, no taller than the room on
+ * its side or `MAX_HEIGHT` (the menu scrolls), and no narrower than it has
+ * been since it opened.
+ */
+export const suggestionMenuMiddleware: Middleware[] = [
+ placeBelowUnlessCramped,
+ offset(OFFSET),
+ shift(),
+ size({
+ apply({ elements, availableHeight }) {
+ elements.floating.style.maxHeight = `${Math.max(0, Math.min(availableHeight, MAX_HEIGHT))}px`;
+ keepWidestWidth(elements.floating);
+ },
+ padding: PADDING,
+ }),
+];
diff --git a/packages/react/src/editor/styles.css b/packages/react/src/editor/styles.css
index f72b760521..1bb4fb4a86 100644
--- a/packages/react/src/editor/styles.css
+++ b/packages/react/src/editor/styles.css
@@ -262,6 +262,14 @@ SideMenuController offsets its position to keep it centered on the line. */
height: 30px;
}
+/* Suggestion menus fill their floating element, which keeps the widest width
+ the menu has had since it opened (`suggestionMenuMiddleware.ts`). Without
+ this, a menu would shrink and grow as the user filters its items. */
+.bn-suggestion-menu,
+.bn-grid-suggestion-menu {
+ flex-grow: 1;
+}
+
/* Thread sidebar styling */
.bn-threads-sidebar {
border-radius: var(--bn-border-radius-medium);
diff --git a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-chromium-linux.png b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-chromium-linux.png
index e1efe0e03c..5de8883883 100644
Binary files a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-chromium-linux.png and b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-chromium-linux.png differ
diff --git a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-firefox-linux.png b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-firefox-linux.png
index dafa559cc4..783674148f 100644
Binary files a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-firefox-linux.png and b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-firefox-linux.png differ
diff --git a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-webkit-linux.png b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-webkit-linux.png
index 97fa0e3170..d19b3abfab 100644
Binary files a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-webkit-linux.png and b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_down-webkit-linux.png differ
diff --git a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-chromium-linux.png b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-chromium-linux.png
index b647cc183f..8653c13946 100644
Binary files a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-chromium-linux.png and b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-chromium-linux.png differ
diff --git a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-firefox-linux.png b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-firefox-linux.png
index a55c42d12a..4616178af8 100644
Binary files a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-firefox-linux.png and b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-firefox-linux.png differ
diff --git a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-webkit-linux.png b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-webkit-linux.png
index 121450b52b..a6a81e6e52 100644
Binary files a/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-webkit-linux.png and b/tests/src/end-to-end/slashmenu/__screenshots__/slashmenu.test.tsx/slash_menu_page_up-webkit-linux.png differ
diff --git a/tests/src/end-to-end/slashmenu/suggestionMenuPlacement.test.tsx b/tests/src/end-to-end/slashmenu/suggestionMenuPlacement.test.tsx
new file mode 100644
index 0000000000..a4a6c307cd
--- /dev/null
+++ b/tests/src/end-to-end/slashmenu/suggestionMenuPlacement.test.tsx
@@ -0,0 +1,284 @@
+import { BlockNoteEditor } from "@blocknote/core";
+import { filterSuggestionItems } from "@blocknote/core/extensions";
+import { BlockNoteView } from "@blocknote/mantine";
+import "@blocknote/mantine/style.css";
+import {
+ getDefaultReactSlashMenuItems,
+ SuggestionMenuController,
+ useCreateBlockNote,
+} from "@blocknote/react";
+import { afterEach, expect, test, vi } from "vite-plus/test";
+import { cleanup, render } from "vitest-browser-react";
+
+import { page, userEvent } from "../../utils/context.js";
+import { sleep, waitForSelector } from "../../utils/editor.js";
+
+// Where the slash menu goes relative to its trigger (#2771): below, unless
+// there is too little room there and more room above. It keeps its side
+// while the user filters the items.
+
+/** How long the slash menu items take to load, with `slowItems`. */
+const ITEMS_DELAY = 500;
+
+let editor: BlockNoteEditor;
+
+function Editor(props: { slowItems?: boolean }) {
+ editor = useCreateBlockNote({
+ initialContent: Array.from({ length: 40 }, (_, i) => ({
+ type: "paragraph" as const,
+ content: `Paragraph ${i}`,
+ })),
+ });
+ if (!props.slowItems) {
+ return ;
+ }
+ // Items that load asynchronously, e.g. from a server.
+ return (
+
+ {
+ await sleep(ITEMS_DELAY);
+ return filterSuggestionItems(
+ getDefaultReactSlashMenuItems(editor),
+ query,
+ );
+ }}
+ />
+
+ );
+}
+
+afterEach(async () => {
+ await cleanup();
+});
+
+/** Opens the slash menu at the end of the paragraph at height `y`. */
+async function openSlashMenuAt(
+ y: number,
+ options: { slowItems?: boolean; scrollTop?: number } = {},
+) {
+ await render();
+ await waitForSelector(".bn-editor");
+ if (options.scrollTop) {
+ document.scrollingElement!.scrollTop = options.scrollTop;
+ await sleep(100);
+ }
+ const menu = await typeSlashAt(y);
+ await sleep(200);
+ return menu;
+}
+
+/** Types "/" at the end of the paragraph at height `y`, in the editor. */
+async function typeSlashAt(y: number) {
+ const paragraph = [
+ ...document.querySelectorAll('[data-content-type="paragraph"]'),
+ ].find((element) => {
+ const rect = element.getBoundingClientRect();
+ return rect.top <= y && rect.bottom >= y;
+ })!;
+ // Through the API rather than a click: a window resized with
+ // `page.viewport` is scaled, so mouse coordinates would miss.
+ editor.setTextCursorPosition(
+ paragraph
+ .closest('[data-node-type="blockContainer"]')!
+ .getAttribute("data-id")!,
+ "end",
+ );
+ editor.focus();
+ await userEvent.keyboard("/");
+ return waitForSelector(".bn-suggestion-menu");
+}
+
+/** The gap (px) between the menu and its trigger. */
+function gap(menu: HTMLElement): number {
+ const trigger = document
+ .querySelector("[data-decoration-id]")!
+ .getBoundingClientRect();
+ const rect = menu.getBoundingClientRect();
+ return side(menu) === "below"
+ ? rect.top - trigger.bottom
+ : trigger.top - rect.bottom;
+}
+
+/** The side of the trigger the menu is on. */
+function side(menu: HTMLElement): "above" | "below" {
+ const trigger = document
+ .querySelector("[data-decoration-id]")!
+ .getBoundingClientRect();
+ return menu.getBoundingClientRect().top > trigger.top ? "below" : "above";
+}
+
+test("stays below the trigger when there is enough room, even with more room above", async () => {
+ // About 300px below the trigger, more above.
+ const menu = await openSlashMenuAt(innerHeight * 0.55);
+ expect(side(menu)).toBe("below");
+});
+
+test("is no taller than its maximum height, even with more room", async () => {
+ // A taller window than the default 720px, so the room below the trigger
+ // (about 850px) is more than the maximum height.
+ await page.viewport(1280, 1000);
+ try {
+ const menu = await openSlashMenuAt(innerHeight * 0.1);
+ expect(side(menu)).toBe("below");
+ const height = menu.getBoundingClientRect().height;
+ expect(height).toBeLessThanOrEqual(600);
+ expect(height).toBeGreaterThan(550);
+ // The full list is longer, so it scrolls.
+ expect(menu.scrollHeight).toBeGreaterThan(menu.clientHeight);
+ } finally {
+ await page.viewport(1280, 720);
+ }
+});
+
+test("goes above the trigger when there is too little room below", async () => {
+ const menu = await openSlashMenuAt(innerHeight * 0.85);
+ expect(side(menu)).toBe("above");
+ // Not squeezed: it gets the room above.
+ expect(menu.getBoundingClientRect().height).toBeGreaterThan(200);
+});
+
+test("keeps its side while the user filters the items", async () => {
+ // About 150px below the trigger: the full list goes above, and the
+ // filtered list would fit below.
+ const menu = await openSlashMenuAt(innerHeight - 170);
+ expect(side(menu)).toBe("above");
+
+ for (const key of ["q", "u", "o", "t", "e"]) {
+ await userEvent.keyboard(key);
+ await sleep(100);
+ expect(side(menu)).toBe("above");
+ // #2155: the shorter menu stays next to the trigger.
+ expect(gap(menu)).toBeLessThan(15);
+ }
+ for (let i = 0; i < 5; i++) {
+ await userEvent.keyboard("{Backspace}");
+ await sleep(100);
+ expect(side(menu)).toBe("above");
+ }
+});
+
+test("goes above the trigger at once when the items load asynchronously", async () => {
+ const menu = await openSlashMenuAt(innerHeight * 0.85, { slowItems: true });
+ // The items are still loading: the menu is nearly empty, and still goes
+ // above.
+ expect(menu.querySelectorAll(".bn-suggestion-menu-item")).toHaveLength(0);
+ expect(side(menu)).toBe("above");
+
+ await sleep(ITEMS_DELAY + 300);
+ expect(
+ menu.querySelectorAll(".bn-suggestion-menu-item").length,
+ ).toBeGreaterThan(5);
+ expect(side(menu)).toBe("above");
+});
+
+test("chooses its side again when the page scrolls", async () => {
+ // The page is scrolled down first, so it can scroll back up below.
+ const menu = await openSlashMenuAt(innerHeight * 0.5, { scrollTop: 300 });
+ expect(side(menu)).toBe("below");
+
+ // Scrolling up moves the trigger down, near the bottom of the screen.
+ document.scrollingElement!.scrollTop = 300 - innerHeight * 0.35;
+ // WebKit delivers the scroll event late (about a second).
+ await vi.waitFor(() => expect(side(menu)).toBe("above"), { timeout: 3000 });
+});
+
+test("chooses its side again when the window is resized", async () => {
+ try {
+ // About 300px below the trigger: the menu goes below.
+ const menu = await openSlashMenuAt(innerHeight * 0.55);
+ expect(side(menu)).toBe("below");
+
+ // A shorter window (e.g. a phone's keyboard opens): little room is left
+ // below the trigger, and more above.
+ await page.viewport(1280, 480);
+ await sleep(300);
+ expect(side(menu)).toBe("above");
+ } finally {
+ await page.viewport(1280, 720);
+ }
+});
+
+test("shows the last item when it is selected near the bottom of the screen", async () => {
+ const menu = await openSlashMenuAt(innerHeight * 0.85);
+
+ // ArrowUp from the first item selects the last one.
+ await userEvent.keyboard("{ArrowUp}");
+ await sleep(300);
+
+ const selected = menu.querySelector('[aria-selected="true"]')!;
+ const item = selected.getBoundingClientRect();
+ const visible = menu.getBoundingClientRect();
+ expect(item.top).toBeGreaterThanOrEqual(visible.top - 1);
+ expect(item.bottom).toBeLessThanOrEqual(visible.bottom + 1);
+});
+
+/** The menu's width (px). */
+function width(menu: HTMLElement): number {
+ return Math.round(menu.getBoundingClientRect().width);
+}
+
+test("keeps its width while the user filters the items", async () => {
+ const menu = await openSlashMenuAt(innerHeight * 0.15);
+ const opened = width(menu);
+
+ // "quo" leaves only short items, which on their own are narrower.
+ for (const key of ["q", "u", "o"]) {
+ await userEvent.keyboard(key);
+ await sleep(100);
+ expect(width(menu)).toBe(opened);
+ }
+ for (let i = 0; i < 3; i++) {
+ await userEvent.keyboard("{Backspace}");
+ await sleep(100);
+ expect(width(menu)).toBe(opened);
+ }
+});
+
+test("grows when the items load asynchronously, then keeps its width while filtering", async () => {
+ const menu = await openSlashMenuAt(innerHeight * 0.15, { slowItems: true });
+ const loading = width(menu);
+
+ await sleep(ITEMS_DELAY + 300);
+ const loaded = width(menu);
+ // The items are wider than the menu while it loads: it grows.
+ expect(loaded).toBeGreaterThan(loading);
+
+ // Each query loads its items again.
+ await userEvent.keyboard("quo");
+ await sleep(ITEMS_DELAY + 300);
+ expect(menu.querySelectorAll(".bn-suggestion-menu-item").length).toBeLessThan(
+ 5,
+ );
+ expect(width(menu)).toBe(loaded);
+});
+
+test("chooses its side and height again when opened again elsewhere", async () => {
+ const first = await openSlashMenuAt(innerHeight * 0.15);
+ expect(side(first)).toBe("below");
+
+ await userEvent.keyboard("{Escape}");
+ await sleep(400);
+ const second = await typeSlashAt(innerHeight * 0.85);
+ await sleep(200);
+ expect(side(second)).toBe("above");
+ // The room above, not the height it had below.
+ expect(second.getBoundingClientRect().height).toBeGreaterThan(400);
+});
+
+test("starts at its own width when opened again", async () => {
+ const first = await openSlashMenuAt(innerHeight * 0.15, {
+ slowItems: true,
+ });
+ await sleep(ITEMS_DELAY + 300);
+ const loaded = width(first);
+
+ await userEvent.keyboard("{Escape}");
+ // After the menu has faded out.
+ await sleep(400);
+ const second = await typeSlashAt(innerHeight * 0.3);
+ // The items are loading again: the menu is as narrow as it was then, not
+ // as wide as it was with its items.
+ expect(width(second)).toBeLessThan(loaded);
+});
diff --git a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-chromium-linux.png b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-chromium-linux.png
index 87aa204a2f..f0358b4ef9 100644
Binary files a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-chromium-linux.png and b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-chromium-linux.png differ
diff --git a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-firefox-linux.png b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-firefox-linux.png
index 7f4e150f92..04bb1de310 100644
Binary files a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-firefox-linux.png and b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-firefox-linux.png differ
diff --git a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-webkit-linux.png b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-webkit-linux.png
index 957b8b3053..684b6e1a50 100644
Binary files a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-webkit-linux.png and b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-slash-menu-webkit-linux.png differ