diff --git a/apps/docs/content/components/listbox/index.ts b/apps/docs/content/components/listbox/index.ts
index 35c8dcd9c4..2f83ec0bd9 100644
--- a/apps/docs/content/components/listbox/index.ts
+++ b/apps/docs/content/components/listbox/index.ts
@@ -9,6 +9,8 @@ import description from "./description";
import sections from "./sections";
import customStyles from "./custom-styles";
import topContent from "./top-content";
+import virtualization from "./virtualization";
+import virtualizationTenThousand from "./virtualization-ten-thousand";
export const listboxContent = {
usage,
@@ -22,4 +24,6 @@ export const listboxContent = {
sections,
customStyles,
topContent,
+ virtualization,
+ virtualizationTenThousand,
};
diff --git a/apps/docs/content/components/listbox/virtualization-ten-thousand.raw.jsx b/apps/docs/content/components/listbox/virtualization-ten-thousand.raw.jsx
new file mode 100644
index 0000000000..f569bea6cc
--- /dev/null
+++ b/apps/docs/content/components/listbox/virtualization-ten-thousand.raw.jsx
@@ -0,0 +1,57 @@
+import {Listbox, ListboxItem} from "@nextui-org/react";
+
+const generateItems = (n) => {
+ const items = [
+ "Cat",
+ "Dog",
+ "Elephant",
+ "Lion",
+ "Tiger",
+ "Giraffe",
+ "Dolphin",
+ "Penguin",
+ "Zebra",
+ "Shark",
+ "Whale",
+ "Otter",
+ "Crocodile",
+ ];
+
+ const dataset = [];
+
+ for (let i = 0; i < n; i++) {
+ const item = items[i % items.length];
+
+ dataset.push({
+ label: `${item}${i}`,
+ value: `${item.toLowerCase()}${i}`,
+ description: "Sample description",
+ });
+ }
+
+ return dataset;
+};
+
+export default function App() {
+ const items = generateItems(1000);
+
+ return (
+
+
+ {items.map((item, index) => (
+
+ {item.label}
+
+ ))}
+
+
+ );
+}
diff --git a/apps/docs/content/components/listbox/virtualization-ten-thousand.ts b/apps/docs/content/components/listbox/virtualization-ten-thousand.ts
new file mode 100644
index 0000000000..1b8e486cb5
--- /dev/null
+++ b/apps/docs/content/components/listbox/virtualization-ten-thousand.ts
@@ -0,0 +1,9 @@
+import App from "./virtualization-ten-thousand.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/listbox/virtualization.raw.jsx b/apps/docs/content/components/listbox/virtualization.raw.jsx
new file mode 100644
index 0000000000..96ec2011b8
--- /dev/null
+++ b/apps/docs/content/components/listbox/virtualization.raw.jsx
@@ -0,0 +1,56 @@
+import {Listbox, ListboxItem} from "@nextui-org/react";
+const generateItems = (n) => {
+ const items = [
+ "Cat",
+ "Dog",
+ "Elephant",
+ "Lion",
+ "Tiger",
+ "Giraffe",
+ "Dolphin",
+ "Penguin",
+ "Zebra",
+ "Shark",
+ "Whale",
+ "Otter",
+ "Crocodile",
+ ];
+
+ const dataset = [];
+
+ for (let i = 0; i < n; i++) {
+ const item = items[i % items.length];
+
+ dataset.push({
+ label: `${item}${i}`,
+ value: `${item.toLowerCase()}${i}`,
+ description: "Sample description",
+ });
+ }
+
+ return dataset;
+};
+
+export default function App() {
+ const items = generateItems(1000);
+
+ return (
+
+
+ {items.map((item, index) => (
+
+ {item.label}
+
+ ))}
+
+
+ );
+}
diff --git a/apps/docs/content/components/listbox/virtualization.ts b/apps/docs/content/components/listbox/virtualization.ts
new file mode 100644
index 0000000000..e40cbd641f
--- /dev/null
+++ b/apps/docs/content/components/listbox/virtualization.ts
@@ -0,0 +1,9 @@
+import App from "./virtualization.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/docs/components/listbox.mdx b/apps/docs/content/docs/components/listbox.mdx
index 2c8eb66fc1..52f9b92575 100644
--- a/apps/docs/content/docs/components/listbox.mdx
+++ b/apps/docs/content/docs/components/listbox.mdx
@@ -151,6 +151,22 @@ function App() {
}
```
+### Virtualization
+
+Select supports virtualization, which allows efficient rendering of large lists by only rendering items that are visible in the viewport. You can enable virtualization by setting the `isVirtualized` prop to `true`.
+
+
+
+> **Note**: The virtualization strategy is based on the [@tanstack/react-virtual](https://tanstack.com/virtual/latest) package, which provides efficient rendering of large lists by only rendering items that are visible in the viewport.
+#### Ten Thousand Items
+
+Here's an example of using virtualization with 10,000 items.
+
+
+
## Slots
Listbox has 3 components with slots the base one `Listbox`, `ListboxItem` and `ListboxSection` components.
@@ -328,6 +344,18 @@ You can customize the `Listbox` items style by using the `itemClasses` prop and
type: "boolean",
description: "Whether keyboard navigation is circular.",
default: "false"
+ },
+ {
+ attribute: "isVirtualized",
+ type: "boolean",
+ description: "Whether to enable virtualization.",
+ default: "false"
+ },
+ {
+ attribute: "virtualization",
+ type: "Record<\"maxListboxHeight\" & \"itemHeight\", number>",
+ description: "Configuration for virtualization, optimizing rendering for large datasets. Required if isVirtualized is set to true.",
+ default: "-",
},
{
attribute: "hideEmptyContent",
diff --git a/packages/components/listbox/stories/listbox.stories.tsx b/packages/components/listbox/stories/listbox.stories.tsx
index 9f24e947a6..c670e57621 100644
--- a/packages/components/listbox/stories/listbox.stories.tsx
+++ b/packages/components/listbox/stories/listbox.stories.tsx
@@ -679,6 +679,59 @@ const CustomWithClassNamesTemplate = ({color, variant, disableAnimation, ...args
);
};
+interface LargeDatasetSchema {
+ label: string;
+ value: string;
+ description: string;
+}
+
+function generateLargeDataset(n: number): LargeDatasetSchema[] {
+ const dataset: LargeDatasetSchema[] = [];
+ const items = [
+ "Cat",
+ "Dog",
+ "Elephant",
+ "Lion",
+ "Tiger",
+ "Giraffe",
+ "Dolphin",
+ "Penguin",
+ "Zebra",
+ "Shark",
+ "Whale",
+ "Otter",
+ "Crocodile",
+ ];
+
+ for (let i = 0; i < n; i++) {
+ const item = items[i % items.length];
+
+ dataset.push({
+ label: `${item}${i}`,
+ value: `${item.toLowerCase()}${i}`,
+ description: "Sample description",
+ });
+ }
+
+ return dataset;
+}
+
+const LargeDatasetTemplate = (args: ListboxProps & {numItems: number}) => {
+ const largeDataset = generateLargeDataset(args.numItems);
+
+ return (
+
+
+ {largeDataset.map((item, index) => (
+
+ {item.label}
+
+ ))}
+
+
+ );
+};
+
export const Default = {
render: Template,
args: {
@@ -782,3 +835,55 @@ export const CustomWithClassNames = {
...defaultProps,
},
};
+
+export const OneThousandList = {
+ render: LargeDatasetTemplate,
+ args: {
+ ...defaultProps,
+ numItems: 1000,
+ isVirtualized: true,
+ virtualization: {
+ maxListboxHeight: 400,
+ itemHeight: 20,
+ },
+ },
+};
+
+export const TenThousandList = {
+ render: LargeDatasetTemplate,
+ args: {
+ ...defaultProps,
+ numItems: 10000,
+ isVirtualized: true,
+ virtualization: {
+ maxListboxHeight: 400,
+ itemHeight: 20,
+ },
+ },
+};
+
+export const CustomMaxListboxHeight = {
+ render: LargeDatasetTemplate,
+ args: {
+ ...defaultProps,
+ numItems: 1000,
+ isVirtualized: true,
+ virtualization: {
+ maxListboxHeight: 600,
+ itemHeight: 20,
+ },
+ },
+};
+
+export const CustomItemHeight = {
+ render: LargeDatasetTemplate,
+ args: {
+ ...defaultProps,
+ numItems: 1000,
+ isVirtualized: true,
+ virtualization: {
+ itemHeight: 40,
+ maxListboxHeight: 600,
+ },
+ },
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c65136c311..781634e8c3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -22124,7 +22124,7 @@ snapshots:
doctrine: 2.1.0
eslint: 7.32.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3