diff --git a/.changeset/mean-parrots-cheat.md b/.changeset/mean-parrots-cheat.md
new file mode 100644
index 0000000000..90ff415959
--- /dev/null
+++ b/.changeset/mean-parrots-cheat.md
@@ -0,0 +1,6 @@
+---
+"@nextui-org/tabs": patch
+"@nextui-org/theme": patch
+---
+
+Add `destroyInactiveTabPanel` prop for Tabs component (#1562)
diff --git a/apps/docs/content/docs/components/tabs.mdx b/apps/docs/content/docs/components/tabs.mdx
index 63718e7358..3e283f55e4 100644
--- a/apps/docs/content/docs/components/tabs.mdx
+++ b/apps/docs/content/docs/components/tabs.mdx
@@ -274,18 +274,19 @@ You can customize the `Tabs` component by passing custom Tailwind CSS classes to
### Tab Props
-| Attribute | Type | Description | Default |
-| --------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
-| children\* | `ReactNode` | The content of the tab. | - |
-| title | `ReactNode` | The title of the tab. | - |
-| titleValue | `string` | A string representation of the item's contents. Use this when the `title` is not readable. | - |
-| href | `string` | A URL to link to. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href). | - |
-| target | `HTMLAttributeAnchorTarget` | The target window for the link. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target). | - |
-| rel | `string` | The relationship between the linked resource and the current page. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel). | - |
-| download | `boolean` \| `string` | Causes the browser to download the linked URL. A string may be provided to suggest a file name. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download). | - |
-| ping | `string` | A space-separated list of URLs to ping when the link is followed. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#ping). | - |
-| referrerPolicy | `HTMLAttributeReferrerPolicy` | How much of the referrer to send when following the link. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#referrerpolicy). | - |
-| shouldSelectOnPressUp | `boolean` | Whether the tab selection should occur on press up instead of press down. | - |
+| Attribute | Type | Description | Default |
+|-------------------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
+| children\* | `ReactNode` | The content of the tab. | - |
+| title | `ReactNode` | The title of the tab. | - |
+| titleValue | `string` | A string representation of the item's contents. Use this when the `title` is not readable. | - |
+| href | `string` | A URL to link to. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href). | - |
+| target | `HTMLAttributeAnchorTarget` | The target window for the link. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target). | - |
+| rel | `string` | The relationship between the linked resource and the current page. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel). | - |
+| download | `boolean` \| `string` | Causes the browser to download the linked URL. A string may be provided to suggest a file name. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download). | - |
+| ping | `string` | A space-separated list of URLs to ping when the link is followed. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#ping). | - |
+| referrerPolicy | `HTMLAttributeReferrerPolicy` | How much of the referrer to send when following the link. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#referrerpolicy). | - |
+| shouldSelectOnPressUp | `boolean` | Whether the tab selection should occur on press up instead of press down. | - |
+| destroyInactiveTabPanel | `boolean` | Whether to destroy inactive tab panel when switching tabs. Inactive tab panels are inert and cannot be interacted with. | `true` |
#### Motion Props
diff --git a/packages/components/tabs/__tests__/tabs.test.tsx b/packages/components/tabs/__tests__/tabs.test.tsx
index a5117c5d6a..ff87798522 100644
--- a/packages/components/tabs/__tests__/tabs.test.tsx
+++ b/packages/components/tabs/__tests__/tabs.test.tsx
@@ -318,4 +318,40 @@ describe("Tabs", () => {
expect(tabWrapper).toHaveAttribute("data-placement", "top");
expect(tabWrapper).toHaveAttribute("data-vertical", "horizontal");
});
+
+ test("should destory inactive tab panels", () => {
+ const {container} = render(
+
+
+ Content 1
+
+
+ Content 2
+
+
+ Content 3
+
+ ,
+ );
+
+ expect(container.querySelectorAll("[data-slot='panel']")).toHaveLength(1);
+ });
+
+ test("should destory inactive tab panels", () => {
+ const {container} = render(
+
+
+ Content 1
+
+
+ Content 2
+
+
+ Content 3
+
+ ,
+ );
+
+ expect(container.querySelectorAll("[data-slot='panel']")).toHaveLength(3);
+ });
});
diff --git a/packages/components/tabs/src/tab-panel.tsx b/packages/components/tabs/src/tab-panel.tsx
index 58968cf97f..fe51d65efb 100644
--- a/packages/components/tabs/src/tab-panel.tsx
+++ b/packages/components/tabs/src/tab-panel.tsx
@@ -1,5 +1,6 @@
import type {AriaTabPanelProps} from "@react-aria/tabs";
+import {Key} from "@react-types/shared";
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/react-utils";
import {clsx} from "@nextui-org/shared-utils";
@@ -10,6 +11,15 @@ import {useFocusRing} from "@react-aria/focus";
import {ValuesType} from "./use-tabs";
interface Props extends HTMLNextUIProps<"div"> {
+ /**
+ * Whether to destroy inactive tab panel when switching tabs.
+ * Inactive tab panels are inert and cannot be interacted with.
+ */
+ destroyInactiveTabPanel: boolean;
+ /**
+ * The current tab key.
+ */
+ tabKey: Key;
/**
* The tab list state.
*/
@@ -30,12 +40,15 @@ export type TabPanelProps = Props & AriaTabPanelProps;
* @internal
*/
const TabPanel = forwardRef<"div", TabPanelProps>((props, ref) => {
- const {as, state, className, slots, classNames, ...otherProps} = props;
+ const {as, tabKey, destroyInactiveTabPanel, state, className, slots, classNames, ...otherProps} =
+ props;
const Component = as || "div";
+
const domRef = useDOMRef(ref);
const {tabPanelProps} = useTabPanel(props, state, domRef);
+
const {focusProps, isFocused, isFocusVisible} = useFocusRing();
const selectedItem = state.selectedItem;
@@ -44,7 +57,9 @@ const TabPanel = forwardRef<"div", TabPanelProps>((props, ref) => {
const tabPanelStyles = clsx(classNames?.panel, className, selectedItem?.props?.className);
- if (!content) {
+ const isSelected = tabKey === selectedItem?.key;
+
+ if (!content || (!isSelected && destroyInactiveTabPanel)) {
return null;
}
@@ -53,7 +68,9 @@ const TabPanel = forwardRef<"div", TabPanelProps>((props, ref) => {
ref={domRef}
data-focus={isFocused}
data-focus-visible={isFocusVisible}
- {...mergeProps(tabPanelProps, focusProps, otherProps)}
+ data-inert={!isSelected ? "true" : undefined}
+ inert={!isSelected ? "true" : undefined}
+ {...(isSelected && mergeProps(tabPanelProps, focusProps, otherProps))}
className={slots.panel?.({class: tabPanelStyles})}
data-slot="panel"
>
diff --git a/packages/components/tabs/src/tabs.tsx b/packages/components/tabs/src/tabs.tsx
index a4c618ac82..e22d48ef28 100644
--- a/packages/components/tabs/src/tabs.tsx
+++ b/packages/components/tabs/src/tabs.tsx
@@ -9,7 +9,15 @@ import TabPanel from "./tab-panel";
interface Props extends UseTabsProps {}
function Tabs(props: Props, ref: ForwardedRef) {
- const {Component, values, state, getBaseProps, getTabListProps, getWrapperProps} = useTabs({
+ const {
+ Component,
+ values,
+ state,
+ destroyInactiveTabPanel,
+ getBaseProps,
+ getTabListProps,
+ getWrapperProps,
+ } = useTabs({
...props,
ref,
});
@@ -41,12 +49,18 @@ function Tabs(props: Props, ref: ForwardedRef{tabs} : tabs}
-
+ {[...state.collection].map((item) => {
+ return (
+
+ );
+ })}
>
);
diff --git a/packages/components/tabs/src/use-tabs.ts b/packages/components/tabs/src/use-tabs.ts
index aaea6f4c12..d3a39e5849 100644
--- a/packages/components/tabs/src/use-tabs.ts
+++ b/packages/components/tabs/src/use-tabs.ts
@@ -57,6 +57,11 @@ export interface Props extends Omit {
* @default false
*/
isVertical?: boolean;
+ /**
+ * Whether to destroy inactive tab panel when switching tabs. Inactive tab panels are inert and cannot be interacted with.
+ * @default true
+ */
+ destroyInactiveTabPanel?: boolean;
}
export type UseTabsProps = Props &
@@ -90,6 +95,7 @@ export function useTabs(originalProps: UseTabsProps) {
motionProps,
isVertical = false,
shouldSelectOnPressUp = true,
+ destroyInactiveTabPanel = true,
...otherProps
} = props;
@@ -182,6 +188,7 @@ export function useTabs(originalProps: UseTabsProps) {
domRef,
state,
values,
+ destroyInactiveTabPanel,
getBaseProps,
getTabListProps,
getWrapperProps,
diff --git a/packages/core/theme/src/components/tabs.ts b/packages/core/theme/src/components/tabs.ts
index 3e126deb7f..22806989ac 100644
--- a/packages/core/theme/src/components/tabs.ts
+++ b/packages/core/theme/src/components/tabs.ts
@@ -68,6 +68,7 @@ const tabs = tv({
"py-3",
"px-1",
"outline-none",
+ "data-[inert=true]:hidden",
// focus ring
...dataFocusVisibleClasses,
],