diff --git a/frontend/packages/data-portal/app/components/Run/AnnotationTable.tsx b/frontend/packages/data-portal/app/components/Run/AnnotationTable.tsx
new file mode 100644
index 000000000..3f13516df
--- /dev/null
+++ b/frontend/packages/data-portal/app/components/Run/AnnotationTable.tsx
@@ -0,0 +1,172 @@
+/* eslint-disable react/no-unstable-nested-components */
+
+import { Button, CellHeader } from '@czi-sds/components'
+import { ColumnDef, createColumnHelper } from '@tanstack/react-table'
+import { range } from 'lodash-es'
+import { useMemo } from 'react'
+
+import { GetRunByIdQuery } from 'app/__generated__/graphql'
+import { MAX_PER_PAGE } from 'app/constants/pagination'
+import { useIsLoading } from 'app/hooks/useIsLoading'
+import { useRunById } from 'app/hooks/useRunById'
+import { i18n } from 'app/i18n'
+import { useDrawer } from 'app/state/drawer'
+import { cnsNoMerge } from 'app/utils/cns'
+
+import { Table, TableCell } from '../Table'
+
+type Annotation =
+ GetRunByIdQuery['runs'][number]['annotation_table'][number]['annotations'][number]
+
+const LOADING_ANNOTATIONS = range(0, MAX_PER_PAGE).map(() => ({}) as Annotation)
+
+function ConfidenceValue({ value }: { value: number }) {
+ return (
+
+
{value}%
+
+ {i18n.confidence}
+
+
+ )
+}
+
+export function AnnotationTable() {
+ const { isLoadingDebounced } = useIsLoading()
+ const { run } = useRunById()
+ const drawer = useDrawer()
+
+ const columns = useMemo(() => {
+ const columnHelper = createColumnHelper()
+
+ function getConfidenceCell(key: keyof Annotation, header: string) {
+ return columnHelper.accessor(key, {
+ header: () => {header},
+ cell: ({ getValue }) => {
+ const value = getValue() as number | null
+
+ return (
+
+ {typeof value === 'number' ? (
+
+ ) : (
+
+ {i18n.na}
+
+ )}
+
+ )
+ },
+ })
+ }
+
+ return [
+ columnHelper.accessor('s3_annotations_path', {
+ header: i18n.annotations,
+ cell: ({ row: { original: annotation } }) => (
+
+
+
+ {annotation.s3_annotations_path?.split('/').at(-1) ?? '--'}
+
+
+ {annotation.ground_truth_status && (
+
+ {i18n.groundTruth}
+
+ )}
+
+
+
+ {annotation.authors.map((author, idx) => {
+ const authorLength = annotation.authors.length
+ const totalAuthorCount =
+ annotation.authors_aggregate.aggregate?.count ?? 0
+
+ return (
+ -
+ {author.name}
+ {idx < authorLength - 1 && ', '}
+
+ {idx === authorLength - 1 && idx < totalAuthorCount - 1 && (
+
+ )}
+
+ )
+ })}
+
+
+ ),
+ }),
+
+ columnHelper.accessor('object_name', {
+ header: i18n.annotationObject,
+ cell: ({ getValue }) => (
+
+ {getValue()}
+
+ ),
+ }),
+
+ columnHelper.accessor('object_count', {
+ header: () => (
+ {i18n.objectCount}
+ ),
+ cell: ({ getValue }) => (
+
+ {getValue()}
+
+ ),
+ }),
+
+ getConfidenceCell('confidence_precision', i18n.precision),
+ getConfidenceCell('confidence_recall', i18n.recall),
+
+ columnHelper.display({
+ id: 'annotation-actions',
+ // Render empty cell header so that it doesn't break the table layout
+ header: () => {null},
+ cell: () => (
+
+
+
+ ),
+ }),
+ ] as ColumnDef[]
+ }, [drawer.toggle])
+
+ const annotations = run.annotation_table.flatMap(
+ (data) => data.annotations,
+ ) as unknown as Annotation[]
+
+ return (
+
+ )
+}