diff --git a/src/components/TablePagination/TablePagination.scss b/src/components/TablePagination/TablePagination.scss
new file mode 100644
index 000000000..a7756b72c
--- /dev/null
+++ b/src/components/TablePagination/TablePagination.scss
@@ -0,0 +1,40 @@
+@import "~vanilla-framework/scss/settings";
+
+.pagination {
+ align-items: baseline;
+ display: flex;
+ margin-top: 1.2rem;
+
+ .description {
+ flex-grow: 1;
+ }
+
+ .back {
+ margin: 0 $spv--large;
+
+ .p-icon--chevron-down {
+ rotate: 90deg;
+ }
+ }
+
+ .next {
+ margin: 0 $spv--large;
+
+ .p-icon--chevron-down {
+ rotate: 270deg;
+ }
+ }
+
+ .pagination-input {
+ margin-right: $spv--small;
+ min-width: 0;
+ width: 3rem;
+ }
+
+ .pagination-select {
+ margin-bottom: 0;
+ margin-left: $spv--x-large;
+ min-width: 0;
+ width: 7rem;
+ }
+}
diff --git a/src/components/TablePagination/TablePagination.stories.mdx b/src/components/TablePagination/TablePagination.stories.mdx
new file mode 100644
index 000000000..ed46becf9
--- /dev/null
+++ b/src/components/TablePagination/TablePagination.stories.mdx
@@ -0,0 +1,214 @@
+import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs";
+
+import TablePagination from "./TablePagination";
+import MainTable from "../MainTable";
+
+
+
+export const Template = (args) => ;
+
+### TablePagination
+
+This is an HOC [React](https://reactjs.org/) component for applying pagination to input data for direct child components.
+This component is un-opinionated about the structure of the input data and can be used with any child component that displays
+a list of data.
+
+To use this component, simply wrap a child component with it and provide the data that you want to paginate to the ```data``` prop.
+This component will then pass the paged data to all direct child components via a child prop specified by ```dataPropOverride```.
+
+### Props
+
+
+
+### Default
+
+
+
+### Custom page limit
+
+
+
+### Custom display title
+
+
+
+### Render above
+
+
+
+### Render below
+
+
diff --git a/src/components/TablePagination/TablePagination.test.tsx b/src/components/TablePagination/TablePagination.test.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/TablePagination/TablePagination.tsx b/src/components/TablePagination/TablePagination.tsx
new file mode 100644
index 000000000..bfe68e417
--- /dev/null
+++ b/src/components/TablePagination/TablePagination.tsx
@@ -0,0 +1,188 @@
+import React, {
+ ChangeEvent,
+ Children,
+ HTMLAttributes,
+ PropsWithChildren,
+ ReactElement,
+ ReactNode,
+ RefObject,
+ cloneElement,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import classnames from "classnames";
+import { usePagination } from "hooks";
+import Select from "components/Select";
+import TablePaginationControls from "./TablePaginationControls";
+import "./TablePagination.scss";
+
+/**
+ * Determine if we are working with a small screen.
+ * 'small screen' in this case is relative to the width of the description div
+ */
+const figureSmallScreen = (descriptionRef: RefObject) => {
+ const descriptionElement = descriptionRef.current;
+ if (!descriptionElement) {
+ return true;
+ }
+ return descriptionElement.getBoundingClientRect().width < 230;
+};
+
+/**
+ * Iterate direct react child components and override the value of the prop specified by @param dataPropOverride
+ * for those child components.
+ * @param children - react node children to iterate
+ * @param dataPropOverride - the name of the prop from the children components to override
+ * @param data - actual data to be passed to the prop specified by @param dataPropOverride
+ */
+const renderChildren = (
+ children: ReactNode,
+ dataPropOverride: string,
+ data: unknown[]
+) => {
+ return Children.map(children, (child) => {
+ return cloneElement(child as ReactElement, {
+ [dataPropOverride]: data,
+ });
+ });
+};
+
+const DEFAULT_PAGE_LIMITS = [50, 100, 200];
+const generatePagingOptions = (pageLimits: number[]) => {
+ return pageLimits.map((limit) => ({ value: limit, label: `${limit}/page` }));
+};
+
+export type Props = PropsWithChildren<{
+ /**
+ * list of data elements to be paginated. This component is un-opinionated about
+ * the structure of the data but it should be identical to the data structure
+ * reuiqred by the child table component
+ */
+ data: unknown[];
+ /**
+ * this is the prop of the underlying table component that should be overwritten with
+ * paginated data. If now specified, the default value for this prop is set to @constant rows
+ * which is the data prop for the @func MainTable component
+ */
+ dataPropOverride?: string;
+ /**
+ * the name of the item associated to each row within the table.
+ */
+ itemName?: string;
+ /**
+ * custom styling for the pagination container
+ */
+ containerClass?: string;
+ /**
+ * custom title to be displayed by the pagination
+ */
+ displayTitle?: ReactNode;
+ /**
+ * custom per page limits express as an array of numbers. default is [50, 100, 200]
+ */
+ pageLimits?: number[];
+ /**
+ * render the pagination component below the table? default to false
+ */
+ renderBelow?: boolean;
+ /**
+ * is the input data filtered? If this is set to @constant true, the default display
+ * title will include 'filtered' text
+ */
+ dataFiltered?: boolean;
+}> &
+ HTMLAttributes;
+
+const TablePagination = ({
+ data,
+ containerClass,
+ itemName = "item",
+ displayTitle,
+ renderBelow = false,
+ dataPropOverride = "rows",
+ pageLimits = DEFAULT_PAGE_LIMITS,
+ dataFiltered = false,
+ children,
+ ...divProps
+}: Props) => {
+ const descriptionRef = useRef(null);
+ const [isSmallScreen, setSmallScreen] = useState(false);
+ const [pageSize, setPageSize] = useState(() => {
+ return generatePagingOptions(pageLimits)[0].value;
+ });
+ const { paginate, currentPage, pageData, totalItems } = usePagination(data, {
+ itemsPerPage: pageSize,
+ autoResetPage: true,
+ });
+
+ useEffect(() => {
+ const handleResize = () => {
+ setSmallScreen(figureSmallScreen(descriptionRef));
+ };
+ window.addEventListener("resize", handleResize);
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [isSmallScreen]);
+
+ const handlePageSizeChange = (e: ChangeEvent) => {
+ paginate(1);
+ setPageSize(parseInt(e.target.value));
+ };
+
+ const getDisplayTitle = () => {
+ if (displayTitle) {
+ return displayTitle;
+ }
+
+ const visibleCount = pageData.length;
+ const filteredText = dataFiltered ? "filtered" : "";
+
+ if (isSmallScreen) {
+ return `${visibleCount} out of ${totalItems}`;
+ }
+
+ if (visibleCount === totalItems && visibleCount > 1) {
+ return `Showing all ${totalItems} ${filteredText} ${itemName}s`;
+ }
+
+ return `Showing ${visibleCount} out of ${totalItems} ${filteredText} ${itemName}${
+ totalItems !== 1 ? "s" : ""
+ }`;
+ };
+
+ const getPageLimitOptions = () => {
+ return generatePagingOptions(pageLimits || DEFAULT_PAGE_LIMITS);
+ };
+
+ const totalPages = Math.ceil(data.length / pageSize);
+
+ return (
+ <>
+ {renderBelow && renderChildren(children, dataPropOverride, pageData)}
+
+
+ {getDisplayTitle()}
+
+
+
+
+ {!renderBelow && renderChildren(children, dataPropOverride, pageData)}
+ >
+ );
+};
+
+export default TablePagination;
diff --git a/src/components/TablePagination/TablePaginationControls/TablePaginationControls.test.tsx b/src/components/TablePagination/TablePaginationControls/TablePaginationControls.test.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/TablePagination/TablePaginationControls/TablePaginationControls.tsx b/src/components/TablePagination/TablePaginationControls/TablePaginationControls.tsx
new file mode 100644
index 000000000..b7ff267ff
--- /dev/null
+++ b/src/components/TablePagination/TablePaginationControls/TablePaginationControls.tsx
@@ -0,0 +1,79 @@
+import Button from "components/Button";
+import Icon from "components/Icon";
+import Input from "components/Input";
+import React, { ChangeEvent } from "react";
+
+export type Props = {
+ /**
+ * Callback function to handle a change in page number
+ */
+ onPageChange: (pageNumber: number) => void;
+ /**
+ * The current page of the data
+ */
+ currentPage: number;
+ /**
+ * The total number of pages that exists within the data
+ */
+ totalPages: number;
+};
+
+const TablePaginationControls = ({
+ onPageChange,
+ currentPage,
+ totalPages,
+}: Props): JSX.Element => {
+ const handleDecrementPage = (currentPage: number) => {
+ if (currentPage > 1) {
+ onPageChange(currentPage - 1);
+ }
+ };
+
+ const handleIncrementPage = (currentPage: number, totalPages: number) => {
+ if (currentPage < totalPages) {
+ onPageChange(currentPage + 1);
+ }
+ };
+
+ const handleInputPageChange = (e: ChangeEvent) => {
+ const newPage = Math.min(totalPages, Math.max(1, parseInt(e.target.value)));
+ onPageChange(newPage);
+ };
+
+ return (
+ <>
+
+ {" "}
+ of {totalPages}
+
+ >
+ );
+};
+
+export default TablePaginationControls;
diff --git a/src/components/TablePagination/TablePaginationControls/__snapshots__/TablePaginationControls.test.tsx.snap b/src/components/TablePagination/TablePaginationControls/__snapshots__/TablePaginationControls.test.tsx.snap
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/TablePagination/TablePaginationControls/index.ts b/src/components/TablePagination/TablePaginationControls/index.ts
new file mode 100644
index 000000000..48aa68113
--- /dev/null
+++ b/src/components/TablePagination/TablePaginationControls/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./TablePaginationControls";
+export type { Props as TablePaginationControlsProps } from "./TablePaginationControls";
diff --git a/src/components/TablePagination/__snapshots__/TablePagination.test.tsx.snap b/src/components/TablePagination/__snapshots__/TablePagination.test.tsx.snap
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/TablePagination/index.ts b/src/components/TablePagination/index.ts
new file mode 100644
index 000000000..aac664179
--- /dev/null
+++ b/src/components/TablePagination/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./TablePagination";
+export type { Props as TablePaginationProps } from "./TablePagination";