Appearance
暗夜模式
通过 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-bg | loading 遮罩 |
--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 / -full | 4 / 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 |
|---|---|---|---|
| 默认 | — | 40px | 40(默认值,可不传) |
| 紧凑 | .vt-compact | 30px | 30 |
| 宽松 | .vt-loose | 50px | 50 |
行高等式
自定义密度时,这三个变量必须凑成整数行高,并把这个数传给 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 = '';
};
}