---
title: "Rows & Interaction"
description: "Row selection, expandable detail slot, click behaviors, dynamic row class/style, hover, context menu and imperative control."
canonical_url: "https://nuxt.mhaibaraai.cn/en/docs/data-table/rows"
---
# Rows & Interaction

> Row selection, expandable detail slot, click behaviors, dynamic row class/style, hover, context menu and imperative control.

## `rowKey` Row Identifier

`row-key` specifies the unique identifier field for a row and automatically derives TanStack's `getRowId`. It is the foundation for selection, expansion, tree data and other features to correctly track rows. Once set, selection and expansion state are tracked by the business id rather than the array index — reordering `data` keeps the selected rows pointing to the same records:

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

const rows = ref<Person[]>(makePeople(6))
const selection = ref<RowSelectionState>({})

const columns: DataTableColumn<Person>[] = [
  { type: 'selection' },
  { accessorKey: 'id', header: '工号' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' }
]

function shuffle(): void {
  rows.value = [...rows.value].sort(() => Math.random() - 0.5)
}
</script>

<template>
  <div class="flex flex-col gap-3">
    <div>
      <UButton size="xs" variant="soft" icon="i-lucide-shuffle" @click="shuffle">
        打乱数据
      </UButton>
    </div>
    <MDataTable v-model:row-selection="selection" row-key="id" :columns="columns" :data="rows" />
    <pre class="text-xs p-3 rounded-md bg-muted overflow-auto">selection: {{ selection }}</pre>
  </div>
</template>
```

## Basic Row Selection

`v-model:row-selection` bi-directionally syncs the selected id set; the `@select` event returns the currently clicked row (`DataTableSelectHandler`):

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

const data = makePeople(10)
const selection = ref<RowSelectionState>({})
const lastSelected = ref('')

const columns: DataTableColumn<Person>[] = [
  { type: 'selection' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
]

const onSelect: DataTableSelectHandler<Person> = (_e, row) => {
  lastSelected.value = row.original.name
}
</script>

<template>
  <div class="flex flex-col gap-2">
    <MDataTable
      v-model:row-selection="selection"
      row-key="id"
      :columns="columns"
      :data="data"
      :ui="{ root: 'max-h-[50vh]' }"
      @select="onSelect"
    />
    <p class="text-xs text-muted">
      最近点击：{{ lastSelected || '（无）' }}
    </p>
    <pre class="text-xs p-3 rounded-md bg-muted overflow-auto">selection: {{ selection }}</pre>
  </div>
</template>
```

## Row Expansion and Detail Slot

`v-model:expanded` controls expanded row ids; the `#expanded` slot renders the detail area:

> [!NOTE]
> 
> The 
> 
> type: 'expand'
> 
>  column header renders an "expand all / collapse all" button by default — disable it with 
> 
> toggleAll: false

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

const data = makePeople(8)
const expanded = ref<ExpandedState>({})

const columns: DataTableColumn<Person>[] = [
  { type: 'expand' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
]
</script>

<template>
  <MDataTable
    v-model:expanded="expanded"
    row-key="id"
    :columns="columns"
    :data="data"
    :ui="{ root: 'max-h-[50vh]' }"
  >
    <template #expanded="{ row }">
      <div class="px-4 py-3 text-sm bg-elevated/30">
        <p>邮箱 {{ row.original.email }} · 入职 {{ row.original.joinedAt }}</p>
        <p class="text-muted">
          {{ row.original.bio }}
        </p>
      </div>
    </template>
  </MDataTable>
</template>
```

## Row Click for Selection and Expansion

`select-on-row-click` toggles selection on a full-row click; `expand-on-row-click` toggles expansion on a full-row click. Both can be enabled independently:

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

const data = makePeople(8)
const selection = ref<RowSelectionState>({})
const expanded = ref<ExpandedState>({})

const columns: DataTableColumn<Person>[] = [
  { type: 'selection' },
  { type: 'expand' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' }
]
</script>

<template>
  <MDataTable
    v-model:row-selection="selection"
    v-model:expanded="expanded"
    row-key="id"
    :columns="columns"
    :data="data"
    :ui="{ root: 'max-h-[50vh]' }"
  >
    <template #expanded="{ row }">
      <div class="px-4 py-2 text-xs text-muted bg-elevated/30">
        {{ row.original.bio }}
      </div>
    </template>
  </MDataTable>
</template>
```

## Dynamic Row Styles

`row-class` accepts `(row) => string`; `row-style` accepts `(row) => string | Record<string, string>` to derive class names and inline styles from row data:

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

const data = makePeople(10)

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'status', header: '状态' },
  { accessorKey: 'salary', header: '薪资', align: 'right', size: 110 }
]

function rowClassByStatus(row: Person): string {
  if (row.status === 'offboarded') return 'bg-gray-50/40 dark:bg-gray-900/10'
  if (row.status === 'leave') return 'bg-warning-50/40 dark:bg-warning-900/10'
  return ''
}
function rowStyleBySalary(row: Person): Record<string, string> {
  if (row.salary >= 40000) return { fontWeight: '600', color: 'var(--ui-color-error-600, #dc2626)' }
  if (row.salary >= 25000) return { fontWeight: '500' }
  return {}
}
</script>

<template>
  <MDataTable
    row-key="id"
    :columns="columns"
    :data="data"
    :row-class="rowClassByStatus"
    :row-style="rowStyleBySalary"
    :ui="{ root: 'max-h-[50vh]' }"
  />
</template>
```

## Row Hover Tracking

`@hover` fires on row enter and leave (`DataTableHoverHandler`); the second argument is a `TableRow` or `null`, useful for syncing external highlights:

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

const data = makePeople(8)
const hovered = ref<string | null>(null)

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
]

const onHover: DataTableHoverHandler<Person> = (_e, row) => {
  hovered.value = row ? row.original.name : null
}
</script>

<template>
  <div class="flex flex-col gap-2">
    <p class="text-xs text-muted">
      悬停行：{{ hovered ?? '（无）' }}
    </p>
    <MDataTable row-key="id" :columns="columns" :data="data" :ui="{ root: 'max-h-[50vh]' }" @hover="onHover" />
  </div>
</template>
```

## Row Context Menu

`@row-contextmenu` intercepts right-click events (`DataTableContextmenuHandler`); the callback receives the original `MouseEvent` and the target row, allowing you to prevent the default menu and show a custom one:

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

const data = makePeople(8)
const toast = useToast()

const columns: DataTableColumn<Person>[] = [
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
]

const onRowContextmenu: DataTableContextmenuHandler<Person> = (e, row) => {
  e.preventDefault()
  toast.add({ title: `右键 ${row.original.name}`, duration: 1500 })
}
</script>

<template>
  <MDataTable
    row-key="id"
    :columns="columns"
    :data="data"
    :ui="{ root: 'max-h-[50vh]' }"
    @row-contextmenu="onRowContextmenu"
  />
</template>
```

## Imperative Control

Obtain the component instance via `useTemplateRef` and call the exposed `clearSelection()` to clear selection or `scrollToTop()` to scroll back to the top:

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

const data = makePeople(15)
const tableRef = useTemplateRef<DataTableExposed<Person>>('tableRef')
const selection = ref<RowSelectionState>({})

const columns: DataTableColumn<Person>[] = [
  { type: 'selection' },
  { accessorKey: 'name', header: '姓名' },
  { accessorKey: 'department', header: '部门' },
  { accessorKey: 'role', header: '岗位' }
]
</script>

<template>
  <div class="flex flex-col gap-3">
    <div class="flex flex-wrap gap-2">
      <UButton size="xs" variant="soft" icon="i-lucide-eraser" @click="tableRef?.clearSelection()">
        清空选中
      </UButton>
      <UButton size="xs" variant="soft" icon="i-lucide-arrow-up-to-line" @click="tableRef?.scrollToTop({ behavior: 'smooth' })">
        回到顶部
      </UButton>
    </div>
    <MDataTable
      ref="tableRef"
      v-model:row-selection="selection"
      row-key="id"
      :columns="columns"
      :data="data"
      select-on-row-click
      :ui="{ root: 'max-h-[40vh]' }"
    />
  </div>
</template>
```


## Sitemap

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