From 5191d9e4b8c72951e93b0ba8c914debf1a2329f0 Mon Sep 17 00:00:00 2001
From: AK <144495202+AKnassa@users.noreply.github.com>
Date: Sat, 1 Aug 2026 00:36:05 -0400
Subject: [PATCH] fix(react): show TextField reveal button in Edge
TextField suppressed its own password reveal button on Edge via a
UA sniff (added when the feature shipped, to avoid doubling up with
Edge's native ::-ms-reveal widget). That leaves Edge users with the
native control, which cannot be themed, fails contrast in Windows
contrast themes (Dark, Aquatic, Desert), and is mouse-only.
Drop the Edge carve-out (IE 11 keeps its unhideable native button)
and hide ::-ms-reveal on the field when the custom button renders,
matching the existing ::-ms-clear idiom. Give the reveal button a
resting forced-colors color (ButtonText) so the icon stays visible
in contrast themes; hover already used Highlight.
Password fields without canRevealPassword keep Edge's native
reveal. The now-focusable button also helps #35657.
Fixes #35656
---
...-bf8f90c6-793f-49b5-baff-4859f3b7aa14.json | 7 +++++
.../components/TextField/TextField.base.tsx | 15 +++--------
.../components/TextField/TextField.styles.tsx | 11 ++++++++
.../components/TextField/TextField.test.tsx | 27 +++++++++++++++++++
.../components/TextField/TextField.types.ts | 9 ++++---
.../__snapshots__/TextField.test.tsx.snap | 6 +++++
6 files changed, 60 insertions(+), 15 deletions(-)
create mode 100644 change/@fluentui-react-bf8f90c6-793f-49b5-baff-4859f3b7aa14.json
diff --git a/change/@fluentui-react-bf8f90c6-793f-49b5-baff-4859f3b7aa14.json b/change/@fluentui-react-bf8f90c6-793f-49b5-baff-4859f3b7aa14.json
new file mode 100644
index 0000000000000..d6aef6fd6dee8
--- /dev/null
+++ b/change/@fluentui-react-bf8f90c6-793f-49b5-baff-4859f3b7aa14.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "fix(react): show TextField's themable password reveal button in Edge and hide the native ::-ms-reveal, and give it a resting forced-colors color",
+ "packageName": "@fluentui/react",
+ "email": "144495202+AKnassa@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/react/src/components/TextField/TextField.base.tsx b/packages/react/src/components/TextField/TextField.base.tsx
index aac2245018771..279aa1e945488 100644
--- a/packages/react/src/components/TextField/TextField.base.tsx
+++ b/packages/react/src/components/TextField/TextField.base.tsx
@@ -7,7 +7,6 @@ import {
DelayedRender,
getId,
getNativeProps,
- getWindow,
initializeComponentRef,
inputProperties,
isControlled,
@@ -680,16 +679,10 @@ let __browserNeedsRevealButton: boolean | undefined;
function _browserNeedsRevealButton() {
if (typeof __browserNeedsRevealButton !== 'boolean') {
- const win = getWindow();
-
- if (win?.navigator) {
- // Edge, Chromium Edge
- const isEdge = /Edg/.test(win.navigator.userAgent || '');
-
- __browserNeedsRevealButton = !(isIE11() || isEdge);
- } else {
- __browserNeedsRevealButton = true;
- }
+ // Only IE 11 keeps its unhideable native reveal button. Edge's native ::-ms-reveal is
+ // suppressed via the field styles so the themable, keyboard-accessible custom button can
+ // be shown instead (otherwise both would render).
+ __browserNeedsRevealButton = !isIE11();
}
return __browserNeedsRevealButton;
}
diff --git a/packages/react/src/components/TextField/TextField.styles.tsx b/packages/react/src/components/TextField/TextField.styles.tsx
index 20058d491eb13..5efc9677ac8f7 100644
--- a/packages/react/src/components/TextField/TextField.styles.tsx
+++ b/packages/react/src/components/TextField/TextField.styles.tsx
@@ -318,6 +318,14 @@ export function getStyles(props: ITextFieldStyleProps): ITextFieldStyles {
},
},
getPlaceholderStyles(placeholderStyles),
+ hasRevealButton && {
+ selectors: {
+ // Hide Edge's built-in reveal button since the custom one is shown instead
+ '::-ms-reveal': {
+ display: 'none',
+ },
+ },
+ },
multiline &&
!resizable && [
classNames.unresizable,
@@ -429,6 +437,9 @@ export function getStyles(props: ITextFieldStyleProps): ITextFieldStyles {
backgroundColor: 'transparent',
color: semanticColors.link,
selectors: {
+ [HighContrastSelector]: {
+ color: 'ButtonText',
+ },
':hover': {
outline: 0,
color: semanticColors.primaryButtonBackgroundHovered,
diff --git a/packages/react/src/components/TextField/TextField.test.tsx b/packages/react/src/components/TextField/TextField.test.tsx
index c54b5e0184331..502b2579886f6 100644
--- a/packages/react/src/components/TextField/TextField.test.tsx
+++ b/packages/react/src/components/TextField/TextField.test.tsx
@@ -246,6 +246,33 @@ describe('TextField basic props', () => {
userEvent.click(reveal);
expect(input.type).toEqual('password');
});
+ it('renders reveal password button in Edge (Edg UA)', () => {
+ const originalUAProp = Object.getOwnPropertyDescriptor(window.navigator, 'userAgent');
+ Object.defineProperty(window.navigator, 'userAgent', {
+ value:
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' +
+ 'Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0',
+ configurable: true,
+ });
+ // Reuse the already-loaded React inside the isolated module registry so hooks in the
+ // freshly-loaded component tree use the same dispatcher as the outer `render`
+ jest.doMock('react', () => React);
+ try {
+ jest.isolateModules(() => {
+ // Fresh module instance so the cached __browserNeedsRevealButton is recomputed
+ const { TextField: IsolatedTextField } = require('./TextField');
+ const { container } = render();
+ expect(container.querySelectorAll('.ms-TextField-reveal')).toHaveLength(1);
+ });
+ } finally {
+ jest.dontMock('react');
+ if (originalUAProp) {
+ Object.defineProperty(window.navigator, 'userAgent', originalUAProp);
+ } else {
+ delete (window.navigator as any).userAgent;
+ }
+ }
+ });
it('should not give an aria-labelledby if no label is provided', () => {
const { getByRole } = render();
const input = getByRole('textbox') as HTMLInputElement;
diff --git a/packages/react/src/components/TextField/TextField.types.ts b/packages/react/src/components/TextField/TextField.types.ts
index e583632178801..c7d719caaf18a 100644
--- a/packages/react/src/components/TextField/TextField.types.ts
+++ b/packages/react/src/components/TextField/TextField.types.ts
@@ -285,15 +285,16 @@ export interface ITextFieldProps extends React.AllHTMLAttributes