---
title: "Columns"
description: "Data column configuration — fixed columns, sorting, column pinning, column resize, text truncation, tooltips, visibility and functional per-column configuration."
canonical_url: "https://nuxt.mhaibaraai.cn/en/docs/data-table/columns"
---
# Columns

> Data column configuration — fixed columns, sorting, column pinning, column resize, text truncation, tooltips, visibility and functional per-column configuration.

## `fixed` Fixed Columns

`column.fixed` pins a column to either side of the table, keeping it visible during horizontal scrolling:

```vue [DataTableFixedExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号', fixed: 'left', size: 100 },
  { accessorKey: 'name', header: '姓名', fixed: 'left', size: 120 },
  { accessorKey: 'role', header: '岗位' },
  { accessorKey: 'address', header: '地址' },
  { accessorKey: 'email', header: '邮箱' },
  { accessorKey: 'salary', header: '薪资', fixed: 'right', align: 'right', cell: moneyCell }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data" :ui="{ root: 'max-w-2xl' }" />
</template>
```

## `children` Grouped Headers

Define grouped columns (`DataTableGroupColumn`) using `children` to create multi-level headers:

```vue [DataTableGroupExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(6)
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号' },
  {
    header: '员工信息',
    children: [
      { accessorKey: 'name', header: '姓名' },
      { accessorKey: 'department', header: '部门' },
      { accessorKey: 'role', header: '岗位' }
    ]
  },
  {
    header: '薪酬',
    children: [
      { accessorKey: 'level', header: '职级' },
      { accessorKey: 'salary', header: '薪资', align: 'right', cell: moneyCell }
    ]
  }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data" bordered />
</template>
```

## `sortable` Column Sorting

The global `sortable` enables click-to-sort on all data column headers; a per-column `sortable` overrides the global setting in the opposite direction (e.g. `sortable: false` locks a column as non-sortable):

```vue [DataTableSortableExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号', sortable: true },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'level', header: '职级', sortable: false },
  { accessorKey: 'address', header: '地址' },
  { accessorKey: 'salary', header: '薪资', align: 'right', cell: moneyCell }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data" />
</template>
```

## `pinable` Column Pinning

The global `pinable` lets users click the pin icon on a header to cycle through `left`, `right` and `none`; a per-column `pinable` has higher priority and can force a fixed position:

```vue [DataTablePinableExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号', fixed: 'left', pinable: true },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门', pinable: false },
  { accessorKey: 'role', header: '岗位' },
  { accessorKey: 'salary', header: '薪资', align: 'right', cell: moneyCell }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data" :ui="{ root: 'max-w-3xl' }" />
</template>
```

## `resizable` Column Resize

The global `resizable` adds a drag handle to all data columns; `resizable: false` on a column locks its width. `columnResizeMode` controls whether reflow happens `onChange` (live during drag) or `onEnd` (after release):

```vue [DataTableResizableExample.vue]
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号' },
  { accessorKey: 'name', header: '姓名', minSize: 80 },
  { accessorKey: 'level', header: '职级', resizable: false },
  { accessorKey: 'address', header: '地址', size: 120 },
  { accessorKey: 'bio', header: '个人简介', size: 150, resizable: true }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data" :ui="{ root: 'max-w-3xl' }" />
</template>
```

## `size` / `minSize` / `maxSize` Column Width

`size` sets a fixed column width (a number or a preset `xs`–`xl`). When `size` is not set and only `minSize` / `maxSize` are provided, the column width adapts to content: `minSize` is the lower bound for auto-sizing and dragging; `maxSize` is the maximum draggable width. A column with `size` set is constrained in both directions by `minSize` / `maxSize`.

```ts
const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: 'Name', minSize: 120 },    // auto, min 120
  { accessorKey: 'bio', header: 'Bio', maxSize: 240 },      // auto, drag max 240
  { accessorKey: 'id', header: 'ID', size: 'sm' }           // fixed preset width 120
]
```

> [!NOTE]
> 
> The table uses 
> 
> table-layout: auto
> 
>  and stretches to fill the container; auto-sized column content may exceed 
> 
> maxSize
> 
>  (auto layout ignores cell 
> 
> max-width
> 
> ). Therefore 
> 
> maxSize
> 
>  on an auto-sized column only constrains the drag upper limit. Use 
> 
> size
> 
>  to strictly fix a column width.

> [!NOTE]
> 
> Fixed columns (
> 
> fixed
> 
> ) require a known width to participate in sticky offset calculations, so they default to 
> 
> size
> 
> . Only when a side has a single fixed column (the innermost fixed column closest to the scroll area) can that column auto-size to content — set 
> 
> minSize
> 
>  / 
> 
> maxSize
> 
>  to allow it to stretch (a typical example is the sole actions column on the right).

## Text Truncation and Tooltip

`truncate` controls pure truncation (`true` for single-line / `number` for multi-line / `false` to disable / a function for dynamic behavior); `tooltip` applies the same number of truncation lines and shows the full content in a float when it overflows. You do not need to configure both:

```vue [DataTableTruncateExample.vue]
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'bio', header: '个人简介', size: 240, tooltip: 1 },
  { accessorKey: 'role', header: '角色', size: 60 },
  { accessorKey: 'address', header: '地址', size: 200, truncate: true }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data" :ui="{ root: 'max-w-3xl' }" />
</template>
```

## Column Visibility

`column.visibility` sets the default show/hide state; `columnVisibilityKeys` (allowlist) and `columnVisibilityExcludeKeys` (blocklist) are mutually exclusive — when both are provided the allowlist takes precedence:

```vue [DataTableVisibilityExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(6)
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`
const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' },
  { accessorKey: 'level', header: '职级' },
  { accessorKey: 'email', header: '邮箱' },
  { accessorKey: 'joinedAt', header: '入职日期', visibility: false },
  { accessorKey: 'salary', header: '薪资', align: 'right', cell: moneyCell }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data" />
</template>
```

## Functional Configuration

`sortable`, `pinable` and `resizable` accept `(col) => boolean`; `truncate` and `tooltip` accept `(ctx) => boolean | number`. A single declaration replaces per-column booleans and can make dynamic decisions based on the field or cell context:

```vue [DataTableFunctionalExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn, DataTableProps } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(8)
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'bio', header: '个人简介' },
  { accessorKey: 'address', header: '地址', size: 200 },
  { accessorKey: 'salary', header: '薪资', align: 'right', cell: moneyCell }
]

const sortableFn: DataTableProps<Person>['sortable'] = col => col.accessorKey !== 'id'
const pinableFn: DataTableProps<Person>['pinable'] = col => !['bio', 'address'].includes(col.accessorKey)
const resizableFn: DataTableProps<Person>['resizable'] = col => col.accessorKey !== 'salary'
const truncateFn: DataTableDataColumn<Person>['truncate'] = ctx =>
  ctx.column.id === 'bio' ? (ctx.row.original.bio.length > 45 ? 3 : 2) : true
const tooltipFn: DataTableDataColumn<Person>['tooltip'] = ctx => ctx.column.id === 'address'
</script>

<template>
  <MDataTable
    :columns="columns"
    :data="data"
    :sortable="sortableFn"
    :pinable="pinableFn"
    :resizable="resizableFn"
    :truncate="truncateFn"
    :tooltip="tooltipFn"
  />
</template>
```

> [!TIP]
> 
> Per-column props always take precedence over global props. The recommended pattern is "global on + individual off" or "global off + individual on" to express the difference.

## Header and Cell Slots

Besides configuring `header` / `cell` in `columns`, you can render a column through slots named after its id. The id of a data column is its `accessorKey`; special columns use `__selection`, `__index`, `__expand`, `__row_pinning` and `__actions`:

| Slot | Scope type | Description |
| --- | --- | --- |
| `#<column>-header` | `HeaderContext<T, unknown>` | Replaces the column header label |
| `#<column>-cell` | `CellContext<T, unknown>` | Replaces the column cell content |
| `#<column>-footer` | `HeaderContext<T, unknown>` | Replaces the column footer content |

```vue [DataTableColumnSlotsExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'

const data = makePeople(6)
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'id', header: '工号', size: 100 },
  { accessorKey: 'name', header: '姓名', size: 140, sortable: true },
  { accessorKey: 'department', header: '部门', size: 120 },
  { accessorKey: 'level', header: '职级', size: 120 },
  { accessorKey: 'salary', header: '薪资', align: 'right', size: 140, cell: moneyCell }
]
</script>

<template>
  <MDataTable :columns="columns" :data="data">
    <template #name-header>
      <span class="inline-flex items-center gap-1 truncate">
        <UIcon name="i-lucide-user-round" class="size-4 text-primary" />
        姓名
      </span>
    </template>

    <template #department-cell="{ row }">
      <UBadge color="neutral" variant="subtle" size="sm">
        {{ row.original.department }}
      </UBadge>
    </template>

    <template #level-cell="{ getValue }">
      <UBadge v-if="getValue()" color="primary" variant="soft" size="sm">
        {{ getValue() }}
      </UBadge>
      <span v-else class="text-muted">未定级</span>
    </template>
  </MDataTable>
</template>
```

> [!NOTE]
> 
> #<column>-header
> 
>  only replaces the 
> 
> label
> 
> . The sort button, pin button and resize handle generated by 
> 
> sortable
> 
> , 
> 
> pinable
> 
>  and 
> 
> resizable
> 
>  stay in the header, so you never have to render them yourself.

> [!WARNING]
> 
> #<column>-cell
> 
>  replaces the whole cell content, so 
> 
> truncate
> 
> , 
> 
> tooltip
> 
>  and 
> 
> emptyCell
> 
>  no longer apply to that column — handle them inside the slot when needed. Grouped headers (
> 
> children
> 
> ) get an id assembled by TanStack internally and cannot be overridden by slots; use the 
> 
> header
> 
>  option instead.


## Sitemap

See the full [sitemap](https://nuxt.mhaibaraai.cn/sitemap.md) for all pages.
