diff --git a/.changeset/flat-owls-grin.md b/.changeset/flat-owls-grin.md
new file mode 100644
index 00000000000..3bfa3bced66
--- /dev/null
+++ b/.changeset/flat-owls-grin.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": patch
+---
+
+Implements column width features for the DataTable
diff --git a/src/DataTable/DataTable.features.stories.tsx b/src/DataTable/DataTable.features.stories.tsx
index 75e95c254da..b7d8f615d3d 100644
--- a/src/DataTable/DataTable.features.stories.tsx
+++ b/src/DataTable/DataTable.features.stories.tsx
@@ -930,6 +930,72 @@ export const WithRowActionMenu = () => (
   
 )
 
+export const MixedColumnWidths = () => (
+  
+    
+      Repositories
+    
+     {
+            return 
+          },
+          width: 'shrink',
+          minWidth: '100px',
+        },
+        {
+          header: 'auto',
+          field: 'updatedAt',
+          renderCell: row => {
+            return 
+          },
+          width: 'auto',
+        },
+        {
+          header: '200px',
+          field: 'securityFeatures.dependabot',
+          renderCell: row => {
+            return row.securityFeatures.dependabot.length > 0 ? (
+              
+                {row.securityFeatures.dependabot.map(feature => {
+                  return 
+                })}
+              
+            ) : null
+          },
+          width: '200px',
+        },
+        {
+          header: 'undefined (defaults to grow)',
+          field: 'securityFeatures.codeScanning',
+          renderCell: row => {
+            return row.securityFeatures.codeScanning.length > 0 ? (
+              
+                {row.securityFeatures.codeScanning.map(feature => {
+                  return 
+                })}
+              
+            ) : null
+          },
+        },
+      ]}
+    />
+  
+)
+
 export const WithCustomHeading = () => (
   <>
     
diff --git a/src/DataTable/DataTable.stories.tsx b/src/DataTable/DataTable.stories.tsx
index 1b50a6b6b67..a7b1d29fec1 100644
--- a/src/DataTable/DataTable.stories.tsx
+++ b/src/DataTable/DataTable.stories.tsx
@@ -1,13 +1,16 @@
-import {Meta, ComponentStory} from '@storybook/react'
+import {Meta} from '@storybook/react'
 import React from 'react'
-import {DataTable, Table} from '../DataTable'
+import {DataTable, DataTableProps, Table} from '../DataTable'
 import Label from '../Label'
 import LabelGroup from '../LabelGroup'
 import RelativeTime from '../RelativeTime'
+import {UniqueRow} from './row'
+import {getColumnWidthArgTypes, ColWidthArgTypes} from './storyHelpers'
 
 export default {
   title: 'Components/DataTable',
   component: DataTable,
+  argTypes: getColumnWidthArgTypes(5),
 } as Meta
 
 const now = Date.now()
@@ -179,7 +182,14 @@ export const Default = () => (
   
 )
 
-export const Playground: ComponentStory = args => {
+export const Playground = (args: DataTableProps & ColWidthArgTypes) => {
+  const getColWidth = (colIndex: number) => {
+    return args[`colWidth${colIndex}`] !== 'explicit width'
+      ? args[`colWidth${colIndex}`]
+      : args[`explicitColWidth${colIndex}`]
+      ? args[`explicitColWidth${colIndex}`]
+      : 'grow'
+  }
   return (
     
       
@@ -198,6 +208,9 @@ export const Playground: ComponentStory = args => {
             header: 'Repository',
             field: 'name',
             rowHeader: true,
+            width: getColWidth(0),
+            minWidth: args.minColWidth0,
+            maxWidth: args.maxColWidth0,
           },
           {
             header: 'Type',
@@ -205,6 +218,9 @@ export const Playground: ComponentStory = args => {
             renderCell: row => {
               return 
             },
+            width: getColWidth(1),
+            minWidth: args.minColWidth1,
+            maxWidth: args.maxColWidth1,
           },
           {
             header: 'Updated',
@@ -212,6 +228,9 @@ export const Playground: ComponentStory = args => {
             renderCell: row => {
               return 
             },
+            width: getColWidth(2),
+            minWidth: args.minColWidth2,
+            maxWidth: args.maxColWidth2,
           },
           {
             header: 'Dependabot',
@@ -225,6 +244,9 @@ export const Playground: ComponentStory = args => {
                 
               ) : null
             },
+            width: getColWidth(3),
+            minWidth: args.minColWidth3,
+            maxWidth: args.maxColWidth3,
           },
           {
             header: 'Code scanning',
@@ -238,6 +260,9 @@ export const Playground: ComponentStory = args => {
                 
               ) : null
             },
+            width: getColWidth(4),
+            minWidth: args.minColWidth4,
+            maxWidth: args.maxColWidth4,
           },
         ]}
       />
diff --git a/src/DataTable/DataTable.tsx b/src/DataTable/DataTable.tsx
index 7b6ccb20de3..9fc9f7c419e 100644
--- a/src/DataTable/DataTable.tsx
+++ b/src/DataTable/DataTable.tsx
@@ -61,14 +61,19 @@ function DataTable({
   initialSortColumn,
   initialSortDirection,
 }: DataTableProps) {
-  const {headers, rows, actions} = useTable({
+  const {headers, rows, actions, gridTemplateColumns} = useTable({
     data,
     columns,
     initialSortColumn,
     initialSortDirection,
   })
   return (
-    
+    
       
         
           {headers.map(header => {
diff --git a/src/DataTable/Table.tsx b/src/DataTable/Table.tsx
index 9b9765438ca..7f197d0983f 100644
--- a/src/DataTable/Table.tsx
+++ b/src/DataTable/Table.tsx
@@ -19,7 +19,9 @@ const StyledTable = styled.table>`
   background-color: ${get('colors.canvas.default')};
   border-spacing: 0;
   border-collapse: separate;
+  display: grid;
   font-size: var(--table-font-size);
+  grid-template-columns: var(--grid-template-columns);
   line-height: calc(20 / var(--table-font-size));
   width: 100%;
   overflow-x: auto;
@@ -138,6 +140,22 @@ const StyledTable = styled.table>`
     font-weight: 600;
     text-align: start;
   }
+
+  /* Grid layout */
+  .TableHead,
+  .TableBody,
+  .TableRow {
+    display: contents;
+  }
+
+  @supports (grid-template-columns: subgrid) {
+    .TableHead,
+    .TableBody,
+    .TableRow {
+      display: grid;
+      grid-template-columns: subgrid;
+      grid-column: -1 /1;
+  }
 `
 
 export type TableProps = React.ComponentPropsWithoutRef<'table'> & {
@@ -151,6 +169,11 @@ export type TableProps = React.ComponentPropsWithoutRef<'table'> & {
    */
   'aria-labelledby'?: string
 
+  /**
+   * Column width definitions
+   */
+  gridTemplateColumns?: React.CSSProperties['gridTemplateColumns']
+
   /**
    * Specify the amount of space that should be available around the contents of
    * a cell
@@ -158,8 +181,20 @@ export type TableProps = React.ComponentPropsWithoutRef<'table'> & {
   cellPadding?: 'condensed' | 'normal' | 'spacious'
 }
 
-const Table = React.forwardRef(function Table({cellPadding = 'normal', ...rest}, ref) {
-  return 
+const Table = React.forwardRef(function Table(
+  {cellPadding = 'normal', gridTemplateColumns, ...rest},
+  ref,
+) {
+  return (
+    
+  )
 })
 
 // ----------------------------------------------------------------------------
@@ -169,7 +204,14 @@ const Table = React.forwardRef(function Table({cel
 export type TableHeadProps = React.ComponentPropsWithoutRef<'thead'>
 
 function TableHead({children}: TableHeadProps) {
-  return {children}
+  return (
+    // We need to explicitly pass this role because some ATs and browsers drop table semantics
+    // when we use `display: contents` or `display: grid` in the table
+    // eslint-disable-next-line jsx-a11y/no-redundant-roles
+    
+      {children}
+    
+  )
 }
 
 // ----------------------------------------------------------------------------
@@ -179,7 +221,14 @@ function TableHead({children}: TableHeadProps) {
 export type TableBodyProps = React.ComponentPropsWithoutRef<'tbody'>
 
 function TableBody({children}: TableBodyProps) {
-  return {children}
+  return (
+    // We need to explicitly pass this role because some ATs and browsers drop table semantics
+    // when we use `display: contents` or `display: grid` in the table
+    // eslint-disable-next-line jsx-a11y/no-redundant-roles
+    
+      {children}
+    
+  )
 }
 
 // ----------------------------------------------------------------------------
diff --git a/src/DataTable/__tests__/DataTable.test.tsx b/src/DataTable/__tests__/DataTable.test.tsx
index 865f28f53e3..b977d3876c0 100644
--- a/src/DataTable/__tests__/DataTable.test.tsx
+++ b/src/DataTable/__tests__/DataTable.test.tsx
@@ -2,7 +2,8 @@ import userEvent from '@testing-library/user-event'
 import {render, screen, getByRole, queryByRole, queryAllByRole} from '@testing-library/react'
 import React from 'react'
 import {DataTable, Table} from '../../DataTable'
-import {createColumnHelper} from '../column'
+import {Column, createColumnHelper} from '../column'
+import {getGridTemplateFromColumns} from '../useTable'
 
 describe('DataTable', () => {
   it('should render a semantic  through `data` and `columns`', () => {
@@ -809,4 +810,174 @@ describe('DataTable', () => {
       expect(getRowOrder()).toEqual(['3', '2', '1'])
     })
   })
+
+  describe('column widths', () => {
+    it('correctly sets the column width to "grow" when width is undefined', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns = [
+        columnHelper.column({
+          header: 'Name',
+          field: 'name',
+        }),
+      ]
+
+      expect(getGridTemplateFromColumns(columns)).toEqual(['minmax(max-content, 1fr)'])
+    })
+    it('correctly sets the column width when width === "grow"', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns = [
+        columnHelper.column({
+          header: 'Name',
+          field: 'name',
+          width: 'grow',
+        }),
+      ]
+
+      expect(getGridTemplateFromColumns(columns)).toEqual(['minmax(max-content, 1fr)'])
+    })
+    it('correctly sets the column width when width === "shrink"', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns = [
+        columnHelper.column({
+          header: 'Name',
+          field: 'name',
+          width: 'shrink',
+        }),
+      ]
+
+      expect(getGridTemplateFromColumns(columns)).toEqual(['minmax(0, 1fr)'])
+    })
+    it('correctly sets the column width when width === "auto"', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns = [
+        columnHelper.column({
+          header: 'Name',
+          field: 'name',
+          width: 'auto',
+        }),
+      ]
+
+      expect(getGridTemplateFromColumns(columns)).toEqual(['auto'])
+    })
+    it('correctly sets the column width when width is a CSS width string', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns = [
+        columnHelper.column({
+          header: 'Name',
+          field: 'name',
+          width: '42ch',
+        }),
+      ]
+
+      expect(getGridTemplateFromColumns(columns)).toEqual(['42ch'])
+    })
+    it('correctly sets the column width when width is a number', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns = [
+        columnHelper.column({
+          header: 'Name',
+          field: 'name',
+          width: 200,
+        }),
+      ]
+
+      expect(getGridTemplateFromColumns(columns)).toEqual(['200px'])
+    })
+    it('correctly sets min-widths for the column', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns: Record[]> = {
+        grow: [
+          columnHelper.column({
+            header: 'Name',
+            field: 'name',
+            width: 'grow',
+            minWidth: '42ch',
+          }),
+        ],
+        shrink: [
+          columnHelper.column({
+            header: 'Name',
+            field: 'name',
+            width: 'shrink',
+            minWidth: '42ch',
+          }),
+        ],
+        auto: [
+          columnHelper.column({
+            header: 'Name',
+            field: 'name',
+            width: 'auto',
+            minWidth: '42ch',
+          }),
+        ],
+      }
+      const expectedWidths: Record = {
+        grow: 'minmax(42ch, 1fr)',
+        shrink: 'minmax(42ch, 1fr)',
+        auto: 'minmax(42ch, auto)',
+      }
+
+      for (const widthOpt in columns) {
+        expect(getGridTemplateFromColumns(columns[widthOpt])).toEqual([expectedWidths[widthOpt]])
+      }
+    })
+    it('correctly sets max-widths for the column', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns: Record[]> = {
+        grow: [
+          columnHelper.column({
+            header: 'Name',
+            field: 'name',
+            width: 'grow',
+            maxWidth: '42ch',
+          }),
+        ],
+        shrink: [
+          columnHelper.column({
+            header: 'Name',
+            field: 'name',
+            width: 'shrink',
+            maxWidth: '42ch',
+          }),
+        ],
+        auto: [
+          columnHelper.column({
+            header: 'Name',
+            field: 'name',
+            width: 'auto',
+            maxWidth: '42ch',
+          }),
+        ],
+      }
+      const expectedWidths: Record = {
+        grow: 'minmax(auto, 42ch)',
+        shrink: 'minmax(0, 42ch)',
+        auto: 'minmax(auto, 42ch)',
+      }
+
+      for (const widthOpt in columns) {
+        expect(getGridTemplateFromColumns(columns[widthOpt])).toEqual([expectedWidths[widthOpt]])
+      }
+    })
+    it('sets a custom property style to define the column grid template', () => {
+      const columnHelper = createColumnHelper<{id: number; name: string}>()
+      const columns = [
+        columnHelper.column({
+          header: 'Name',
+          field: 'name',
+        }),
+      ]
+      const data = [
+        {
+          id: 1,
+          name: 'one',
+        },
+      ]
+      render()
+
+      expect(screen.getByRole('table')).toHaveStyle({
+        '--grid-template-columns': 'minmax(max-content, 1fr)',
+      })
+    })
+  })
 })
diff --git a/src/DataTable/column.ts b/src/DataTable/column.ts
index 6a01fdeec18..7294177bcc0 100644
--- a/src/DataTable/column.ts
+++ b/src/DataTable/column.ts
@@ -2,6 +2,7 @@ import {ObjectPaths} from './utils'
 import {UniqueRow} from './row'
 import {SortStrategy, CustomSortStrategy} from './sorting'
 
+export type ColumnWidth = 'grow' | 'shrink' | 'auto' | React.CSSProperties['width']
 export interface Column {
   id?: string
 
@@ -21,6 +22,18 @@ export interface Column {
    */
   field?: ObjectPaths
 
+  /**
+   * The minimum width the column can shrink to
+   */
+  // TODO: uncomment ResponsiveValue when I'm ready to implement the responsive part
+  maxWidth?: React.CSSProperties['maxWidth'] /*| ResponsiveValue*/
+
+  /**
+   * The maximum width the column can grow to
+   */
+  // TODO: uncomment ResponsiveValue when I'm ready to implement the responsive part
+  minWidth?: React.CSSProperties['minWidth'] /*| ResponsiveValue*/
+
   /**
    * Provide a custom component or render prop to render the data for this
    * column in a row
@@ -38,6 +51,16 @@ export interface Column {
    * specific sort strategy or custom sort strategy
    */
   sortBy?: boolean | SortStrategy | CustomSortStrategy
+
+  /**
+   * Controls the width of the column.
+   * - 'grow': Stretch to fill available space, and min width is the width of the widest cell in the column
+   * - 'shrink': Stretch to fill available space or shrink to fit in the available space. Allows the column to shrink smaller than the cell content's width.
+   * - 'auto': The column is the width of it’s widest cell. Not intended for use with columns who’s content length varies a lot because a layout shift will occur when the content changes
+   * - explicit width: Will be exactly that width and will not grow or shrink to fill the parent
+   * @default 'grow'
+   */
+  width?: ColumnWidth
 }
 
 export function createColumnHelper() {
diff --git a/src/DataTable/storyHelpers.ts b/src/DataTable/storyHelpers.ts
new file mode 100644
index 00000000000..0781b255b0e
--- /dev/null
+++ b/src/DataTable/storyHelpers.ts
@@ -0,0 +1,52 @@
+import {InputType} from '@storybook/csf'
+
+// Keeping this generic because we can't know how many columns there will be,
+// so we can't know all the possible width keys (for example. 'colWidth1')
+export type ColWidthArgTypes = Record
+
+export const getColumnWidthArgTypes = (colCount: number) => {
+  const argTypes: InputType = {}
+  for (let i = 0; i <= colCount - 1; i++) {
+    argTypes[`colWidth${i}`] = {
+      name: `column ${i + 1} width`,
+      control: {
+        type: 'radio',
+      },
+      defaultValue: 'grow',
+      options: ['grow', 'shrink', 'auto', 'explicit width'],
+      table: {
+        category: 'Column widths',
+      },
+    }
+    argTypes[`explicitColWidth${i}`] = {
+      name: `column ${i + 1} explicit width`,
+      control: {
+        type: 'text',
+      },
+      defaultValue: '200px',
+      if: {arg: `colWidth${i}`, eq: 'explicit width'},
+      table: {
+        category: 'Column widths',
+      },
+    }
+    argTypes[`minColWidth${i}`] = {
+      name: `column ${i + 1} min width`,
+      control: {
+        type: 'text',
+      },
+      table: {
+        category: 'Column widths',
+      },
+    }
+    argTypes[`maxColWidth${i}`] = {
+      name: `column ${i + 1} max width`,
+      control: {
+        type: 'text',
+      },
+      table: {
+        category: 'Column widths',
+      },
+    }
+  }
+  return argTypes
+}
diff --git a/src/DataTable/useTable.ts b/src/DataTable/useTable.ts
index 43c71fc2c17..149b2c3eaa2 100644
--- a/src/DataTable/useTable.ts
+++ b/src/DataTable/useTable.ts
@@ -17,6 +17,7 @@ interface Table {
   actions: {
     sortBy: (header: Header) => void
   }
+  gridTemplateColumns: React.CSSProperties['gridTemplateColumns']
 }
 
 interface Header {
@@ -41,6 +42,48 @@ interface Cell {
 
 type ColumnSortState = {id: string | number; direction: Exclude} | null
 
+export function getGridTemplateFromColumns(columns: Array>): string[] {
+  return columns.map(column => {
+    const columnWidth = column.width ?? 'grow'
+    let minWidth = 'auto'
+    let maxWidth = '1fr'
+
+    if (columnWidth === 'auto') {
+      maxWidth = 'auto'
+    }
+
+    // Setting a min-width of 'max-content' ensures that the column will grow to fit the widest cell's content.
+    // However, If the column has a max width, we can't set the min width to `max-content` because
+    // the widest cell's content might overflow the container.
+    if (columnWidth === 'grow' && !column.maxWidth) {
+      minWidth = 'max-content'
+    }
+
+    // Column widths set to "shrink" don't need a min width unless one is explicitly provided.
+    if (columnWidth === 'shrink') {
+      minWidth = '0'
+    }
+
+    // If a consumer passes `minWidth` or `maxWidth`, we need to override whatever we set above.
+    if (column.minWidth) {
+      minWidth = typeof column.minWidth === 'number' ? `${column.minWidth}px` : column.minWidth
+    }
+
+    if (column.maxWidth) {
+      maxWidth = typeof column.maxWidth === 'number' ? `${column.maxWidth}px` : column.maxWidth
+    }
+
+    // If a consumer is passing one of the shorthand widths or doesn't pass a width at all, we use the
+    // min and max width calculated above to create a minmax() column template value.
+    if (typeof columnWidth !== 'number' && ['grow', 'shrink', 'auto'].includes(columnWidth)) {
+      return minWidth === maxWidth ? minWidth : `minmax(${minWidth}, ${maxWidth})`
+    }
+
+    // If we reach this point, the consumer is passing an explicit width value.
+    return typeof columnWidth === 'number' ? `${columnWidth}px` : columnWidth
+  })
+}
+
 export function useTable({
   columns,
   data,
@@ -192,6 +235,7 @@ export function useTable({
     actions: {
       sortBy,
     },
+    gridTemplateColumns: getGridTemplateFromColumns(columns).join(' '),
   }
 }