Skip to content

暗夜模式

通过 theme 选项或 setTheme() 方法显式切换亮色 / 暗色。表格样式基于 .vt-root 上的 --vt-* CSS 变量,暗色下自动套用暗色 token。

除显式切换外,表格处于 .dark 祖先元素下(如 VitePress / Tailwind 暗色)会自动应用暗色,无需手动设置。

使用的 API

ts
type Options = {
  list: T[];
  itemKey: string;
  estimatedSize: number;
  theme?: 'light' | 'dark';   // 初始主题
};

// 实例方法:运行时切换
table.setTheme('dark');
table.setTheme('light');

主题定制(CSS 变量)

表格的所有颜色、圆角、间距、字号、阴影、动效时长都抽离成了 --vt-* 变量,换肤只需覆盖变量,无需覆盖任何选择器

css
/* 全局换肤:写在 .vt-root 或任意祖先(如 :root)上都生效 */
.vt-root {
  --vt-color-primary: #7c3aed; /* 主色一改,悬停底色/选区/focus 环/按钮全部联动 */
  --vt-radius-lg: 14px;
  --vt-cell-padding-y: 12px;
}

悬停底色、选中底色、框选底色、focus 环等派生色默认由 --vt-color-primary 通过 color-mix() 推导,所以只改主色即可整体换色;也可以单独覆盖某个派生色变量。旧浏览器(不支持 color-mix())自动回落到内置静态值。

颜色

变量说明
--vt-color-primary / -hover / -active主色三态
--vt-color-primary-soft / -softer主色浅底(图标悬停、菜单悬停)
--vt-color-danger / -soft--vt-color-warning / -success语义色(校验失败等)
--vt-bg / --vt-bg-elevated表格底色 / 浮层底色
--vt-header-bg / -hover--vt-footer-bg--vt-stripe-bg表头 / 表头悬停 / 表尾 / 斑马纹
--vt-row-hover-bg / --vt-row-selected-bg行悬停 / 行选中
--vt-cell-selection-bg--vt-focus-ring-color区域框选底色、focus 环颜色
--vt-border-color / -light / -strong--vt-border-width分隔线三档(单元格 / 次级 / 表头表尾)
--vt-text-color / -secondary / -placeholder / -disabled文字四档
--vt-text-inverse--vt-header-text-color主色块上的反色文字、表头文字
--vt-fill-color / -light / -hover次级填充(按钮、分组行、展开行)
--vt-scrollbar-thumb / -hover滚动条滑块配色(--vt-scrollbar-size 控粗细)
--vt-scrollbar-track轨道底色,默认 transparent。表体的两条是 overlay 浮层(不占布局空间、随滚动淡入淡出),所以默认无底色;想要「槽」的观感可改成半透明色如 rgb(248 250 252 / 85%),它会跟着滑块一起淡入淡出
--vt-mask-bgloading 遮罩
--vt-highlight-bg / --vt-highlight-border搜索命中高亮
--vt-group-bg / -lv2 / -lv3 / -hover--vt-group-border--vt-group-accent / -width--vt-group-text分组行(分层底色、上下分隔线、左侧标记条)

圆角

变量默认值说明
--vt-radius-sm / -md / -lg / -full4 / 6 / 10 / 999px圆角梯度
--vt-radius-root--vt-radius-lg容器圆角(border 模式下生效)
--vt-radius-control--vt-radius-sm勾选框 / 输入框 / 按钮
--vt-radius-panel--vt-radius-lg筛选面板 / 右键菜单 / 搜索框

间距、字体、尺寸、动效

变量说明
--vt-cell-padding-y / -x--vt-header-padding-y / -x单元格 / 表头内边距(密度)
--vt-font-family--vt-font-size / -sm / -xs字体与字号
--vt-line-height单元格行高,必须是 px,参与下方行高等式
--vt-font-weight-medium / -semibold--vt-font-numeric字重;--vt-font-numeric: normal 可关等宽数字
--vt-control-size--vt-icon-btn-size--vt-indent-size勾选框 / 图标按钮 / 树形缩进
--vt-shadow-rgb--vt-shadow-sm / -md / -lg阴影基色与三档阴影
--vt-fixed-shadow-color / -size固定列阴影
--vt-transition-fast / --vt-transition / --vt-ease动效时长与曲线
--vt-empty-icon / --vt-empty-icon-size空态图标(尺寸设 0 可隐藏)

密度变体

内置紧凑 / 宽松两档(本质就是覆盖内边距与行高变量)。类加在 .vt-root 上或任意外层容器上都生效:

html
<!-- 紧凑:vt-compact,宽松:vt-loose -->
<div class="vt-compact">
  <div id="table-container" style="height: 400px"></div>
</div>

换密度要同时改 estimatedSize

密度只改 CSS,表格的滚动几何却是按 estimatedSize 建的。两者不一致时滚动条长度会失真,十万行量级下还可能滚不到底(见下方「行高等式」)。

密度类名行高要传的 estimatedSize
默认40px40(默认值,可不传)
紧凑.vt-compact30px30
宽松.vt-loose50px50

行高等式

自定义密度时,这三个变量必须凑成整数行高,并把这个数传给 estimatedSize

--vt-line-height + --vt-cell-padding-y × 2 + --vt-border-width(1px) = 行高 = estimatedSize

所以 --vt-line-height 用 px 而不是无单位比例——14px × 1.55 = 21.7px 这种小数行高会让实际渲染高度比 estimatedSize 高出零点几像素,误差按行累积,十万行下足以让滚动位置超出内部模型总高、窗口卡在半路不再前进。

css
/* 自定义密度:36px 行高,配 new VirtTable({ estimatedSize: 36 }) */
#table-container .vt-root {
  --vt-line-height: 21px; /* 21 + 7×2 + 1 = 36 */
  --vt-cell-padding-y: 7px;
  --vt-cell-padding-x: 12px;
}
密度变体为什么走 --vt-density-* 中间变量

内置的 .vt-compact / .vt-loose 设置的是 --vt-density-cell-padding-y 这类中间变量, 而不是直接改 --vt-cell-padding-y。因为 CSS 变量的规则是「元素自己声明的值胜过从祖先继承的」, 而 .vt-root 自己声明了全套 token —— 密度类若也直接声明同名 token,加在外层容器上就会被 root 自己的声明挡住、完全失效(实测:父元素上 --vt-line-height 是 19px,.vt-root 上 仍是 21px,行高照旧 40)。

.vt-root 改成 --vt-cell-padding-y: var(--vt-density-cell-padding-y, 9px) 之后,三种用法 都成立:加在外层容器、加在 root 自己、以及像上面那样直接覆盖最终 token(同元素声明优先级最高)。

深色/浅色作用域

css
/* 暗色 token 生效条件(任一即可) */
.vt-root--dark { }                 /* theme: 'dark' / setTheme('dark') */
.dark .vt-root { }                 /* 处于 .dark 祖先下(VitePress / Tailwind) */
[data-theme='dark'] .vt-root { }   /* 处于 data-theme=dark 祖先下 */

/* 在暗色页面里强制某个表格用亮色 */
.vt-root--light { }

示例

微应用尚未挂载。

源码

点击查看源码
ts
import { faker } from '@faker-js/faker';
import { VirtTable, type VirtTableColumn } from '@virt-table/vanilla';

interface Row {
  id: number;
  name: string;
  email: string;
  city: string;
  score: number;
}

const ROW_COUNT = 1000;

export function bootstrapTableTheme(root: HTMLElement): () => void {
  const columns: VirtTableColumn<Row>[] = [
    { key: 'id', title: 'ID', width: 80, fixed: 'left' },
    { key: 'name', title: '姓名', width: 160 },
    { key: 'email', title: '邮箱', width: 240 },
    { key: 'city', title: '城市', width: 160 },
    { key: 'score', title: '分数', width: 120, align: 'right' },
  ];

  const list: Row[] = Array.from({ length: ROW_COUNT }, (_, i) => ({
    id: i + 1,
    name: faker.person.fullName(),
    email: faker.internet.email(),
    city: faker.location.city(),
    score: faker.number.int({ min: 0, max: 100 }),
  }));

  root.innerHTML = `
    <div class="virt-table-controls">
      <button class="virt-table-btn virt-table-btn-primary" id="btnToggle">🌙 切换暗夜模式</button>
      <span id="themeLabel" class="demo-note">当前:亮色</span>
    </div>
    <div style="width:760px;height:520px;" class="demo-container" id="tableContainer"></div>`;
  const container = root.querySelector('#tableContainer') as HTMLElement;

  const table = new VirtTable<Row>(container, {
    list,
    columns,
    itemKey: 'id',
    estimatedSize: 40,
    buffer: 6,
    border: true,
    stripe: true,
    highlightHoverRow: true,
    theme: 'light',
  });

  let theme: 'light' | 'dark' = 'light';
  const btn = root.querySelector('#btnToggle') as HTMLButtonElement;
  const label = root.querySelector('#themeLabel') as HTMLElement;
  btn.onclick = () => {
    theme = theme === 'light' ? 'dark' : 'light';
    table.setTheme(theme);
    btn.textContent = theme === 'light' ? '🌙 切换暗夜模式' : '☀️ 切换亮色模式';
    label.textContent = theme === 'light' ? '当前:亮色' : '当前:暗色';
  };

  return () => {
    table.destroy();
    root.innerHTML = '';
  };
}