diff --git a/src/internal/visual-mode/__tests__/use-visual-mode-performance.test.tsx b/src/internal/visual-mode/__tests__/use-visual-mode-performance.test.tsx
new file mode 100644
index 0000000..635c425
--- /dev/null
+++ b/src/internal/visual-mode/__tests__/use-visual-mode-performance.test.tsx
@@ -0,0 +1,328 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import React, { useLayoutEffect, useRef, useState } from 'react';
+import { isMotionDisabled, useCurrentMode, useDensityMode, useReducedMotion } from '../index';
+import { render, screen } from '@testing-library/react';
+import { mutate } from './utils';
+
+function ModeRender({ testId }: { testId: string }) {
+ const ref = useRef(null);
+ const colorMode = useCurrentMode(ref);
+ const densityMode = useDensityMode(ref);
+ return (
+
+ {colorMode}-{densityMode}
+
+ );
+}
+
+/** Renders `count` detectors under a shared ancestor chain of the given depth. */
+function ManyDetectors({ count, depth }: { count: number; depth: number }) {
+ let tree = (
+ <>
+ {Array.from({ length: count }, (_, i) => (
+
+ ))}
+ >
+ );
+ for (let i = 0; i < depth; i++) {
+ tree = {tree}
;
+ }
+ return tree;
+}
+
+function spyOnClassListReads() {
+ const spy = jest.fn();
+ const descriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'classList')!;
+ jest.spyOn(Element.prototype, 'classList', 'get').mockImplementation(function (this: Element) {
+ spy();
+ return descriptor.get!.call(this);
+ });
+ return spy;
+}
+
+afterEach(() => {
+ jest.restoreAllMocks();
+ // Reset shared state explicitly rather than in each test, so that one failing assertion
+ // cannot leave a mode class behind and cascade into unrelated failures.
+ document.documentElement.className = '';
+ document.body.className = '';
+ document.body.replaceChildren();
+});
+
+describe('mode detection cost', () => {
+ test('does not wake subscribers for attribute changes that cannot affect a mode', async () => {
+ render();
+
+ const reads = spyOnClassListReads();
+ await mutate(() => document.body.setAttribute('data-awsui-focus-visible', 'true'));
+ expect(reads).not.toHaveBeenCalled();
+
+ await mutate(() => document.body.removeAttribute('data-awsui-focus-visible'));
+ expect(reads).not.toHaveBeenCalled();
+ });
+
+ test('shares ancestor lookups between subscribers within a single flush', async () => {
+ const depth = 20;
+ async function countReadsForOneFlush(detectorCount: number) {
+ const { container, unmount } = render();
+ const reads = spyOnClassListReads();
+ await mutate(() => container.classList.add('unrelated-class'));
+ const total = reads.mock.calls.length;
+ jest.restoreAllMocks();
+ unmount();
+ return total;
+ }
+
+ const withForty = await countReadsForOneFlush(40);
+ const withEighty = await countReadsForOneFlush(80);
+
+ // Assert the marginal cost of one more subscriber, which is what memoization bounds.
+ // Each additional subscriber should only read its own element's classList, once per
+ // detected mode, because its ancestors were already resolved by an earlier subscriber.
+ // Without memoization each one re-walks the shared chain instead, so the marginal cost
+ // would scale with `depth`.
+ const marginalReadsPerSubscriber = (withEighty - withForty) / 40;
+ expect(marginalReadsPerSubscriber).toBeLessThan(depth / 2);
+ });
+
+ test('does not wake subscribers for node insertions that move no subscriber', async () => {
+ render();
+ const unrelated = document.createElement('div');
+ unrelated.appendChild(document.createElement('span'));
+ document.body.appendChild(unrelated);
+ await mutate(() => undefined);
+
+ const reads = spyOnClassListReads();
+ await mutate(() => unrelated.appendChild(document.createElement('span')));
+ expect(reads).not.toHaveBeenCalled();
+
+ await mutate(() => unrelated.firstElementChild!.remove());
+ expect(reads).not.toHaveBeenCalled();
+ });
+
+ test('wakes subscribers when a childList change moves one of them', async () => {
+ const host = document.createElement('div');
+ document.body.appendChild(host);
+ render(, { container: host });
+ await mutate(() => undefined);
+
+ const reads = spyOnClassListReads();
+ // The subscribers sit below `host`, so re-parenting it changes their ancestor chains.
+ const newParent = document.createElement('div');
+ document.body.appendChild(newParent);
+ await mutate(() => newParent.appendChild(host));
+ expect(reads).toHaveBeenCalled();
+ });
+
+ test('wakes a subscriber inside a foreignObject when its svg is moved', async () => {
+ const svgNamespace = 'http://www.w3.org/2000/svg';
+ const svg = document.createElementNS(svgNamespace, 'svg');
+ const foreignObject = document.createElementNS(svgNamespace, 'foreignObject');
+ const host = document.createElement('div');
+ foreignObject.appendChild(host);
+ svg.appendChild(foreignObject);
+ document.body.appendChild(svg);
+ const darkSubtree = document.createElement('div');
+ darkSubtree.className = 'awsui-dark-mode';
+ document.body.appendChild(darkSubtree);
+
+ render(, { container: host });
+ expect(screen.getByTestId('detector')).toHaveTextContent('light-comfortable');
+
+ // The moved node is the