diff --git a/.changeset/nine-moles-sort.md b/.changeset/nine-moles-sort.md
new file mode 100644
index 0000000000..3e3d65dc45
--- /dev/null
+++ b/.changeset/nine-moles-sort.md
@@ -0,0 +1,5 @@
+---
+"@heroui/tabs": patch
+---
+
+fix unresponsive modal after switching tabs inside (#5543)
diff --git a/packages/components/tabs/__tests__/tabs.test.tsx b/packages/components/tabs/__tests__/tabs.test.tsx
index 9734e5c1f7..89fd7ec857 100644
--- a/packages/components/tabs/__tests__/tabs.test.tsx
+++ b/packages/components/tabs/__tests__/tabs.test.tsx
@@ -2,10 +2,12 @@ import type {UserEvent} from "@testing-library/user-event";
import type {TabsProps} from "../src";
import * as React from "react";
-import {act, render, fireEvent, within} from "@testing-library/react";
+import {act, render, fireEvent, within, waitFor} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {focus} from "@heroui/test-utils";
import {spy, shouldIgnoreReactWarning} from "@heroui/test-utils";
+import {Modal, ModalContent, ModalHeader, ModalBody, ModalFooter} from "@heroui/modal";
+import {Button} from "@heroui/button";
import {Tabs, Tab} from "../src";
@@ -435,4 +437,106 @@ describe("Tabs", () => {
expect(item2Click).toHaveBeenCalledTimes(2);
expect(tab2).toHaveAttribute("aria-selected", "true");
});
+
+ it("should allow reopening modal with tabs without blocking", async () => {
+ const TestComponent = () => {
+ const [isOpen, setIsOpen] = React.useState(false);
+
+ return (
+ <>
+
+
+
+ {(onClose) => (
+ <>
+ Test Modal with Tabs
+
+
+
+ Content for Tab 1
+
+
+ Content for Tab 2
+
+
+ Content for Tab 3
+
+
+
+
+
+
+ >
+ )}
+
+
+ >
+ );
+ };
+
+ const {getByTestId, getByRole, queryByRole} = render();
+
+ const openButton = getByTestId("open-modal-btn");
+
+ await act(async () => {
+ fireEvent.click(openButton);
+ });
+
+ await waitFor(() => {
+ const modal = getByRole("dialog");
+
+ expect(modal).toBeInTheDocument();
+ });
+
+ const tabButtons = getByRole("dialog").querySelectorAll('[role="tab"]');
+
+ expect(tabButtons).toHaveLength(3);
+
+ await act(async () => {
+ fireEvent.click(tabButtons[1]);
+ });
+
+ await waitFor(() => {
+ expect(tabButtons[1]).toHaveAttribute("aria-selected", "true");
+ });
+
+ const closeButton = getByTestId("close-modal-btn");
+
+ await act(async () => {
+ fireEvent.click(closeButton);
+ });
+
+ await waitFor(
+ () => {
+ expect(queryByRole("dialog")).not.toBeInTheDocument();
+ },
+ {timeout: 1000},
+ );
+
+ await act(async () => {
+ fireEvent.click(openButton);
+ });
+
+ await waitFor(() => {
+ const modal = getByRole("dialog");
+
+ expect(modal).toBeInTheDocument();
+ });
+
+ const newTabButtons = getByRole("dialog").querySelectorAll('[role="tab"]');
+
+ expect(newTabButtons).toHaveLength(3);
+
+ await act(async () => {
+ fireEvent.click(newTabButtons[2]);
+ });
+
+ await waitFor(() => {
+ expect(newTabButtons[2]).toHaveAttribute("aria-selected", "true");
+ });
+ });
});
diff --git a/packages/components/tabs/package.json b/packages/components/tabs/package.json
index 24fb54adca..8a28d438ad 100644
--- a/packages/components/tabs/package.json
+++ b/packages/components/tabs/package.json
@@ -61,6 +61,7 @@
"@heroui/input": "workspace:*",
"@heroui/test-utils": "workspace:*",
"@heroui/button": "workspace:*",
+ "@heroui/modal": "workspace:*",
"@heroui/shared-icons": "workspace:*",
"clean-package": "2.2.0",
"react": "18.3.0",
diff --git a/packages/components/tabs/src/tab.tsx b/packages/components/tabs/src/tab.tsx
index d3f68ad4ac..d346c545d3 100644
--- a/packages/components/tabs/src/tab.tsx
+++ b/packages/components/tabs/src/tab.tsx
@@ -76,6 +76,8 @@ const Tab = forwardRef<"button", TabItemProps>((props, ref) => {
rerender: true,
});
+ const isInModal = domRef?.current?.closest('[aria-modal="true"]') !== null;
+
const handleClick = () => {
if (!domRef?.current || !listRef?.current) return;
@@ -120,7 +122,7 @@ const Tab = forwardRef<"button", TabItemProps>((props, ref) => {
title={otherProps?.titleValue}
type={Component === "button" ? "button" : undefined}
>
- {isSelected && !disableAnimation && !disableCursorAnimation && isMounted ? (
+ {isSelected && !disableAnimation && !disableCursorAnimation && isMounted && !isInModal ? (
// use synchronous loading for domMax here
// since lazy loading produces different behaviour
diff --git a/packages/components/tabs/src/tabs.tsx b/packages/components/tabs/src/tabs.tsx
index 2d8c0ffb79..a1b289388b 100644
--- a/packages/components/tabs/src/tabs.tsx
+++ b/packages/components/tabs/src/tabs.tsx
@@ -21,6 +21,7 @@ const Tabs = forwardRef(function Tabs(
Component,
values,
state,
+ domRef,
destroyInactiveTabPanel,
getBaseProps,
getTabListProps,
@@ -32,7 +33,9 @@ const Tabs = forwardRef(function Tabs(
const layoutId = useId();
- const layoutGroupEnabled = !props.disableAnimation && !props.disableCursorAnimation;
+ const isInModal = domRef?.current?.closest('[aria-modal="true"]') !== null;
+
+ const layoutGroupEnabled = !props.disableAnimation && !props.disableCursorAnimation && !isInModal;
const tabsProps = {
state,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 20859ca7c0..cfcdceed94 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2811,6 +2811,9 @@ importers:
'@heroui/input':
specifier: workspace:*
version: link:../input
+ '@heroui/modal':
+ specifier: workspace:*
+ version: link:../modal
'@heroui/shared-icons':
specifier: workspace:*
version: link:../../utilities/shared-icons