diff --git a/src/components/Table/Table.mdx b/src/components/Table/Table.mdx
new file mode 100644
index 00000000..1af6c21b
--- /dev/null
+++ b/src/components/Table/Table.mdx
@@ -0,0 +1,177 @@
+import { Meta, Canvas } from '@storybook/blocks';
+
+import * as TableStories from './Table.stories.svelte';
+
+
+
+# Table
+
+The `Table` component presents data as a table that you can make searchable, filtereable, sortable, or paginated.
+
+```svelte
+
+
+
+```
+
+
+
+## Text elements
+
+Set the `title`, `dek`, `notes` and `source` options to add supporting metadata above and below the table.
+
+```svelte
+
+```
+
+
+
+## Truncated
+
+When your table has 10 or more rows, consider clipping it by setting the `truncated` option. When it is enabled, the table is clipped and readers must click a button below the table to see all rows.
+
+By default, this configuration will limit the table to 5 records. Change the cutoff point by adjusting the `truncateLength` option.
+
+This is a good option for simple tables with between 10 and 30 rows. It works best when the table doesn't require interactivity.
+
+```svelte
+
+```
+
+
+
+## Paginated
+
+When your table has many rows, you should consider breaking it up into pages by setting `paginated` to `true`. When it is enabled, readers can leaf through the data using a set of buttons below the table.
+
+By default, there are 25 records per page. Change the number by adjusting the `pageSize` option.
+
+This is a good option when publishing large tables for readers to explore. It works well with interactive features like searching and filters.
+
+```svelte
+
+```
+
+
+
+## Search bar
+
+Allow users to search the table by setting the optional `searchable` option to `true`. Modify the default text that appears in the box by setting `searchPlaceholder` to a different placeholder text.
+
+```svelte
+
+```
+
+
+
+## Filter
+
+Allow users to filter the table by providing one of the attributes as the `filterField`. This works best with categorical columns.
+
+Set `filterLabel` to make the category name more readable. For example, if the column is `Region`, set `filterLabel` to `regions` or `regions of the world`.
+
+```svelte
+
+```
+
+
+
+## Search and filter
+
+Feel free to both search and filter.
+
+```svelte
+
+```
+
+
+```
+
+## Sort
+
+Allow users to sort the table by setting `sortable` to `true`. Specify the starting order by setting `sortField` to a column name and `sortDirection` to `ascending` or `descending`.
+
+By default, all fields are sortable. If you'd like to limit the columns where sorting is allowed, provide a list to the `sortableFields` option.
+
+```svelte
+
+```
+
+
+
+## Format
+
+Format column values by supplying functions keyed to field names with the `fieldFormatters` option. Columns are still sorted using the raw, underlying values.
+
+Among other things, this feature can be used to provide a unit of measurement, such as `$` or `%`, with numeric fields.
+
+```svelte
+
+
+
+```
+
+
diff --git a/src/components/Table/Table.stories.svelte b/src/components/Table/Table.stories.svelte
index d27f6859..7b230b9a 100644
--- a/src/components/Table/Table.stories.svelte
+++ b/src/components/Table/Table.stories.svelte
@@ -1,60 +1,30 @@
-
- {#snippet children({ args })}
-
- {/snippet}
-
-
,
+ },
sortable: true,
sortField: 'Net worth (in billions)',
sortDirection: 'descending',
- fieldFormatters: { 'Net worth (in billions)': currencyFormat },
- }}
-/>
-
-
-
+
{#if title || dek || searchable || filterList}
-
-```
diff --git a/src/components/Table/stories/docs/truncate.md b/src/components/Table/stories/docs/truncate.md
deleted file mode 100644
index d36a230f..00000000
--- a/src/components/Table/stories/docs/truncate.md
+++ /dev/null
@@ -1,11 +0,0 @@
-When your table has 10 or more rows, consider clipping it by setting the `truncated` option. When it is enabled, the table is clipped and readers must request to see all rows by clicking a button below the table. By default this configuration will limit the table to five records. You can change the cutoff point by adjusting the `truncateLength` option.
-
-This is a good option for simple tables with row counts between 10 and 30. It works best when the table doesn't require interactivity.
-
-```svelte
-
-```
diff --git a/src/components/Table/utils.js b/src/components/Table/utils.js
deleted file mode 100644
index 0eb2b6b8..00000000
--- a/src/components/Table/utils.js
+++ /dev/null
@@ -1,36 +0,0 @@
-export function filterArray(data, searchText, filterField, filterValue) {
- if (searchText) {
- data = data.filter((item) => {
- return item.searchStr.includes(searchText.toLowerCase());
- });
- }
- if (filterValue && filterValue) {
- data = data.filter((item) => {
- return item[filterField] === filterValue;
- });
- }
- return data;
-}
-
-export function paginateArray(array, pageSize, pageNumber) {
- return array.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
-}
-
-function uniqueAttr(array, attr) {
- return array.map((e) => e[attr]).filter(unique);
-}
-
-function unique(value, index, array) {
- return array.indexOf(value) === index;
-}
-
-export function getOptions(data, attr) {
- // Get all the unique values in the provided field. Sort it.
- const attrList = uniqueAttr(data, attr).sort((a, b) => a.localeCompare(b));
-
- // Tack 'All' as the front as the first option.
- attrList.unshift('All');
-
- // Convert the list into Option typed objects ready for our Select component
- return attrList.map((a) => ({ text: a, value: a }));
-}
diff --git a/src/components/Table/utils.ts b/src/components/Table/utils.ts
new file mode 100644
index 00000000..51b36a7a
--- /dev/null
+++ b/src/components/Table/utils.ts
@@ -0,0 +1,122 @@
+type FilterableDatum> = T & {
+ searchStr: string;
+};
+
+export function filterArray>(
+ data: FilterableDatum[],
+ searchText: string,
+ filterField: keyof FilterableDatum,
+ filterValue: FilterableDatum[keyof FilterableDatum]
+) {
+ if (searchText) {
+ data = data.filter((item) => {
+ return item.searchStr.includes(searchText.toLowerCase());
+ });
+ }
+
+ if (filterValue) {
+ data = data.filter((item) => {
+ if (!filterField) return true; // or handle the undefined case as appropriate
+
+ return item[filterField] === filterValue;
+ });
+ }
+
+ return data;
+}
+export function paginateArray(
+ array: T[],
+ pageSize: number,
+ pageNumber: number
+) {
+ return array.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
+}
+
+/**
+ * We specify the output type here by adding `string` to the union because we want to explicitly define the output array as accepting strings.
+ *
+ * This is to get rid of the type error from `attrList.unshift('All')`
+ */
+function uniqueAttr(array: T[], attr: keyof T): (T[keyof T] | string)[] {
+ return array.map((e) => e[attr]).filter(unique);
+}
+
+function unique(value: T, index: number, array: T[]) {
+ return array.indexOf(value) === index;
+}
+
+export function getOptions(data: T[], attr: keyof T) {
+ // Get all the unique values in the provided field. Sort it.
+ const attrList = uniqueAttr(data, attr).sort((a, b) => {
+ // Throw errors if a and b are not strings.
+ // a and b should be strings since they are keys of T.
+ if (typeof a !== 'string' || typeof b !== 'string') {
+ throw new Error(`Expected string, got ${typeof a} and ${typeof b}`);
+ }
+
+ return a.localeCompare(b);
+ });
+
+ // Tack 'All' at the start of `attrList`, making it the first option.
+ attrList.unshift('All');
+
+ // Convert the list into Option typed objects ready for our Select component
+ return attrList.map((a) => ({ text: a, value: a }));
+}
+interface SortableItem {
+ [key: string]: unknown; // Or more specific types if known
+}
+
+/**
+ * Sorts an array of objects based on a specified column and direction.
+ */
+export function sortArray(
+ /** The array to sort. */
+ array: T[],
+ /** The column to sort by. */
+ column: keyof T,
+ /** The sorting direction ('ascending' or 'descending'). */
+ direction: 'ascending' | 'descending',
+ /** Whether or not sorting is turned on */
+ sortable: boolean
+) {
+ if (!sortable) return array;
+
+ const sorted = [...array].sort((a, b) => {
+ if (a[column] < b[column]) {
+ return direction === 'ascending' ? -1 : 1;
+ } else if (a[column] > b[column]) {
+ return direction === 'ascending' ? 1 : -1;
+ } else {
+ return 0;
+ }
+ });
+
+ return sorted;
+}
+
+export type Formatter = (value: T) => string;
+
+export type FieldFormatters = {
+ [K in keyof T]?: Formatter;
+};
+/**
+ * Formats a value based on a field and a dictionary of formatters.
+ */
+export function formatValue>(
+ /** The object containing the field. */
+ item: FilterableDatum,
+ /** The field to format. */
+ field: keyof T,
+ /** An optional dictionary of formatters. */
+ fieldFormatters?: FieldFormatters
+) {
+ const value = item[field];
+
+ if (fieldFormatters && field in fieldFormatters && fieldFormatters[field]) {
+ const formatter = fieldFormatters[field];
+ return formatter(value);
+ } else {
+ return value;
+ }
+}