diff --git a/src/dom/__tests__/node-belongs.test.ts b/src/dom/__tests__/node-belongs.test.ts
new file mode 100644
index 0000000..950597c
--- /dev/null
+++ b/src/dom/__tests__/node-belongs.test.ts
@@ -0,0 +1,67 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { nodeBelongs } from '../node-belongs';
+
+describe('nodeBelongs', () => {
+ let div: HTMLDivElement;
+
+ beforeEach(() => {
+ div = document.createElement('div');
+ document.documentElement.appendChild(div);
+ });
+
+ afterEach(() => document.documentElement.removeChild(div));
+
+ test('returns "true", when the node and the container are the same element', () => {
+ div.innerHTML = `
+
+ `;
+ expect(nodeBelongs(div.querySelector('#container1'), div.querySelector('#container1') as Node)).toBe(true);
+ });
+
+ test('returns "true", when the node is descendant from the container', () => {
+ div.innerHTML = `
+
+
+
+ `;
+ expect(nodeBelongs(div.querySelector('#container1'), div.querySelector('#node') as Node)).toBe(true);
+ });
+
+ test('returns "false", when the node is not a child of the container', () => {
+ div.innerHTML = `
+
+
+ `;
+ expect(nodeBelongs(div.querySelector('#container1'), div.querySelector('#node') as Node)).toBe(false);
+ });
+
+ test('returns "true" when node belongs to a portal issued from within the container', () => {
+ div.innerHTML = `
+
+
+
+
+
+
+ `;
+ expect(nodeBelongs(div.querySelector('#container1'), div.querySelector('#node') as Node)).toBe(true);
+ });
+
+ test('returns "true" when the node is a descendant of the container, both inside a portal', () => {
+ div.innerHTML = `
+
+
+
+
+
+
+ `;
+ expect(nodeBelongs(div.querySelector('#container1'), div.querySelector('#node') as Node)).toBe(true);
+ });
+
+ test('returns false if target is not a node', () => {
+ expect(nodeBelongs(div.querySelector('#container1'), {} as any)).toBe(false);
+ });
+});
diff --git a/src/dom/node-belongs.ts b/src/dom/node-belongs.ts
new file mode 100644
index 0000000..5ffc3ba
--- /dev/null
+++ b/src/dom/node-belongs.ts
@@ -0,0 +1,29 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { findUpUntil, nodeContains } from '.';
+
+import { isHTMLElement, isNode } from './element-types';
+
+/**
+ * Checks whether the given node (target) belongs to the container.
+ * The function is similar to nodeContains but also accounts for dropdowns with expandToViewport=true.
+ *
+ * @param container Container node
+ * @param target Node that is checked to be a descendant of the container
+ */
+export function nodeBelongs(container: Node | null, target: Node | EventTarget | null): boolean {
+ if (!isNode(target)) {
+ return false;
+ }
+ const portal = findUpUntil(
+ target as HTMLElement,
+ node => node === container || (isHTMLElement(node) && !!node.dataset.awsuiReferrerId)
+ );
+ if (portal && portal === container) {
+ // We found the container as a direct ancestor without a portal
+ return true;
+ }
+ const referrer = isHTMLElement(portal) ? document.getElementById(portal.dataset.awsuiReferrerId ?? '') : null;
+ return referrer ? nodeContains(container, referrer) : nodeContains(container, target);
+}
diff --git a/src/internal/focus-lock-utils/utils.ts b/src/internal/focus-lock-utils/utils.ts
new file mode 100644
index 0000000..d4c11eb
--- /dev/null
+++ b/src/internal/focus-lock-utils/utils.ts
@@ -0,0 +1,48 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// Credits to
+// https://github.com/theKashey/focus-lock/blob/33f8b4bd9675d2605b15e2e4015b77fe35fbd6d0/src/utils/tabbables.ts
+const tabbables = [
+ 'button:enabled',
+ 'select:enabled',
+ 'textarea:enabled',
+ 'input:enabled',
+
+ 'a[href]',
+ 'area[href]',
+
+ 'summary',
+ 'iframe',
+ 'object',
+ 'embed',
+
+ 'audio[controls]',
+ 'video[controls]',
+
+ '[tabindex]',
+ '[contenteditable]',
+ '[autofocus]',
+].join(',');
+
+export function isFocusable(element: HTMLElement): boolean {
+ return element.matches(tabbables);
+}
+
+export function getAllFocusables(container: HTMLElement): HTMLElement[] {
+ return Array.prototype.slice.call(container.querySelectorAll(tabbables));
+}
+
+function getFocusables(container: HTMLElement): HTMLElement[] {
+ return getAllFocusables(container).filter((element: HTMLElement) => element.tabIndex !== -1);
+}
+
+export function getFirstFocusable(container: HTMLElement): null | HTMLElement {
+ const focusables = getFocusables(container);
+ return focusables[0] ?? null;
+}
+
+export function getLastFocusable(container: HTMLElement): null | HTMLElement {
+ const focusables = getFocusables(container);
+ return focusables[focusables.length - 1] ?? null;
+}
diff --git a/src/internal/single-tab-stop/__tests__/context.test.tsx b/src/internal/single-tab-stop/__tests__/context.test.tsx
new file mode 100644
index 0000000..7b812c0
--- /dev/null
+++ b/src/internal/single-tab-stop/__tests__/context.test.tsx
@@ -0,0 +1,98 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import React, { useRef } from 'react';
+import { render, act } from '@testing-library/react';
+
+import { SingleTabStopNavigationContext, useSingleTabStopNavigation } from '../';
+import { renderWithSingleTabStopNavigation } from './utils';
+
+function Button(props: React.HTMLAttributes) {
+ const buttonRef = useRef(null);
+ const { tabIndex } = useSingleTabStopNavigation(buttonRef, { tabIndex: props.tabIndex });
+ return ;
+}
+
+test('subscribed components can be rendered outside single tab stop navigation context', () => {
+ render();
+ expect(document.querySelector('button')).not.toHaveAttribute('tabIndex');
+});
+
+test('does not override tab index when keyboard navigation is not active', () => {
+ renderWithSingleTabStopNavigation(, { navigationActive: false });
+ expect(document.querySelector('#button')).not.toHaveAttribute('tabIndex');
+});
+
+test('does not override tab index for suppressed elements', () => {
+ const { setCurrentTarget } = renderWithSingleTabStopNavigation(
+