diff --git a/desktop/src/features/messages/ui/FormattingToolbar.tsx b/desktop/src/features/messages/ui/FormattingToolbar.tsx
index 7c3af11ab..fe76bdb56 100644
--- a/desktop/src/features/messages/ui/FormattingToolbar.tsx
+++ b/desktop/src/features/messages/ui/FormattingToolbar.tsx
@@ -269,7 +269,7 @@ export const FormattingToolbar = React.memo(function FormattingToolbar({
return (
{items.map((item) => (
-
+
diff --git a/desktop/src/features/messages/ui/FormattingToolbar.tsx b/desktop/src/features/messages/ui/FormattingToolbar.tsx
index fe76bdb56..c82e78390 100644
--- a/desktop/src/features/messages/ui/FormattingToolbar.tsx
+++ b/desktop/src/features/messages/ui/FormattingToolbar.tsx
@@ -291,16 +291,22 @@ export const FormattingToolbar = React.memo(function FormattingToolbar({
- {/* pointer-events-none keeps these label tooltips click-through so
- they don't block the message textarea floating beneath them,
- matching ComposerIconButton. Content only — the trigger button
- keeps its pointer/focus behavior (WCAG content-on-hover-or-focus).
- disableHoverableContent (on the Root above) kills Radix's
- hover-to-persist safe bridge so the tooltip dismisses the instant
- the cursor leaves the trigger. These formatting buttons are raw
+ {/* pointer-events-none + select-none keep these label tooltips
+ click-through and non-selectable so they don't block the message
+ textarea floating beneath them, matching ComposerIconButton.
+ Content only — the trigger button keeps its pointer/focus behavior
+ (WCAG content-on-hover-or-focus). disableHoverableContent (on the
+ Root above) kills Radix's hover-to-persist safe bridge; the
+ data-composer-tooltip marker lets a scoped globals.css rule reach
+ the positioned [data-radix-popper-content-wrapper] (pe:auto,
+ overlaps the trigger) that's the real camp surface — see
+ ComposerIconButton's doc comment. These formatting buttons are raw
s with custom active styling, so we apply the overrides
here rather than swap in ComposerIconButton. */}
-
+
{"shortcut" in item
? `${item.label} (${item.shortcut})`
: item.label}
diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css
index ff63f3051..9a9eb7da8 100644
--- a/desktop/src/shared/styles/globals.css
+++ b/desktop/src/shared/styles/globals.css
@@ -2229,3 +2229,25 @@
transform: translateX(400%);
}
}
+
+/*
+ * Composer tooltip click-through + non-camp.
+ *
+ * Radix renders TooltipContent inside a positioned
+ * [data-radix-popper-content-wrapper] DIV that it styles directly with
+ * pointer-events: auto and never exposes to React props. That wrapper is the
+ * outermost popup surface and it overlaps the trigger, so a real cursor sliding
+ * off the trigger lands on the wrapper, keeps the tooltip alive (camps on it),
+ * and can select its text — pointer-events-none on the inner content alone is
+ * one level too shallow to stop it.
+ *
+ * We tag the composer tooltips' content with data-composer-tooltip and reach
+ * the wrapper via :has(), so pointer-events/select are killed on the actual
+ * camp surface. Scoped to composer tooltips only — no app-wide tooltip change.
+ * The trigger keeps its hover/focus-to-show lifecycle (WCAG
+ * content-on-hover-or-focus); we only make the floating popup inert.
+ */
+[data-radix-popper-content-wrapper]:has(> [data-composer-tooltip]) {
+ pointer-events: none;
+ user-select: none;
+}