介绍

查看源码
基于 TanStack Table 的声明式数据表格,含列配置、选择、展开、树形、分页、加载更多与完整 API 参考。

简介

MDataTable 是基于 @tanstack/vue-table 与 Nuxt UI UTable 封装的声明式数据表格。它把列定义、排序、列固定、列宽拖拽、选择、展开、树形、分页与加载更多统一收敛到一个 columns 数组与少量 props,并提供完整的 TypeScript 类型推断。

核心理念:
  • 声明式列 —— 用对象数组描述列,数据列、选择列、索引列、展开列、操作列、分组列等通过 accessorKeytype 区分。
  • 全局 + 列级开关 —— sortablepinableresizabletruncatetooltip 等既可全局启用,也可按列覆盖或传函数动态决定。
  • 类型安全 —— 列、单元格回调、事件处理均由 DataTableColumn<T>DataTableProps<T> 等类型驱动。

用法

一个 columns 数组即可声明选择列、索引列、固定列、单元格渲染与操作列,再配合 row-key 标识行、sortable/pinable/resizable 启用排序与列固定切换、列宽拖拽、v-model:pagination 分页,无需逐项拼装即得到完整能力:

姓名
#
部门
岗位
状态
薪资
操作
李勇
1
产品
UI 设计师
休假
¥8,257
王超
2
市场
运营专员
已离职
¥8,514
陈娜
3
设计
全栈工程师
在职
¥8,771
刘丽
4
运营
数据分析师
休假
¥9,028
杨军
5
研发
后端工程师
已离职
¥9,285
黄涛
6
产品
产品经理
在职
¥9,542
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
import { UBadge } from '#components'

const data = makePeople(40)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 6 })
const toast = useToast()
const notify = (msg: string): void => { toast.add({ title: msg, duration: 1500 }) }

const STATUS_LABEL: Record<Person['status'], string> = {
  active: '在职',
  leave: '休假',
  offboarded: '已离职'
}
const STATUS_COLOR: Record<Person['status'], 'success' | 'warning' | 'neutral'> = {
  active: 'success',
  leave: 'warning',
  offboarded: 'neutral'
}

const statusCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => {
  const v = getValue<Person['status']>()
  return h(UBadge, { color: STATUS_COLOR[v], variant: 'subtle' }, () => STATUS_LABEL[v])
}
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { type: 'selection' },
  { type: 'index' },
  { accessorKey: 'name', header: '姓名', size: 120, fixed: 'left', pinable: true },
  { accessorKey: 'department', header: '部门', size: 100 },
  { accessorKey: 'role', header: '岗位', size: 160, resizable: true },
  { accessorKey: 'status', header: '状态', size: 100, cell: statusCell },
  { accessorKey: 'salary', header: '薪资', size: 120, align: 'right', cell: moneyCell },
  {
    type: 'actions',
    size: 100,
    maxInline: 2,
    actions: [
      { key: 'edit', buttonProps: { icon: 'i-lucide-pencil', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`编辑 ${row.name}`) },
      { key: 'delete', buttonProps: { icon: 'i-lucide-trash-2', variant: 'ghost', color: 'error', size: 'xs' }, onClick: ({ row }) => notify(`删除 ${row.name}`) }
    ]
  }
]
</script>

<template>
  <MDataTable
    v-model:pagination="pagination"
    row-key="id"
    :columns="columns"
    :data="data"
    sortable
    bordered
  />
</template>

示例数据

本章所有示例的 data 均由以下工具函数派生,可直接复制到项目中复用:

app/composables/useTableMock.ts
export interface Person {
  id: string
  name: string
  email: string
  department: '研发' | '设计' | '产品' | '运营' | '市场'
  role: string
  level?: 'P5' | 'P6' | 'P7' | 'P8'
  status: 'active' | 'leave' | 'offboarded'
  salary: number
  joinedAt: string
  address: string
  bio: string
  children?: Person[]
}

const FIRST = ['', '', '', '', '', '', '', '', '', '', '', '']
const LAST = ['', '', '', '', '', '', '', '', '', '', '', '', '', '']
const DEPARTMENTS: Person['department'][] = ['研发', '设计', '产品', '运营', '市场']
const ROLES = ['前端工程师', '后端工程师', '全栈工程师', 'UI 设计师', '产品经理', '数据分析师', '运营专员']
const LEVELS: Person['level'][] = ['P5', 'P6', 'P7', 'P8']
const STATUS: Person['status'][] = ['active', 'leave', 'offboarded']
const ADDRESSES = [
  '上海市浦东新区张江高科技园区博云路 2 号 IBM 大厦 7 层 708',
  '北京市海淀区中关村大街 27 号中关村大厦 A 座 1502',
  '深圳市南山区科技园南区高新南九道 9 号深圳湾科技生态园 10 栋 B 座 2301',
  '广州市天河区珠江新城华夏路 16 号富力盈凯广场 38 楼',
  '杭州市余杭区文一西路 969 号阿里巴巴西溪园区 6 号楼 318',
  '成都市高新区天府大道中段 1199 号天府软件园 D 区 5 号楼 1207',
  '南京市建邺区江东中路 222 号高科荣域大厦 19 层',
  '武汉市洪山区光谷大道 70 号光谷国际企业中心 3 期 B 座 1808'
]
const BIO_TEMPLATES = [
  '在跨境电商团队负责供应链与履约链路建设,主导过日均百万单的实时调度系统重构,关注稳定性与成本平衡。',
  '长期投入大数据平台与离线计算调度,搭建过 PB 级数据仓库与数据治理体系,对数据质量与权限治理有较强经验。',
  '聚焦设计系统与组件库建设,推动跨产品视觉一致性,主导多次 Design Tokens 与暗色主题落地。',
  '负责自研低代码平台与 BFF 中间层,输出过若干内部脚手架与 CLI 工具,关注研发效率与可维护性。',
  '深耕实时音视频与 WebRTC 互动场景,主导过千万级直播间的延迟优化与弱网降级方案。',
  '在 To B SaaS 团队负责权限、计费与多租户体系,对 RBAC、ABAC 与审计合规有完整落地经验。',
  '负责风控与反作弊算法工程化,搭建过实时特征平台与规则引擎,覆盖支付、营销、社交多场景。',
  '聚焦移动端性能优化与跨端渲染框架,主导过启动耗时、内存与包体积的全链路治理。'
]

function pick<T>(list: readonly T[], seed: number): T {
  return list[seed % list.length] as T
}

export function makePerson(seed: number): Person {
  const first = pick(FIRST, seed)
  const last = pick(LAST, seed * 31 + 7)
  const dept = pick(DEPARTMENTS, seed * 7)
  return {
    id: `P${String(seed).padStart(4, '0')}`,
    name: `${first}${last}`,
    email: `user${seed}@movk.dev`,
    department: dept,
    role: pick(ROLES, seed * 3),
    level: pick(LEVELS, seed * 11),
    status: pick(STATUS, seed * 13),
    salary: 8000 + (seed * 257) % 50000,
    joinedAt: new Date(2018 + (seed % 7), seed % 12, 1 + (seed % 27)).toISOString().slice(0, 10),
    address: pick(ADDRESSES, seed * 17),
    bio: pick(BIO_TEMPLATES, seed * 19)
  }
}

export function makePeople(count: number, offset = 0): Person[] {
  return Array.from({ length: count }, (_, i) => makePerson(offset + i + 1))
}

export function makePeopleTree(rootCount = 5, childPerRoot = 3, depth = 1): Person[] {
  const build = (seed: number, level: number): Person => {
    const person = makePerson(seed)
    if (level >= depth) return person
    return {
      ...person,
      role: '团队负责人',
      level: undefined,
      children: Array.from({ length: childPerRoot }, (_, j) =>
        build(seed * 100 + j + 1, level + 1))
    }
  }
  return Array.from({ length: rootCount }, (_, i) => build(i + 1, 0))
}

API

Props

Prop Default Type
as'div'any

The element or component this component should render as.

rowKeystring & {} | keyof T & string

行唯一标识字段,自动派生 getRowId;与 getRowId 同传时后者优先

columnsDataTableColumn<T>[]
borderedfalseboolean | { color?: string; width?: string; style?: "solid" | "dashed" | "dotted" | "double"; }

纵向边框,传对象可定制 color/width/style

emptyCell'-'string | false | (props: CellContext<T, unknown>): any

空值占位符

pinablefalseboolean | ((col: DataTableDataColumn<T>) => boolean)

启用列固定按钮,传函数可按列动态决定

pinButtonPropsButtonProps | (ctx: DataTablePinButtonContext<T>): ButtonProps
sortablefalseboolean | ((col: DataTableDataColumn<T>) => boolean)

启用列排序,传函数可按列动态决定

sortButtonPropsButtonProps | (ctx: DataTableSortButtonContext<T>): ButtonProps
actionButtonPropsButtonProps | (ctx: DataTableActionButtonContext<T>): ButtonProps

全局 action 按钮 props,与列级 action.buttonProps 深度合并,列级优先

actionsMaxInline3number

行内最多展示多少 action 按钮,超出折叠到 overflow

actionsOverflowTriggerButtonProps | (ctx: CellContext<T, unknown>): ButtonProps
resizablefalseboolean | ((col: DataTableDataColumn<T>) => boolean)

启用列宽拖拽,传函数可按列动态决定

columnResizeMode'onChange'"onChange" | "onEnd"
  • 'onChange' 拖动中实时重排
  • 'onEnd' 释放后才更新
density"compact" | "normal" | "comfortable" | { th?: string | ((cell: Header<T, unknown>) => string); td?: string | ((cell: Cell<T, unknown>) => string); }

单元格内边距密度

metaTableMeta<T>
rowClassstring | (row: T): string
rowStylestring | Record<string, string> | (row: T): string | Record<string, string>
tooltipfalsenumber | false | true | (ctx: CellContext<T, unknown>): number | boolean

单元格溢出 Tooltip:true 单行 / number 多行 / false 禁用 / 函数 动态

tooltipPropsOmitByKey<TooltipProps, "text">
truncatetruenumber | false | true | (ctx: CellContext<T, unknown>): number | boolean

单元格文本截断:true 单行 / number 多行 / false 禁用 / 函数 动态

sortingOptionsOmit<SortingOptions<T>, "getSortedRowModel" | "onSortingChange">
columnSizingOptionsOmit<ColumnSizingOptions, "onColumnSizingChange" | "onColumnSizingInfoChange">
columnPinningOptionsOmit<ColumnPinningOptions, "onColumnPinningChange">
rowSelectionOptionsOmit<RowSelectionOptions<T>, "onRowSelectionChange">
childrenKeystring & {} | keyof T & string

子行字段名,设置后启用树形模式

indentSize'1rem'string | number | (ctx: CellContext<T, unknown>): string

树形缩进:number 每层缩进 px / string CSS 值 / 函数 动态返回 CSS

defaultExpandedfalsenumber | false | true | (row: T, depth: number): boolean

树形模式下的默认展开行为,仅在未提供 expanded / expandedKeys 时生效。true 展开全部父级行,number 展开 depth 小于该值的父级行,函数按行与深度自定义

expandedOptionsOmit<ExpandedOptions<T>, "getExpandedRowModel" | "onExpandedChange">
columnVisibilityKeysstring[]

可见列白名单(数组形)

columnVisibilityExcludeKeysstring[]

隐藏列黑名单(数组形),与 columnVisibilityKeys 互斥,同传时白名单优先

rowSelectionKeysstring[]

选中行 id 列表(数组形)

expandedKeysstring[]

展开行 id 列表(数组形)

onSelectDataTableSelectHandler<T>
onHoverDataTableHoverHandler<T>
onRowContextmenu(e: Event, row: TableRow<T>): void | ((e: Event, row: TableRow<T>) => void)[]
paginationOptionsOmit<PaginationOptions, "onPaginationChange">

分页配置,透传给 TanStack / UTable

  • 客户端分页:传入即启用,自动注入 getPaginationRowModel
  • 服务端分页:manualPagination=true 并提供 rowCount 或 pageCount
stickytrueboolean | "header" | "footer"

粘性表头

paginationUiDataTablePaginationUi & { ui?: { root?: SlotClass; summary?: SlotClass; summaryText?: SlotClass; selectedCount?: SlotClass; actions?: SlotClass; pageSizeSelect?: SlotClass; pagination?: SlotClass; }; }
loadMore() => void | Promise<void>

触底加载回调,传入即启用无限滚动模式(自动隐藏内置分页、async 期间派生 loading)

loadMoreDistance100number

触发 loadMore 的距底像素阈值

dataT[]
captionstring
virtualizefalseboolean | (Partial<Omit<VirtualizerOptions<Element, Element>, "count" | "estimateSize" | "overscan">> & { getScrollElement?: () => Element; overscan?: number; estimateSize?: number | ((index: number) => number); })

Enable virtualization for large datasets. Note: row pinning is not supported when virtualization is enabled.

emptyt('table.noData')string

The text to display when the table is empty.

loadingColor'primary'"primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral"
loadingAnimation'carousel'"carousel" | "carousel-inverse" | "swing" | "elastic"
watchOptions{ deep: true }WatchOptions<boolean>

Use the watchOptions prop to customize reactivity (for ex: disable deep watching for changes in your data or limiting the max traversal depth). This can improve performance by reducing unnecessary re-renders, but it should be used with caution as it may lead to unexpected behavior if not managed properly.

globalFilterOptionsOmit<GlobalFilterOptions<T>, "onGlobalFilterChange">
columnFiltersOptionsOmit<ColumnFiltersOptions<T>, "getFilteredRowModel" | "onColumnFiltersChange">
visibilityOptionsOmit<VisibilityOptions, "onColumnVisibilityChange">
groupingOptionsOmit<GroupingOptions, "onGroupingChange">
rowPinningOptionsOmit<RowPinningOptions<T>, "onRowPinningChange">
facetedOptionsFacetedOptions<T>
statePartial<TableState>
onStateChange(updater: Updater<TableState>) => void
renderFallbackValueany
_featuresTableFeature<any>[]

An array of extra features that you can add to the table instance.

defaultColumnPartial<ColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<ColumnDefBase<T, unknown> & IdIdentifier<T, unknown>> | Partial<GroupColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<GroupColumnDefBase<T, unknown> & IdIdentifier<T, unknown>> | Partial<AccessorKeyColumnDefBase<T, unknown> & Partial<StringHeaderIdentifier>> | Partial<AccessorKeyColumnDefBase<T, unknown> & Partial<IdIdentifier<T, unknown>>> | Partial<AccessorFnColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<AccessorFnColumnDefBase<T, unknown> & IdIdentifier<T, unknown>>

Default column options to use for all column defs supplied to the table.

getRowId(originalRow: T, index: number, parent?: Row<T>) => string

This optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with . using their grandparents' index eg. index.index.index). If you need to identify individual rows that are originating from any server-side operations, it's suggested you use this function to return an ID that makes sense regardless of network IO/ambiguity eg. a userId, taskId, database ID field, etc.

getSubRows(originalRow: T, index: number) => T[]

This optional function is used to access the sub rows for any given row. If you are using nested rows, you will need to use this function to return the sub rows object (or undefined) from the row.

initialStateInitialTableState

Use this option to optionally pass initial state to the table. This state will be used when resetting various table states either automatically by the table (eg. options.autoResetPageIndex) or via functions like table.resetRowSelection(). Most reset function allow you optionally pass a flag to reset to a blank/default state instead of the initial state.

Table state will not be reset when this object changes, which also means that the initial state object does not need to be stable.

mergeOptions(defaultOptions: TableOptions<T>, options: Partial<TableOptions<T>>) => TableOptions<T>

This option is used to optionally implement the merging of table options.

cellpaddingstring | number
cellspacingstring | number
summarystring
widthstring | number
paginationPaginationState
columnVisibilityVisibilityState
columnPinningColumnPinningState
columnSizingColumnSizingState
rowSelectionRowSelectionState
rowPinning{ top: [], bottom: [] }RowPinningState
sorting[]SortingState
expandedtrue | Record<string, boolean>
loadingboolean
stripefalseboolean

斑马纹

fitContentfalseboolean

表格宽度由列宽内容决定(w-fit)

expandOnRowClickfalseboolean
selectOnRowClickfalseboolean
canLoadMoretrueboolean

是否还能加载更多

autoResetAllboolean

Set this option to override any of the autoReset... feature options.

debugAllboolean

Set this option to true to output all debugging information to the console.

debugCellsboolean

Set this option to true to output cell debugging information to the console.

debugColumnsboolean

Set this option to true to output column debugging information to the console.

debugHeadersboolean

Set this option to true to output header debugging information to the console.

debugRowsboolean

Set this option to true to output row debugging information to the console.

debugTableboolean

Set this option to true to output table debugging information to the console.

uiRecord<string, ClassNameValue> & { wrapper?: SlotClass; base?: SlotClass; tbody?: SlotClass; th?: SlotClass; td?: SlotClass; root?: SlotClass; caption?: SlotClass; thead?: SlotClass; tfoot?: SlotClass; tr?: SlotClass; separator?: SlotClass; empty?: SlotClass; loading?: SlotClass; }

Emits

Event Type
update:pagination[value: PaginationState]
update:columnVisibility[value: VisibilityState]
update:columnPinning[value: ColumnPinningState]
update:columnSizing[value: ColumnSizingState]
update:rowSelection[value: RowSelectionState]
update:rowPinning[value: RowPinningState]
update:sorting[value: SortingState]
update:expanded[value: ExpandedState]
update:columnVisibilityKeys[value: string[]]
update:columnVisibilityExcludeKeys[value: string[]]
update:rowSelectionKeys[value: string[]]
update:expandedKeys[value: string[]]

Slots

Slot Type
expanded{ row: Row<T>; }
emptyany
loadingany
captionany
body-topany
body-bottomany
paginationDataTablePaginationSlotProps<T>
pagination-summary{ summaryText: string; selectedText: string; selectedCount: number; rowCount: number; rowCountKnown: boolean; from: number; to: number; page: number; pageCount: number; showSelectedCount: boolean; }
pagination-actions{ tableApi: Table<T>; page: number; pageCount: number; pageSize: number; rowCount: number; pageSizes: number[]; pageSizeOptions: { label: string; value: number; }[]; showPageSizeSelect: boolean; setPage: (page: number) => void; setPageSize: (pageSize: unknown) => void; }

Expose

通过 useTemplateRef 访问类型化的组件实例(DataTableExposed<T>)。

NameType
tableRefHTMLTableElement | null

原生 table 元素

tableApiTable<T> | null

TanStack Table 实例

elHTMLElement | null

UTable 根元素(滚动容器)

scrollToTop(options?)void

滚动到顶部

clearSelection()void

清空行选择

expandToDepth(depth)void

展开到指定层级,depth=0 等价收起全部

collapseAll()void

收起全部行

treeSelectionTreeSelectionResult<T>

树形选中派生:selectedleavesparentshalfSelectedstrictlyChecked

列类型

columns 数组的每一项是 DataTableColumn<T>,按形态区分:

类型判定说明
DataTableDataColumn<T>accessorKey数据列:排序、固定、截断、tooltip、可见性、自定义渲染
DataTableGroupColumn<T>children分组表头
DataTableSelectionColumn<T>type: 'selection'选择列(单/多选、树形策略)
DataTableIndexColumn<T>type: 'index'索引列
DataTableExpandColumn<T>type: 'expand'展开列
DataTableRowPinningColumn<T>type: 'row-pinning'行固定列
DataTableActionsColumn<T>type: 'actions'操作列

事件处理类型

事件处理器使用以下类型,参数由 contextual typing 推断:

类型用途
DataTableSelectHandler<T>@select
DataTableHoverHandler<T>@hover
DataTableContextmenuHandler<T>@row-contextmenu

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    dataTable: {
      slots: {
        wrapper: 'flex flex-col min-h-0 relative',
        base: 'border-separate border-spacing-0',
        tbody: 'divide-y-0',
        th: 'data-[pinned]:sticky [&:is([data-pinned=left],[data-pinned=right])]:bg-default [&:is([data-pinned=left],[data-pinned=right])]:[--m-dt-pin-shadow-color:color-mix(in_oklab,var(--ui-text-muted)_25%,transparent)] data-[pinned]:[--m-dt-pin-shadow:0_0_0_0_transparent] data-[pinned]:shadow-[var(--m-dt-pin-shadow)] [[data-pin-start]_&[data-pinned=left]:not(:has(+[data-pinned=left]))]:[--m-dt-pin-shadow:8px_0_10px_-8px_var(--m-dt-pin-shadow-color)] [[data-pin-end]_:not([data-pinned=right])+&[data-pinned=right]]:[--m-dt-pin-shadow:-8px_0_10px_-8px_var(--m-dt-pin-shadow-color)]',
        td: 'empty:p-0 [[data-slot=tr]+[data-slot=tr]>&]:border-t [[data-slot=tr]+[data-slot=tr]>&]:border-default data-[pinned]:sticky [&:is([data-pinned=left],[data-pinned=right])]:bg-default [&:is([data-pinned=left],[data-pinned=right])]:[--m-dt-pin-shadow-color:color-mix(in_oklab,var(--ui-text-muted)_25%,transparent)] data-[pinned]:[--m-dt-pin-shadow:0_0_0_0_transparent] data-[pinned]:shadow-[var(--m-dt-pin-shadow)] [[data-pin-start]_&[data-pinned=left]:not(:has(+[data-pinned=left]))]:[--m-dt-pin-shadow:8px_0_10px_-8px_var(--m-dt-pin-shadow-color)] [[data-pin-end]_:not([data-pinned=right])+&[data-pinned=right]]:[--m-dt-pin-shadow:-8px_0_10px_-8px_var(--m-dt-pin-shadow-color)] [[data-selected=true]>&[data-pinned]:not([data-pinned=left]):not([data-pinned=right])]:bg-[color:color-mix(in_oklab,var(--ui-bg-elevated)_50%,transparent)] [[data-selected=true]>&:is([data-pinned=left],[data-pinned=right])]:bg-[color:color-mix(in_oklab,var(--ui-bg-elevated)_50%,var(--ui-bg))] [[data-slot=tr][data-selectable=true]:hover>&:is([data-pinned=left],[data-pinned=right])]:bg-[color:color-mix(in_oklab,var(--ui-bg-elevated)_50%,var(--ui-bg))]'
      },
      variants: {
        fitContent: {
          true: {
            base: 'w-fit min-w-0'
          }
        },
        bordered: {
          true: {
            root: 'border-[length:var(--m-dt-border-width,1px)] border-[color:var(--m-dt-border-color,var(--ui-border))] [border-style:var(--m-dt-border-style,solid)] rounded-[var(--ui-radius)]',
            th: 'not-last:not-data-[pinned=left]:not-data-[pinned=right]:border-r-[length:var(--m-dt-border-width,1px)] not-last:not-data-[pinned=left]:not-data-[pinned=right]:border-r-[color:var(--m-dt-border-color,var(--ui-border))] not-last:not-data-[pinned=left]:not-data-[pinned=right]:[border-right-style:var(--m-dt-border-style,solid)] data-[pinned=left]:shadow-[inset_-1px_0_0_var(--m-dt-border-color,var(--ui-border)),var(--m-dt-pin-shadow)] [[data-slot=tr]:has([colspan])>&]:border-b-[length:var(--m-dt-border-width,1px)] [[data-slot=tr]:has([colspan])>&]:border-b-[color:var(--m-dt-border-color,var(--ui-border))] [[data-slot=tr]:has([colspan])>&]:[border-bottom-style:var(--m-dt-border-style,solid)]',
            td: '[[data-slot=tr]+[data-slot=tr]>&]:border-t-[length:var(--m-dt-border-width,1px)] [[data-slot=tr]+[data-slot=tr]>&]:border-t-[color:var(--m-dt-border-color,var(--ui-border))] [[data-slot=tr]+[data-slot=tr]>&]:[border-top-style:var(--m-dt-border-style,solid)] not-last:not-data-[pinned=left]:not-data-[pinned=right]:border-r-[length:var(--m-dt-border-width,1px)] not-last:not-data-[pinned=left]:not-data-[pinned=right]:border-r-[color:var(--m-dt-border-color,var(--ui-border))] not-last:not-data-[pinned=left]:not-data-[pinned=right]:[border-right-style:var(--m-dt-border-style,solid)] data-[pinned=left]:shadow-[inset_-1px_0_0_var(--m-dt-border-color,var(--ui-border)),var(--m-dt-pin-shadow)]'
          }
        },
        striped: {
          true: {
            td: '[[data-slot=tr]:nth-child(even)>&[data-pinned]:not([data-pinned=left]):not([data-pinned=right])]:bg-[color:color-mix(in_oklab,var(--ui-bg-accented)_10%,transparent)] [[data-slot=tr]:nth-child(even)>&:is([data-pinned=left],[data-pinned=right])]:bg-[color:color-mix(in_oklab,var(--ui-bg-accented)_10%,var(--ui-bg))]'
          }
        },
        tree: {
          true: {
            tr: 'has-[>[data-slot=td]:only-child:empty]:hidden'
          }
        },
        resizing: {
          true: {
            base: 'cursor-col-resize! select-none! [&_*]:cursor-col-resize! [&_*]:select-none!'
          }
        }
      }
    }
  }
})

Changelog

No recent changes
Copyright © 2025 - 2026 YiXuan - MIT License