From 0227a829f468a5172b4f01d7c07d25285271f495 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 21 Aug 2026 07:34:27 +1000 Subject: [PATCH 1/3] revert "feat: add useShowFocusIndicator hook (#10426)" --- packages/react-aria/exports/index.ts | 1 - .../exports/useShowFocusIndicator.ts | 13 ------- .../src/interactions/useFocusVisible.ts | 9 +---- .../test/interactions/useFocusVisible.test.js | 37 +------------------ 4 files changed, 2 insertions(+), 58 deletions(-) delete mode 100644 packages/react-aria/exports/useShowFocusIndicator.ts diff --git a/packages/react-aria/exports/index.ts b/packages/react-aria/exports/index.ts index 2e0d996c660..84ae4850e3a 100644 --- a/packages/react-aria/exports/index.ts +++ b/packages/react-aria/exports/index.ts @@ -77,7 +77,6 @@ export {useNumberFormatter} from '../src/i18n/useNumberFormatter'; export {useListFormatter} from '../src/i18n/useListFormatter'; export {useFocus} from '../src/interactions/useFocus'; export {useFocusVisible} from '../src/interactions/useFocusVisible'; -export {useShowFocusIndicator} from '../src/interactions/useFocusVisible'; export {useFocusWithin} from '../src/interactions/useFocusWithin'; export {useHover} from '../src/interactions/useHover'; export {useInteractOutside} from '../src/interactions/useInteractOutside'; diff --git a/packages/react-aria/exports/useShowFocusIndicator.ts b/packages/react-aria/exports/useShowFocusIndicator.ts deleted file mode 100644 index 50e431f11a8..00000000000 --- a/packages/react-aria/exports/useShowFocusIndicator.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright 2026 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ - -export {useShowFocusIndicator} from '../src/interactions/useFocusVisible'; diff --git a/packages/react-aria/src/interactions/useFocusVisible.ts b/packages/react-aria/src/interactions/useFocusVisible.ts index 72b1d22fd25..9e992f5334f 100644 --- a/packages/react-aria/src/interactions/useFocusVisible.ts +++ b/packages/react-aria/src/interactions/useFocusVisible.ts @@ -23,7 +23,7 @@ import {isMac} from '../utils/platform'; import {isVirtualClick} from '../utils/isVirtualEvent'; import {openLink} from '../utils/openLink'; import {PointerType} from '@react-types/shared'; -import {useCallback, useEffect, useState} from 'react'; +import {useEffect, useState} from 'react'; import {useIsSSR} from '../ssr/SSRProvider'; export type Modality = 'keyboard' | 'pointer' | 'virtual'; @@ -303,13 +303,6 @@ export function setInteractionModality(modality: Modality): void { triggerChangeHandlers(modality, null); } -/** - * Returns a callback that makes the focus indicator visible. - */ -export function useShowFocusIndicator(): () => void { - return useCallback(() => setInteractionModality('keyboard'), []); -} - /** @private */ export function getPointerType(): PointerType { return currentPointerType; diff --git a/packages/react-aria/test/interactions/useFocusVisible.test.js b/packages/react-aria/test/interactions/useFocusVisible.test.js index 72c798438d0..d469996ee35 100644 --- a/packages/react-aria/test/interactions/useFocusVisible.test.js +++ b/packages/react-aria/test/interactions/useFocusVisible.test.js @@ -21,8 +21,7 @@ import { import { addWindowFocusTracking, useFocusVisible, - useFocusVisibleListener, - useShowFocusIndicator + useFocusVisibleListener } from '../../src/interactions/useFocusVisible'; import {changeHandlers, hasSetupGlobalListeners} from '../../src/interactions/useFocusVisible'; import {mergeProps} from '../../src/utils/mergeProps'; @@ -376,40 +375,6 @@ describe('useFocusVisible', function () { }); }); -describe('useShowFocusIndicator', function () { - // A form library that moves focus to the first invalid field does so by calling - // element.focus() from the submit handler. Clicking submit leaves the modality on - // 'pointer', so the programmatic focus lands without a visible indicator. - function FormExample() { - let {focusProps, isFocusVisible} = useFocusRing(); - let showFocusIndicator = useShowFocusIndicator(); - let ref = React.useRef(null); - - let onSubmit = e => { - e.preventDefault(); - showFocusIndicator(); - ref.current.focus(); - }; - - return ( -
- - -
- ); - } - - it('shows the focus indicator on programmatic focus after a pointer interaction', async function () { - let user = userEvent.setup({delay: null, pointerMap}); - render(); - await user.click(screen.getByRole('button', {name: 'Submit'})); - - let input = screen.getByRole('textbox'); - expect(input).toHaveFocus(); - expect(input).toHaveAttribute('data-focus-visible'); - }); -}); - describe('useFocusVisibleListener', function () { it('emits on modality change (non-text input)', function () { let fnMock = jest.fn(); From 84ef32d19041b26d145ddbe4241ada6cf084fa6a Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 21 Aug 2026 07:50:01 +1000 Subject: [PATCH 2/3] expose setInteractionModality --- .../s2-docs/pages/react-aria/useFocusVisible.mdx | 14 ++++++++++++++ packages/react-aria-components/exports/index.ts | 2 ++ packages/react-aria/exports/index.ts | 8 ++++++-- packages/react-aria/exports/useFocusVisible.ts | 3 ++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx b/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx index 27fb5cbf28b..20c785c72e2 100644 --- a/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx +++ b/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx @@ -53,6 +53,20 @@ in CSS. To determine whether a focus ring should be visible for an individual component rather than globally, see [useFocusRing](useFocusRing). +### Making focus visible + +For the most part, focus visibility is handled automatically by `useFocusVisible`. However, if you need to +manually set the interaction modality, such as moving focus programmatically to the first item +of an invalid form, you can use `setInteractionModality`. + +```tsx +import {setInteractionModality} from 'react-aria/useFocusVisible'; + +setInteractionModality('keyboard'); +``` + + + ## API diff --git a/packages/react-aria-components/exports/index.ts b/packages/react-aria-components/exports/index.ts index b9c568ddb9f..9773c0a8ef0 100644 --- a/packages/react-aria-components/exports/index.ts +++ b/packages/react-aria-components/exports/index.ts @@ -276,6 +276,7 @@ export {Focusable} from 'react-aria/Focusable'; export {VisuallyHidden} from 'react-aria/VisuallyHidden'; export {FormValidationContext} from 'react-stately/private/form/useFormValidationState'; export {parseColor, getColorChannels} from 'react-stately/Color'; +export {setInteractionModality} from 'react-aria/useFocusVisible'; export {ToastQueue as UNSTABLE_ToastQueue} from 'react-stately/useToastState'; export {useListData} from 'react-stately/useListData'; export {useTreeData} from 'react-stately/useTreeData'; @@ -608,6 +609,7 @@ export type {DateRangePickerState} from 'react-stately/useDateRangePickerState'; export type {DisclosureState} from 'react-stately/useDisclosureState'; export type {DisclosureGroupState} from 'react-stately/useDisclosureGroupState'; export type {ListState} from 'react-stately/useListState'; +export type {Modality} from 'react-aria/useFocusVisible'; export type {NumberFieldState} from 'react-stately/useNumberFieldState'; export type {OverlayTriggerState} from 'react-stately/useOverlayTriggerState'; export type {QueuedToast, ToastOptions, ToastState} from 'react-stately/useToastState'; diff --git a/packages/react-aria/exports/index.ts b/packages/react-aria/exports/index.ts index 84ae4850e3a..2c8a2d20d1f 100644 --- a/packages/react-aria/exports/index.ts +++ b/packages/react-aria/exports/index.ts @@ -76,7 +76,7 @@ export { export {useNumberFormatter} from '../src/i18n/useNumberFormatter'; export {useListFormatter} from '../src/i18n/useListFormatter'; export {useFocus} from '../src/interactions/useFocus'; -export {useFocusVisible} from '../src/interactions/useFocusVisible'; +export {setInteractionModality, useFocusVisible} from '../src/interactions/useFocusVisible'; export {useFocusWithin} from '../src/interactions/useFocusWithin'; export {useHover} from '../src/interactions/useHover'; export {useInteractOutside} from '../src/interactions/useInteractOutside'; @@ -330,7 +330,11 @@ export type { } from '../src/dnd/useDroppableCollection'; export type {DroppableItemOptions, DroppableItemResult} from '../src/dnd/useDroppableItem'; export type {FocusProps, FocusResult} from '../src/interactions/useFocus'; -export type {FocusVisibleProps, FocusVisibleResult} from '../src/interactions/useFocusVisible'; +export type { + Modality, + FocusVisibleProps, + FocusVisibleResult +} from '../src/interactions/useFocusVisible'; export type {FocusWithinProps, FocusWithinResult} from '../src/interactions/useFocusWithin'; export type {HoverProps, HoverResult} from '../src/interactions/useHover'; export type {InteractOutsideProps} from '../src/interactions/useInteractOutside'; diff --git a/packages/react-aria/exports/useFocusVisible.ts b/packages/react-aria/exports/useFocusVisible.ts index fcaad89237f..7a468303552 100644 --- a/packages/react-aria/exports/useFocusVisible.ts +++ b/packages/react-aria/exports/useFocusVisible.ts @@ -10,6 +10,7 @@ * governing permissions and limitations under the License. */ -export {useFocusVisible} from '../src/interactions/useFocusVisible'; +export {setInteractionModality, useFocusVisible} from '../src/interactions/useFocusVisible'; export type {FocusVisibleProps, FocusVisibleResult} from '../src/interactions/useFocusVisible'; export type {FocusEvents} from '@react-types/shared'; +export type {Modality} from '../src/interactions/useFocusVisible'; From 2d1e946c1d9997bdb3346694b8be176b5e382e8e Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 21 Aug 2026 08:54:30 +1000 Subject: [PATCH 3/3] Update packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx Co-authored-by: Daniel Lu --- packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx b/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx index 20c785c72e2..fc452ee715f 100644 --- a/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx +++ b/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx @@ -56,7 +56,7 @@ globally, see [useFocusRing](useFocusRing). ### Making focus visible For the most part, focus visibility is handled automatically by `useFocusVisible`. However, if you need to -manually set the interaction modality, such as moving focus programmatically to the first item +manually show the focus ring, such as when moving focus programmatically to the first item of an invalid form, you can use `setInteractionModality`. ```tsx