| Subject | From | Date |
|---|---|---|
| Kumo v1.0.0 released | Visal In | 5 seconds ago |
| New Job Offer | Cloudflare | 10 minutes ago |
| Daily Email Digest | Cloudflare | 1 hour ago |
import { LayerCard, Table } from "@cloudflare/kumo";
export function TableBasicDemo() {
return (
<LayerCard className="p-0">
<Table>
<Table.Header>
<Table.Row>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{emailData.slice(0, 3).map((row) => (
<Table.Row key={row.id}>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}安装
桶式导出
import { Table } from "@cloudflare/kumo";细粒度导入
import { Table } from "@cloudflare/kumo/components/table";用法
import { Table, LayerCard } from "@cloudflare/kumo";
export default function Example() {
return (
<LayerCard className="p-0">
<Table>
<Table.Header>
<Table.Row>
<Table.Head>Name</Table.Head>
<Table.Head>Email</Table.Head>
<Table.Head>Role</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>John Doe</Table.Cell>
<Table.Cell>john@example.com</Table.Cell>
<Table.Cell>Admin</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</LayerCard>
);
}示例
带复选框
使用 Table.CheckHead 与 Table.CheckCell 添加行选择。两者都接受
onCheckedChange,其签名与底层 Checkbox 组件一致,并通过第二个参数暴露可选的事件详情。
较早的 onValueChange 属性仍可用,但已废弃 —— lint 规则会予以标记,并将在未来的
主版本中移除。迁移方式为重命名该属性:
// Before (deprecated)
<Table.CheckCell onValueChange={(checked) => toggleRow(id)} />
// After
<Table.CheckCell onCheckedChange={(checked) => toggleRow(id)} />
// With event details
<Table.CheckCell
onCheckedChange={(checked, eventDetails) => {
toggleRow(id);
eventDetails?.event.stopPropagation();
}}
/>| Subject | From | Date | |
|---|---|---|---|
| Kumo v1.0.0 released | Visal In | 5 seconds ago | |
| New Job Offer | Cloudflare | 10 minutes ago | |
| Daily Email Digest | Cloudflare | 1 hour ago |
import { useState } from "react";
import { LayerCard, Table } from "@cloudflare/kumo";
export function TableWithCheckboxDemo() {
const rows = emailData.slice(0, 3);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const toggleRow = (id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
};
const toggleAll = () => {
if (selectedIds.size === rows.length) {
setSelectedIds(new Set());
} else {
setSelectedIds(new Set(rows.map((r) => r.id)));
}
};
return (
<LayerCard className="p-0">
<Table>
<Table.Header>
<Table.Row>
<Table.CheckHead
checked={selectedIds.size === rows.length}
indeterminate={
selectedIds.size > 0 && selectedIds.size < rows.length
}
onCheckedChange={toggleAll}
aria-label="Select all rows"
/>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{rows.map((row) => (
<Table.Row key={row.id}>
<Table.CheckCell
checked={selectedIds.has(row.id)}
onCheckedChange={() => toggleRow(row.id)}
aria-label={`Select ${row.subject}`}
/>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}紧凑表头
在 Table.Header 上使用 variant="compact" 可获得更紧凑的表头样式。
| Subject | From | Date |
|---|---|---|
| Kumo v1.0.0 released | Visal In | 5 seconds ago |
| New Job Offer | Cloudflare | 10 minutes ago |
| Daily Email Digest | Cloudflare | 1 hour ago |
import { LayerCard, Table } from "@cloudflare/kumo";
export function TableWithCompactHeaderDemo() {
return (
<LayerCard className="p-0">
<Table>
<Table.Header variant="compact">
<Table.Row>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{emailData.slice(0, 3).map((row) => (
<Table.Row key={row.id}>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}选中行
在 Table.Row 上使用 variant="selected" 可高亮选中行。
| Subject | From | Date | |
|---|---|---|---|
| Kumo v1.0.0 released | Visal In | 5 seconds ago | |
| New Job Offer | Cloudflare | 10 minutes ago | |
| Daily Email Digest | Cloudflare | 1 hour ago |
import { useState } from "react";
import { LayerCard, Table } from "@cloudflare/kumo";
export function TableSelectedRowDemo() {
const rows = emailData.slice(0, 3);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(["2"]));
const toggleRow = (id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
};
const toggleAll = () => {
if (selectedIds.size === rows.length) {
setSelectedIds(new Set());
} else {
setSelectedIds(new Set(rows.map((r) => r.id)));
}
};
return (
<LayerCard className="p-0">
<Table>
<Table.Header>
<Table.Row>
<Table.CheckHead
checked={selectedIds.size === rows.length}
indeterminate={
selectedIds.size > 0 && selectedIds.size < rows.length
}
onCheckedChange={toggleAll}
aria-label="Select all rows"
/>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{rows.map((row) => (
<Table.Row
key={row.id}
variant={selectedIds.has(row.id) ? "selected" : "default"}
>
<Table.CheckCell
checked={selectedIds.has(row.id)}
onCheckedChange={() => toggleRow(row.id)}
aria-label={`Select ${row.subject}`}
/>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}固定布局与列宽
如需精确控制列宽,可设置 layout="fixed" 并使用带 col 元素的 colgroup。
| Subject | From | Date |
|---|---|---|
| Kumo v1.0.0 released | Visal In | 5 seconds ago |
| New Job Offer | Cloudflare | 10 minutes ago |
| Daily Email Digest | Cloudflare | 1 hour ago |
| GitLab - New Comment | Rob Knecht | 1 day ago |
| Out of Office | Johnnie Lappen | 3 days ago |
import { LayerCard, Table } from "@cloudflare/kumo";
export function TableFixedLayoutDemo() {
return (
<LayerCard className="p-0">
<Table layout="fixed">
<colgroup>
<col />
<col className="w-[150px]" />
<col className="w-[150px]" />
</colgroup>
<Table.Header>
<Table.Row>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{emailData.map((row) => (
<Table.Row key={row.id}>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}固定列
在 Table.Head 与 Table.Cell 上使用 sticky="left" 或 sticky="right",
可将列固定到滚动容器的左缘或右缘。组件会自动添加不透明背景与渐变淡出效果。请将表格
包裹在 overflow-x-auto 容器中。
| Subject | From | Date | Tags | Actions |
|---|---|---|---|---|
| Kumo v1.0.0 released | Visal In | 5 seconds ago | — | |
| New Job Offer | Cloudflare | 10 minutes ago | — | |
| Daily Email Digest | Cloudflare | 1 hour ago | promotion | |
| GitLab - New Comment | Rob Knecht | 1 day ago | — | |
| Out of Office | Johnnie Lappen | 3 days ago | — |
import { Badge, Button, DropdownMenu, LayerCard, Table } from "@cloudflare/kumo";
import { DotsThree, Eye, PencilSimple, Trash } from "@phosphor-icons/react";
/**
* Demonstrates a sticky right-hand actions column that stays pinned while the
* table scrolls horizontally. Uses `sticky="right"` on `Table.Head` and
* `Table.Cell` — the gradient fade and opaque background are applied
* automatically.
*/
export function TableStickyColumnDemo() {
return (
<LayerCard className="w-full max-w-md overflow-x-auto p-0">
<Table>
<Table.Header>
<Table.Row>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
<Table.Head>Tags</Table.Head>
<Table.Head sticky="right">
<span className="sr-only">Actions</span>
</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{emailData.map((row) => (
<Table.Row key={row.id}>
<Table.Cell className="whitespace-nowrap">
{row.subject}
</Table.Cell>
<Table.Cell className="whitespace-nowrap">{row.from}</Table.Cell>
<Table.Cell className="whitespace-nowrap">{row.date}</Table.Cell>
<Table.Cell className="whitespace-nowrap">
{row.tags ? (
<div className="inline-flex gap-1">
{row.tags.map((tag) => (
<Badge key={tag}>{tag}</Badge>
))}
</div>
) : (
"—"
)}
</Table.Cell>
<Table.Cell sticky="right" className="text-right">
<DropdownMenu>
<DropdownMenu.Trigger
render={
<Button
variant="ghost"
size="sm"
shape="square"
aria-label="More options"
>
<DotsThree weight="bold" size={16} />
</Button>
}
/>
<DropdownMenu.Content>
<DropdownMenu.Item icon={Eye}>View</DropdownMenu.Item>
<DropdownMenu.Item icon={PencilSimple}>
Edit
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item icon={Trash} variant="danger">
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}紧凑表头与固定列
在 Table.Header 上组合使用 variant="compact" 与 sticky 固定列。
| Subject | From | Date | Tags | Actions |
|---|---|---|---|---|
| Kumo v1.0.0 released | Visal In | 5 seconds ago | — | |
| New Job Offer | Cloudflare | 10 minutes ago | — | |
| Daily Email Digest | Cloudflare | 1 hour ago | promotion | |
| GitLab - New Comment | Rob Knecht | 1 day ago | — | |
| Out of Office | Johnnie Lappen | 3 days ago | — |
import { Badge, Button, DropdownMenu, LayerCard, Table } from "@cloudflare/kumo";
import { DotsThree, Eye, PencilSimple, Trash } from "@phosphor-icons/react";
/**
* Demonstrates a compact header combined with sticky columns. This combination
* may exhibit visual inconsistencies between the compact header background
* (`bg-kumo-elevated`) and the sticky cell background (`bg-kumo-base`).
*/
export function TableCompactStickyDemo() {
return (
<LayerCard className="w-full max-w-md overflow-x-auto p-0">
<Table>
<Table.Header variant="compact">
<Table.Row>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
<Table.Head>Tags</Table.Head>
<Table.Head sticky="right">
<span className="sr-only">Actions</span>
</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{emailData.map((row) => (
<Table.Row key={row.id}>
<Table.Cell className="whitespace-nowrap">
{row.subject}
</Table.Cell>
<Table.Cell className="whitespace-nowrap">{row.from}</Table.Cell>
<Table.Cell className="whitespace-nowrap">{row.date}</Table.Cell>
<Table.Cell className="whitespace-nowrap">
{row.tags ? (
<div className="inline-flex gap-1">
{row.tags.map((tag) => (
<Badge key={tag}>{tag}</Badge>
))}
</div>
) : (
"—"
)}
</Table.Cell>
<Table.Cell sticky="right" className="text-right">
<DropdownMenu>
<DropdownMenu.Trigger
render={
<Button
variant="ghost"
size="sm"
shape="square"
aria-label="More options"
>
<DotsThree weight="bold" size={16} />
</Button>
}
/>
<DropdownMenu.Content>
<DropdownMenu.Item icon={Eye}>View</DropdownMenu.Item>
<DropdownMenu.Item icon={PencilSimple}>
Edit
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item icon={Trash} variant="danger">
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}完整示例
包含复选框、徽章、操作按钮与固定列宽的完整表格。
| Subject | From | Date | ||
|---|---|---|---|---|
Kumo v1.0.0 released | Visal In | 5 seconds ago | ||
New Job Offer | Cloudflare | 10 minutes ago | ||
Daily Email Digest promotion | Cloudflare | 1 hour ago | ||
GitLab - New Comment | Rob Knecht | 1 day ago | ||
Out of Office | Johnnie Lappen | 3 days ago |
import { useState } from "react";
import { Badge, Button, DropdownMenu, LayerCard, Table } from "@cloudflare/kumo";
import { DotsThree, EnvelopeSimple, Eye, PencilSimple, Trash } from "@phosphor-icons/react";
export function TableFullDemo() {
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(["2"]));
const toggleRow = (id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
};
const toggleAll = () => {
if (selectedIds.size === emailData.length) {
setSelectedIds(new Set());
} else {
setSelectedIds(new Set(emailData.map((r) => r.id)));
}
};
return (
<LayerCard className="w-full overflow-x-auto p-0">
<Table layout="fixed">
<colgroup>
<col />{" "}
{/* Checkbox column - width handled by Table.CheckHead/CheckCell */}
<col />
<col style={{ width: "150px" }} />
<col style={{ width: "120px" }} />
<col style={{ width: "50px" }} />
</colgroup>
<Table.Header>
<Table.Row>
<Table.CheckHead
checked={selectedIds.size === emailData.length}
indeterminate={
selectedIds.size > 0 && selectedIds.size < emailData.length
}
onCheckedChange={toggleAll}
aria-label="Select all rows"
/>
<Table.Head>Subject</Table.Head>
<Table.Head>From</Table.Head>
<Table.Head>Date</Table.Head>
<Table.Head></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{emailData.map((row) => (
<Table.Row
key={row.id}
variant={selectedIds.has(row.id) ? "selected" : "default"}
>
<Table.CheckCell
checked={selectedIds.has(row.id)}
onCheckedChange={() => toggleRow(row.id)}
aria-label={`Select ${row.subject}`}
/>
<Table.Cell>
<div className="flex items-center gap-2">
<EnvelopeSimple size={16} />
<span className="truncate">{row.subject}</span>
{row.tags && (
<div className="ml-2 inline-flex gap-1">
{row.tags.map((tag) => (
<Badge key={tag}>{tag}</Badge>
))}
</div>
)}
</div>
</Table.Cell>
<Table.Cell>
<span className="truncate">{row.from}</span>
</Table.Cell>
<Table.Cell>
<span className="truncate">{row.date}</span>
</Table.Cell>
<Table.Cell className="text-right">
<DropdownMenu>
<DropdownMenu.Trigger
render={
<Button
variant="ghost"
size="sm"
shape="square"
aria-label="More options"
>
<DotsThree weight="bold" size={16} />
</Button>
}
/>
<DropdownMenu.Content>
<DropdownMenu.Item icon={Eye}>View</DropdownMenu.Item>
<DropdownMenu.Item icon={PencilSimple}>
Edit
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item icon={Trash} variant="danger">
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</LayerCard>
);
}API 参考
Table
根表格组件,渲染语义化的 <table> 元素。
| Prop | Type | Default | Description |
|---|---|---|---|
| layout | "auto" | "fixed" | "auto" | - |
| variant | "default" | "selected" | "default" | - |
| sticky | "left" | "right" | - | - |
| className | string | - | Additional CSS classes |
| children | ReactNode | - | Child elements |
Table.Header
表头区域,渲染 <thead>。设置 sticky 可将表头行固定在滚动容器顶部。
Table.Body
表体区域,渲染 <tbody>。
Table.Row
表格行,支持 variant="selected" 用于高亮。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
Table.Head
表头单元格,渲染 <th>。支持 sticky="left" 或 sticky="right" 固定所在列。
Table.Cell
表体单元格,渲染 <td>。支持 sticky="left" 或 sticky="right" 固定所在列。
Table.CheckHead
带复选框的表头单元格,用于全选。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
Table.CheckCell
带复选框的表体单元格,用于行选择。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
Table.ResizeHandle
用于调整列宽的可拖拽手柄。可搭配 TanStack Table 或自定义缩放逻辑使用。
TanStack Table 集成
如需排序、筛选、可调整列宽等高级功能,可与 TanStack Table 集成。Table 组件专为与 TanStack 的无头 API 无缝协作而设计。
import {
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Table } from "@cloudflare/kumo";
function DataTable({ data, columns }) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
});
return (
<Table layout="fixed">
<colgroup>
{table.getAllColumns().map((column) => (
<col key={column.id} style={{ width: column.getSize() }} />
))}
</colgroup>
<Table.Header>
{table.getHeaderGroups().map((headerGroup) => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Table.Head key={header.id}>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
<Table.ResizeHandle
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
/>
</Table.Head>
))}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows.map((row) => (
<Table.Row key={row.id}>
{row.getVisibleCells().map((cell) => (
<Table.Cell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Table.Cell>
))}
</Table.Row>
))}
</Table.Body>
</Table>
);
}无障碍
语义化 HTML
Table 使用语义化的 <table>、<thead>、<tbody>、<th> 和 <td> 元素,以便屏幕阅读器正确导航。
复选框标签
请始终为 Table.CheckHead 与 Table.CheckCell 提供 aria-label,说明其用途。
键盘导航
Tab 可在各交互元素之间移动焦点。复选框响应 Space。