diff --git a/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx b/packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx
index 27fb5cbf28b..fc452ee715f 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 show the focus ring, such as when 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 2e0d996c660..2c8a2d20d1f 100644
--- a/packages/react-aria/exports/index.ts
+++ b/packages/react-aria/exports/index.ts
@@ -76,8 +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 {useShowFocusIndicator} 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';
@@ -331,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';
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();