Commit 2a4adee7c6c for woocommerce

commit 2a4adee7c6ca787fd8e1f552708b049e4bb90137
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date:   Mon Sep 7 12:33:03 2026 +0100

    Restore WordPress defaults in the experimental Settings UI (#66867)

    * fix(settings): Restore a visible focus indicator on the shell tabs

    The shell tab styles set outline: none and box-shadow: none on :focus,
    leaving only a colour change as the focus indicator. The active-tab
    underline is bound to .is-active, not :focus, so keyboard users got no
    visible focus indicator on the tabs, violating WPDS focus guidance and
    WCAG 2.4.7.

    Add the standard WPDS focus ring on :focus-visible, drawn inset so the
    tab bar's overflow clipping keeps it visible. The ring uses the
    wpds-border-width-focus and wpds-color-stroke-focus tokens, which the
    settings embed already delivers through design-tokens.css.

    Refs WOOPRD-3569

    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

    * chore: Add changelog for the shell tab focus ring fix

    Refs WOOPRD-3569

    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

    * fix(settings): preserve native WordPress tab focus styles

    * fix(settings): preserve independently registered unload handlers

    * fix(settings): identify current navigation links

    * refactor(settings): use native WordPress modal styling

    * chore(settings): update changelogs for native behaviour fixes

    * test(settings-ui): cover current navigation links

    * fix(settings-ui): align active navigation underlines

    ---------

    Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

diff --git a/packages/js/settings-ui/changelog/fix-preserve-wordpress-settings-behaviour b/packages/js/settings-ui/changelog/fix-preserve-wordpress-settings-behaviour
new file mode 100644
index 00000000000..d9d78444b57
--- /dev/null
+++ b/packages/js/settings-ui/changelog/fix-preserve-wordpress-settings-behaviour
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Identify current navigation links and preserve independently registered unload handlers in the Settings UI.
diff --git a/packages/js/settings-ui/src/settings-ui-page.tsx b/packages/js/settings-ui/src/settings-ui-page.tsx
index 86a13f8a1be..f8e45a3c41e 100644
--- a/packages/js/settings-ui/src/settings-ui-page.tsx
+++ b/packages/js/settings-ui/src/settings-ui-page.tsx
@@ -94,10 +94,6 @@ const getBadgeIntent = ( intent?: string ): SettingsUIShellBadgeIntent =>
 const getSaveStrategy = ( schema: SettingsUISchema ): SettingsUISaveStrategy =>
 	schema.save || { adapter: 'form_post' };

-const clearLegacyFormPrompt = () => {
-	window.onbeforeunload = null;
-};
-
 const setFormPostRedirectInput = ( form: HTMLFormElement, href: string ) => {
 	let redirectInput = form.querySelector< HTMLInputElement >(
 		`input[name="${ FORM_POST_REDIRECT_INPUT_NAME }"]`
@@ -383,6 +379,9 @@ const ShellHeader = ( {
 											? 'wc-settings-ui-shell__tab is-active'
 											: 'wc-settings-ui-shell__tab'
 									}
+									aria-current={
+										item.active ? 'page' : undefined
+									}
 									href={ item.href }
 									key={ item.id }
 								>
@@ -407,6 +406,9 @@ const ShellHeader = ( {
 											? 'wc-settings-ui-shell__tab is-active'
 											: 'wc-settings-ui-shell__tab'
 									}
+									aria-current={
+										item.active ? 'page' : undefined
+									}
 									href={ item.href }
 									key={ item.id }
 								>
@@ -480,7 +482,6 @@ export const SettingsUIPage = ( {

 	const allowNavigation = useCallback( () => {
 		allowNavigationRef.current = true;
-		clearLegacyFormPrompt();
 	}, [] );

 	const submitSettingsForm = useCallback(
diff --git a/packages/js/settings-ui/src/test/html-rendering.test.tsx b/packages/js/settings-ui/src/test/html-rendering.test.tsx
index 61fbd0e9296..f9fce857f44 100644
--- a/packages/js/settings-ui/src/test/html-rendering.test.tsx
+++ b/packages/js/settings-ui/src/test/html-rendering.test.tsx
@@ -578,7 +578,7 @@ describe( 'settings HTML rendering', () => {
 		}
 	} );

-	it( 'submits form-post saves with the pending destination', () => {
+	it( 'submits form-post saves without clearing another unload handler', () => {
 		const requestSubmit = jest
 			.spyOn( HTMLFormElement.prototype, 'requestSubmit' )
 			.mockImplementation( () => undefined );
@@ -616,6 +616,10 @@ describe( 'settings HTML rendering', () => {
 			<SettingsUIPage schema={ schema } />
 		);

+		const previousBeforeUnload = window.onbeforeunload;
+		const otherBeforeUnload = jest.fn();
+		window.onbeforeunload = otherBeforeUnload;
+
 		try {
 			const input = container.querySelector(
 				'input:not([type="hidden"])'
@@ -665,7 +669,15 @@ describe( 'settings HTML rendering', () => {
 			expect( requestSubmit ).toHaveBeenCalledWith(
 				container.querySelector( '.woocommerce-save-button' )
 			);
+			expect( window.onbeforeunload ).toBe( otherBeforeUnload );
+			const beforeUnloadEvent = new Event( 'beforeunload', {
+				cancelable: true,
+			} );
+			window.dispatchEvent( beforeUnloadEvent );
+			expect( otherBeforeUnload ).toHaveBeenCalledTimes( 1 );
+			expect( beforeUnloadEvent.defaultPrevented ).toBe( false );
 		} finally {
+			window.onbeforeunload = previousBeforeUnload;
 			act( () => root.unmount() );
 			form.remove();
 			requestSubmit.mockRestore();
diff --git a/packages/js/settings-ui/src/test/shell-header-visibility.test.tsx b/packages/js/settings-ui/src/test/shell-header-visibility.test.tsx
index ae195ebfccc..4ef900759dd 100644
--- a/packages/js/settings-ui/src/test/shell-header-visibility.test.tsx
+++ b/packages/js/settings-ui/src/test/shell-header-visibility.test.tsx
@@ -75,6 +75,49 @@ describe( 'settings UI shell header visibility', () => {
 		document.body.innerHTML = '';
 	} );

+	it.each( [
+		[ 'navigation', 'Settings pages' ],
+		[ 'sectionNavigation', 'Settings sections' ],
+	] )(
+		'marks only the active link as current in %s',
+		( navigation, label ) => {
+			const { container, root } = renderElement(
+				<SettingsUIPage
+					schema={ baseSchema( {
+						[ navigation ]: [
+							{ id: 'first', label: 'First', href: '#first' },
+							{
+								id: 'current',
+								label: 'Current',
+								href: '#current',
+								active: true,
+							},
+							{
+								id: 'last',
+								label: 'Last',
+								href: '#last',
+								active: false,
+							},
+						],
+					} ) }
+					page="test_page"
+				/>
+			);
+
+			const links = container.querySelectorAll(
+				`nav[aria-label="${ label }"] a`
+			);
+			expect(
+				Array.from( links, ( link ) =>
+					link.getAttribute( 'aria-current' )
+				)
+			).toEqual( [ null, 'page', null ] );
+
+			act( () => root.unmount() );
+			container.remove();
+		}
+	);
+
 	it( 'labels the shell region with a fallback when the schema has no title', () => {
 		const schema = baseSchema( {} );
 		delete schema.title;
diff --git a/plugins/woocommerce/changelog/fix-settings-ui-shell-tab-focus-ring b/plugins/woocommerce/changelog/fix-settings-ui-shell-tab-focus-ring
new file mode 100644
index 00000000000..7fc08951469
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-settings-ui-shell-tab-focus-ring
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Restore WordPress focus and modal styling, identify current navigation links, and preserve other plugins' navigation warnings in the experimental Settings UI.
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
index f7d6a9debe6..566cefc56cc 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
@@ -160,7 +160,8 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
 		display: flex;
 		margin: 0 !important;
 		overflow-x: auto;
-		padding: 0 48px;
+		// Keep WordPress focus rings inside the scrolling tab bar.
+		padding: var(--wpds-dimension-padding-xs) 48px;
 		width: 100%;
 	}

@@ -180,9 +181,7 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
 		&:focus,
 		&:hover,
 		&:active {
-			box-shadow: none;
 			color: var(--wpds-color-foreground-interactive-brand);
-			outline: none;
 		}

 		&.is-active {
@@ -191,7 +190,7 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
 			&::after {
 				background: var(--wpds-color-stroke-interactive-brand);
 				border-radius: 999px;
-				bottom: 0;
+				inset-block-end: calc(-1 * var(--wpds-dimension-padding-xs));
 				content: "";
 				height: var(--wpds-border-width-sm);
 				left: 0;
@@ -214,29 +213,6 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
 		max-width: var(--wpds-dimension-surface-width-xl);
 	}

-	.wc-settings-ui__unsaved-changes-modal {
-		.components-modal__header {
-			min-height: 72px;
-			padding: 24px 32px 8px;
-		}
-
-		.components-modal__header-heading {
-			font-size: 20px;
-			font-weight: 500;
-			line-height: 24px;
-		}
-
-		.components-modal__content {
-			padding: 4px 32px 32px;
-		}
-
-		p {
-			font-size: 13px;
-			line-height: 20px;
-			margin: 0;
-		}
-	}
-
 	.wc-settings-ui__unsaved-changes-actions {
 		display: flex;
 		gap: 8px;