From 5427f5a2e3c13cecf9a4e57ea2ac8f5c7bb85dba Mon Sep 17 00:00:00 2001
From: automated-signal <37887102+automated-signal@users.noreply.github.com>
Date: Tue, 9 Jun 2026 22:04:51 -0500
Subject: [PATCH] Autofocus on close button when opening lightbox
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
---
stylesheets/components/Lightbox.scss | 6 ++++--
ts/components/Lightbox.dom.tsx | 7 ++-----
ts/components/conversation/Message.dom.tsx | 1 +
ts/hooks/useRestoreFocus.dom.ts | 7 ++++++-
4 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/stylesheets/components/Lightbox.scss b/stylesheets/components/Lightbox.scss
index 10376657df..ebcd97637e 100644
--- a/stylesheets/components/Lightbox.scss
+++ b/stylesheets/components/Lightbox.scss
@@ -332,8 +332,10 @@
width: 100%;
}
- &:focus {
- outline: 4px solid variables.$color-ultramarine;
+ @include mixins.keyboard-mode {
+ &:focus {
+ outline: 4px solid variables.$color-ultramarine;
+ }
}
&:disabled {
diff --git a/ts/components/Lightbox.dom.tsx b/ts/components/Lightbox.dom.tsx
index 9a9297c823..08ed9e3c4f 100644
--- a/ts/components/Lightbox.dom.tsx
+++ b/ts/components/Lightbox.dom.tsx
@@ -729,11 +729,7 @@ export function Lightbox({
role="presentation"
>
-
+
{getConversation && currentItem != null ? (
diff --git a/ts/components/conversation/Message.dom.tsx b/ts/components/conversation/Message.dom.tsx
index cf0b32c709..de0d487e3f 100644
--- a/ts/components/conversation/Message.dom.tsx
+++ b/ts/components/conversation/Message.dom.tsx
@@ -3309,6 +3309,7 @@ export class Message extends PureComponent
{
return;
}
+ window.enterKeyboardMode();
this.handleOpen(event);
};
diff --git a/ts/hooks/useRestoreFocus.dom.ts b/ts/hooks/useRestoreFocus.dom.ts
index ed1f443a75..b1b7d5e2f7 100644
--- a/ts/hooks/useRestoreFocus.dom.ts
+++ b/ts/hooks/useRestoreFocus.dom.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { useRef, useCallback, useEffect } from 'react';
+import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary.std.ts';
type CallbackType = (toFocus: HTMLElement | null | undefined) => void;
@@ -9,11 +10,15 @@ type CallbackType = (toFocus: HTMLElement | null | undefined) => void;
export const useRestoreFocus = (): [CallbackType] => {
const toFocusRef = useRef(null);
const lastFocusedRef = useRef(null);
+ const focusLastElementTimeoutRef =
+ useRef>(null);
// We need to use a callback here because refs aren't necessarily populated on first
// render. For example, ModalHost makes a top-level parent div first, and then renders
// into it. And the children you pass it don't have access to that root div.
const setFocusRef = useCallback((toFocus: HTMLElement | null | undefined) => {
+ clearTimeoutIfNecessary(focusLastElementTimeoutRef.current);
+
if (!toFocus) {
return;
}
@@ -32,7 +37,7 @@ export const useRestoreFocus = (): [CallbackType] => {
useEffect(() => {
return () => {
// On unmount, returned focus to element focused before we set the focus
- setTimeout(() => {
+ focusLastElementTimeoutRef.current = setTimeout(() => {
if (lastFocusedRef.current && lastFocusedRef.current.focus) {
lastFocusedRef.current.focus();
}