diff --git a/frontend/src/metabase/components/VirtualizedList.jsx b/frontend/src/metabase/components/VirtualizedList.tsx
similarity index 69%
rename from frontend/src/metabase/components/VirtualizedList.jsx
rename to frontend/src/metabase/components/VirtualizedList.tsx
index 7f8104e36f00997a64310e03d76f5bf390d2ea27..acd498d2a2eafdc8c7b5dde432e9940d44a7f6c0 100644
--- a/frontend/src/metabase/components/VirtualizedList.jsx
+++ b/frontend/src/metabase/components/VirtualizedList.tsx
@@ -1,8 +1,20 @@
-/* eslint-disable react/prop-types */
 import React from "react";
 import { List, WindowScroller, AutoSizer } from "react-virtualized";
 
-function VirtualizedList({ items, rowHeight, renderItem, scrollElement }) {
+export interface VirtualizedListProps<Item = unknown>
+  extends React.HTMLProps<HTMLUListElement> {
+  items: Item[];
+  rowHeight: number;
+  renderItem: (props: { item: Item; index: number }) => React.ReactNode;
+  scrollElement?: HTMLElement | null;
+}
+
+function VirtualizedList<Item>({
+  items,
+  rowHeight,
+  renderItem,
+  scrollElement,
+}: VirtualizedListProps<Item>) {
   const rowRenderer = React.useCallback(
     ({ index, key, style }) => (
       <div key={key} style={style}>
@@ -15,7 +27,7 @@ function VirtualizedList({ items, rowHeight, renderItem, scrollElement }) {
   const renderScrollComponent = React.useCallback(
     ({ width }) => {
       return (
-        <WindowScroller scrollElement={scrollElement}>
+        <WindowScroller scrollElement={scrollElement || undefined}>
           {({ height, isScrolling, scrollTop }) => (
             <List
               autoHeight
diff --git a/frontend/src/metabase/containers/DataPicker/CardPicker/CardPicker.styled.tsx b/frontend/src/metabase/containers/DataPicker/CardPicker/CardPicker.styled.tsx
index 5bb3ffb3db8ab4bdc3032ee8c2efa6fe9ff9a7fa..4c5897c86f1578db3060a45c41ca92f82dd8253f 100644
--- a/frontend/src/metabase/containers/DataPicker/CardPicker/CardPicker.styled.tsx
+++ b/frontend/src/metabase/containers/DataPicker/CardPicker/CardPicker.styled.tsx
@@ -1,8 +1,6 @@
 import styled from "@emotion/styled";
 
-import SelectList from "metabase/components/SelectList";
-
-export const StyledSelectList = styled(SelectList)`
+export const ListContainer = styled.div`
   width: 100%;
   padding-left: 1rem;
 `;
diff --git a/frontend/src/metabase/containers/DataPicker/CardPicker/CardPickerView.tsx b/frontend/src/metabase/containers/DataPicker/CardPicker/CardPickerView.tsx
index b301fd7e472bdf40d46edf8d10a00f952635af93..cb7a60925e7ace19c3923a299c99f37241828b63 100644
--- a/frontend/src/metabase/containers/DataPicker/CardPicker/CardPickerView.tsx
+++ b/frontend/src/metabase/containers/DataPicker/CardPicker/CardPickerView.tsx
@@ -1,8 +1,6 @@
 import React, { useCallback, useMemo } from "react";
 import _ from "underscore";
 
-import SelectList from "metabase/components/SelectList";
-
 import { canonicalCollectionId } from "metabase/collections/utils";
 
 import type { ITreeNodeItem } from "metabase/components/tree/types";
@@ -13,9 +11,10 @@ import type { DataPickerSelectedItem, VirtualTable } from "../types";
 
 import EmptyState from "../EmptyState";
 import LoadingState from "../LoadingState";
+import VirtualizedSelectList from "../VirtualizedSelectList";
 import PanePicker from "../PanePicker";
 
-import { StyledSelectList } from "./CardPicker.styled";
+import { ListContainer } from "./CardPicker.styled";
 
 type TargetModel = "model" | "question";
 
@@ -55,7 +54,7 @@ function TableSelectListItem({
   onSelect: (id: Table["id"]) => void;
 }) {
   return (
-    <SelectList.Item
+    <VirtualizedSelectList.Item
       id={table.id}
       name={table.display_name}
       isSelected={isSelected}
@@ -63,7 +62,7 @@ function TableSelectListItem({
       onSelect={onSelect}
     >
       {table.display_name}
-    </SelectList.Item>
+    </VirtualizedSelectList.Item>
   );
 }
 
@@ -104,7 +103,7 @@ function CardPickerView({
   );
 
   const renderVirtualTable = useCallback(
-    (table: VirtualTable) => (
+    ({ item: table }: { item: VirtualTable }) => (
       <TableSelectListItem
         key={table.id}
         table={table}
@@ -130,9 +129,14 @@ function CardPickerView({
       ) : isEmpty ? (
         <EmptyState />
       ) : (
-        <StyledSelectList>
-          {virtualTables?.map?.(renderVirtualTable)}
-        </StyledSelectList>
+        <ListContainer>
+          {Array.isArray(virtualTables) && (
+            <VirtualizedSelectList<VirtualTable>
+              items={virtualTables}
+              renderItem={renderVirtualTable}
+            />
+          )}
+        </ListContainer>
       )}
     </PanePicker>
   );
diff --git a/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPicker.styled.tsx b/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPicker.styled.tsx
index 98464679d166be1285e4109fc3d4c94bac1ac4b8..fb0f876d87113cfa399abe591eee7f75c672c76e 100644
--- a/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPicker.styled.tsx
+++ b/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPicker.styled.tsx
@@ -1,8 +1,6 @@
 import styled from "@emotion/styled";
 
-import SelectList from "metabase/components/SelectList";
-
-export const StyledSelectList = styled(SelectList)`
+export const ListContainer = styled.div`
   width: 100%;
   padding: 0 1rem;
 `;
diff --git a/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPickerView.tsx b/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPickerView.tsx
index cd741fc35e5817b1cd2c7e9f18324db0faf0f849..c741e5d92deba123e5672c2638d7c023b4a3331d 100644
--- a/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPickerView.tsx
+++ b/frontend/src/metabase/containers/DataPicker/RawDataPicker/RawDataPickerView.tsx
@@ -1,8 +1,6 @@
 import React, { useCallback, useMemo } from "react";
 import _ from "underscore";
 
-import SelectList from "metabase/components/SelectList";
-
 import type { ITreeNodeItem } from "metabase/components/tree/types";
 
 import type Database from "metabase-lib/metadata/Database";
@@ -13,8 +11,9 @@ import type { DataPickerSelectedItem } from "../types";
 
 import EmptyState from "../EmptyState";
 import LoadingState from "../LoadingState";
+import VirtualizedSelectList from "../VirtualizedSelectList";
 import PanePicker from "../PanePicker";
-import { StyledSelectList } from "./RawDataPicker.styled";
+import { ListContainer } from "./RawDataPicker.styled";
 
 interface RawDataPickerViewProps {
   databases: Database[];
@@ -61,7 +60,7 @@ function TableSelectListItem({
 }) {
   const name = table.displayName();
   return (
-    <SelectList.Item
+    <VirtualizedSelectList.Item
       id={table.id}
       name={name}
       isSelected={isSelected}
@@ -69,7 +68,7 @@ function TableSelectListItem({
       onSelect={onSelect}
     >
       {name}
-    </SelectList.Item>
+    </VirtualizedSelectList.Item>
   );
 }
 
@@ -135,9 +134,8 @@ function RawDataPickerView({
   );
 
   const renderTable = useCallback(
-    (table: Table) => (
+    ({ item: table }: { item: Table }) => (
       <TableSelectListItem
-        key={table.id}
         table={table}
         isSelected={selectedTableIds.includes(table.id)}
         onSelect={onSelectedTable}
@@ -162,7 +160,14 @@ function RawDataPickerView({
       ) : isEmpty ? (
         <EmptyState />
       ) : (
-        <StyledSelectList>{tables?.map?.(renderTable)}</StyledSelectList>
+        <ListContainer>
+          {Array.isArray(tables) && (
+            <VirtualizedSelectList<Table>
+              items={tables}
+              renderItem={renderTable}
+            />
+          )}
+        </ListContainer>
       )}
     </PanePicker>
   );
diff --git a/frontend/src/metabase/containers/DataPicker/VirtualizedSelectList.tsx b/frontend/src/metabase/containers/DataPicker/VirtualizedSelectList.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7ad52248e1d46910e97184b24ab18b5678e42aa7
--- /dev/null
+++ b/frontend/src/metabase/containers/DataPicker/VirtualizedSelectList.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+import VirtualizedList, {
+  VirtualizedListProps,
+} from "metabase/components/VirtualizedList";
+import SelectList from "metabase/components/SelectList";
+
+const SELECT_LIST_ITEM_HEIGHT = 40;
+
+type VirtualizedSelectListProps<Item> = Omit<
+  VirtualizedListProps<Item>,
+  "rowHeight" | "role"
+>;
+
+function VirtualizedSelectList<Item>(props: VirtualizedSelectListProps<Item>) {
+  return (
+    <VirtualizedList<Item>
+      data-testid="select-list"
+      {...props}
+      role="menu"
+      rowHeight={SELECT_LIST_ITEM_HEIGHT}
+    />
+  );
+}
+
+export default Object.assign(VirtualizedSelectList, {
+  Item: SelectList.Item,
+});
diff --git a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Common.unit.spec.ts b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Common.unit.spec.ts
index d2c6dc25d66c30a62be3517668b95730df1a1f16..eb638ade81cee18fa2b40a8c2533e9c0915e5bf4 100644
--- a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Common.unit.spec.ts
+++ b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Common.unit.spec.ts
@@ -1,13 +1,13 @@
 import nock from "nock";
 
-import { screen, waitFor } from "__support__/ui";
+import { screen } from "__support__/ui";
 import { SAMPLE_DATABASE } from "__support__/sample_database_fixture";
 
-import { setup } from "./common";
+import { setup, setupVirtualizedLists } from "./common";
 
 describe("DataPicker", () => {
   beforeAll(() => {
-    window.HTMLElement.prototype.scrollIntoView = jest.fn();
+    setupVirtualizedLists();
   });
 
   afterEach(() => {
diff --git a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Models.unit.spec.ts b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Models.unit.spec.ts
index 44ac210af3e1933291f8542bef5060fc2c6ceac8..a1a7c36d4c195c691fa95a09f5db63df80c08ac0 100644
--- a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Models.unit.spec.ts
+++ b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Models.unit.spec.ts
@@ -13,6 +13,7 @@ import {
 
 import {
   setup,
+  setupVirtualizedLists,
   EMPTY_COLLECTION,
   SAMPLE_COLLECTION,
   SAMPLE_MODEL,
@@ -27,7 +28,7 @@ const ROOT_COLLECTION_MODEL_VIRTUAL_SCHEMA_ID = getCollectionVirtualSchemaId(
 
 describe("DataPicker — picking models", () => {
   beforeAll(() => {
-    window.HTMLElement.prototype.scrollIntoView = jest.fn();
+    setupVirtualizedLists();
   });
 
   afterEach(() => {
diff --git a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Questions.unit.spec.ts b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Questions.unit.spec.ts
index 320c707e4a18bac2b4c077bf5096a60968fb72e6..5d38a0b09a837ee4c4749d93ca03921bcb890c52 100644
--- a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Questions.unit.spec.ts
+++ b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-Questions.unit.spec.ts
@@ -13,6 +13,7 @@ import {
 
 import {
   setup,
+  setupVirtualizedLists,
   EMPTY_COLLECTION,
   SAMPLE_COLLECTION,
   SAMPLE_QUESTION,
@@ -25,7 +26,7 @@ const ROOT_COLLECTION_QUESTIONS_VIRTUAL_SCHEMA_ID =
 
 describe("DataPicker — picking questions", () => {
   beforeAll(() => {
-    window.HTMLElement.prototype.scrollIntoView = jest.fn();
+    setupVirtualizedLists();
   });
 
   afterEach(() => {
diff --git a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-RawData.unit.spec.ts b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-RawData.unit.spec.ts
index ee9c06872a033d6a7390fcb39a4059ebfaee69f0..db37ba4d10032208040edaf20d60b03211441578 100644
--- a/frontend/src/metabase/containers/DataPicker/tests/DataPicker-RawData.unit.spec.ts
+++ b/frontend/src/metabase/containers/DataPicker/tests/DataPicker-RawData.unit.spec.ts
@@ -10,11 +10,11 @@ import {
 
 import { generateSchemaId } from "metabase-lib/metadata/utils/schema";
 
-import { setup } from "./common";
+import { setup, setupVirtualizedLists } from "./common";
 
 describe("DataPicker — picking raw data", () => {
   beforeAll(() => {
-    window.HTMLElement.prototype.scrollIntoView = jest.fn();
+    setupVirtualizedLists();
   });
 
   afterEach(() => {
diff --git a/frontend/src/metabase/containers/DataPicker/tests/common.tsx b/frontend/src/metabase/containers/DataPicker/tests/common.tsx
index 7846c37c9a9deaff706a9f2b9289b5b51cda32c2..3bb1fdb15c47f8feebb5717a1061cfddf0f40657 100644
--- a/frontend/src/metabase/containers/DataPicker/tests/common.tsx
+++ b/frontend/src/metabase/containers/DataPicker/tests/common.tsx
@@ -111,6 +111,21 @@ interface SetupOpts {
   hasNestedQueriesEnabled?: boolean;
 }
 
+// react-virtualized's AutoSizer uses offsetWidth and offsetHeight.
+// Jest runs in JSDom which doesn't support measurements APIs.
+export function setupVirtualizedLists() {
+  Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
+    configurable: true,
+    value: 100,
+  });
+  Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
+    configurable: true,
+    value: 100,
+  });
+
+  window.HTMLElement.prototype.scrollIntoView = jest.fn();
+}
+
 export async function setup({
   initialValue = { tableIds: [] },
   filters,