| 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 |
<script lang="ts">
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
</script>
<LayerCard.Root class="p-0">
<Table.Root>
<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>
{#each emailData.slice(0, 3) as row (row.id)}
<Table.Row>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
Import
import * as Table from "kumo-svelte/components/table"; Usage
<script lang="ts">
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
</script>
<LayerCard.Root class="p-0">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Name</Table.Head>
<Table.Head>Email</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>John Doe</Table.Cell>
<Table.Cell>john@example.com</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
</LayerCard.Root> Examples
With Checkboxes
Add row selection with Table.CheckHead and Table.CheckCell. Both accept onCheckedChange, which matches the Checkbox component’s callback shape.
| 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 |
<script lang="ts">
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
const rows = emailData.slice(0, 3);
let selectedIds = $state(new Set<string>());
function toggleRow(id: string) {
const next = new Set(selectedIds);
if (next.has(id)) next.delete(id);
else next.add(id);
selectedIds = next;
}
function toggleAll() {
selectedIds = selectedIds.size === rows.length ? new Set() : new Set(rows.map((row) => row.id));
}
</script>
<LayerCard.Root class="p-0">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.CheckHead
checked={selectedIds.size === rows.length}
indeterminate={selectedIds.size > 0 && selectedIds.size < rows.length}
onCheckedChange={toggleAll}
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>
{#each rows as row (row.id)}
<Table.Row>
<Table.CheckCell checked={selectedIds.has(row.id)} onCheckedChange={() => toggleRow(row.id)} label={`Select ${row.subject}`} />
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
Compact Header
Use variant="compact" on Table.Header for a more condensed header style.
| 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 |
<script lang="ts">
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
</script>
<LayerCard.Root class="p-0">
<Table.Root>
<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>
{#each emailData.slice(0, 3) as row (row.id)}
<Table.Row>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
Selected Row
Use variant="selected" on Table.Row to highlight selected rows.
| 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 |
<script lang="ts">
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
</script>
<LayerCard.Root class="p-0">
<Table.Root>
<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>
{#each emailData.slice(0, 3) as row (row.id)}
<Table.Row variant={row.id === "2" ? "selected" : "default"}>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
Fixed Layout with Column Sizes
For precise control over column widths, set layout="fixed" and use colgroup with col elements.
| 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 |
<script lang="ts">
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
</script>
<LayerCard.Root class="p-0">
<Table.Root layout="fixed">
<colgroup>
<col />
<col class="w-[150px]" />
<col class="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>
{#each emailData as row (row.id)}
<Table.Row>
<Table.Cell>{row.subject}</Table.Cell>
<Table.Cell>{row.from}</Table.Cell>
<Table.Cell>{row.date}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
Sticky Column
Pin a column to the left or right edge of the scroll container with sticky="left" or sticky="right" on Table.Head and Table.Cell.
| 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 | — |
<script lang="ts">
import { Badge } from "kumo-svelte/components/badge";
import { Button } from "kumo-svelte/components/button";
import * as DropdownMenu from "kumo-svelte/components/dropdown";
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
import DotsThreeIcon from "phosphor-svelte/lib/DotsThreeIcon";
import EyeIcon from "phosphor-svelte/lib/EyeIcon";
import PencilSimpleIcon from "phosphor-svelte/lib/PencilSimpleIcon";
import TrashIcon from "phosphor-svelte/lib/TrashIcon";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
</script>
<LayerCard.Root class="w-full max-w-md overflow-x-auto p-0">
<Table.Root>
<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 class="sr-only">Actions</span></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each emailData as row (row.id)}
<Table.Row>
<Table.Cell class="whitespace-nowrap">{row.subject}</Table.Cell>
<Table.Cell class="whitespace-nowrap">{row.from}</Table.Cell>
<Table.Cell class="whitespace-nowrap">{row.date}</Table.Cell>
<Table.Cell class="whitespace-nowrap">
{#if row.tags}
<div class="inline-flex gap-1">{#each row.tags as tag}<Badge>{tag}</Badge>{/each}</div>
{:else}
—
{/if}
</Table.Cell>
<Table.Cell sticky="right" class="text-right">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="sm" shape="square" aria-label="More options" icon={DotsThreeIcon} />
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>
{#snippet icon()}<EyeIcon size={16} />{/snippet}
View
</DropdownMenu.Item>
<DropdownMenu.Item>
{#snippet icon()}<PencilSimpleIcon size={16} />{/snippet}
Edit
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item variant="danger">
{#snippet icon()}<TrashIcon size={16} />{/snippet}
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
Compact Header with Sticky Column
Combining variant="compact" on Table.Header with sticky columns.
| 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 | — |
<script lang="ts">
import { Badge } from "kumo-svelte/components/badge";
import { Button } from "kumo-svelte/components/button";
import * as DropdownMenu from "kumo-svelte/components/dropdown";
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
import DotsThreeIcon from "phosphor-svelte/lib/DotsThreeIcon";
import EyeIcon from "phosphor-svelte/lib/EyeIcon";
import PencilSimpleIcon from "phosphor-svelte/lib/PencilSimpleIcon";
import TrashIcon from "phosphor-svelte/lib/TrashIcon";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
</script>
<LayerCard.Root class="w-full max-w-md overflow-x-auto p-0">
<Table.Root>
<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 class="sr-only">Actions</span></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each emailData as row (row.id)}
<Table.Row>
<Table.Cell class="whitespace-nowrap">{row.subject}</Table.Cell>
<Table.Cell class="whitespace-nowrap">{row.from}</Table.Cell>
<Table.Cell class="whitespace-nowrap">{row.date}</Table.Cell>
<Table.Cell class="whitespace-nowrap">{#if row.tags}<Badge>{row.tags[0]}</Badge>{:else}—{/if}</Table.Cell>
<Table.Cell sticky="right" class="text-right">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="sm" shape="square" aria-label="More options" icon={DotsThreeIcon} />
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>
{#snippet icon()}<EyeIcon size={16} />{/snippet}
View
</DropdownMenu.Item>
<DropdownMenu.Item>
{#snippet icon()}<PencilSimpleIcon size={16} />{/snippet}
Edit
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item variant="danger">
{#snippet icon()}<TrashIcon size={16} />{/snippet}
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
Full Example
Complete table with checkboxes, badges, action buttons, and fixed column widths.
| 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 |
<script lang="ts">
import { Badge } from "kumo-svelte/components/badge";
import { Button } from "kumo-svelte/components/button";
import * as DropdownMenu from "kumo-svelte/components/dropdown";
import * as LayerCard from "kumo-svelte/components/layer-card";
import * as Table from "kumo-svelte/components/table";
import DotsThreeIcon from "phosphor-svelte/lib/DotsThreeIcon";
import EnvelopeSimpleIcon from "phosphor-svelte/lib/EnvelopeSimpleIcon";
import EyeIcon from "phosphor-svelte/lib/EyeIcon";
import PencilSimpleIcon from "phosphor-svelte/lib/PencilSimpleIcon";
import TrashIcon from "phosphor-svelte/lib/TrashIcon";
const emailData = [
{ id: "1", subject: "Kumo v1.0.0 released", from: "Visal In", date: "5 seconds ago" },
{ id: "2", subject: "New Job Offer", from: "Cloudflare", date: "10 minutes ago" },
{
id: "3",
subject: "Daily Email Digest",
from: "Cloudflare",
date: "1 hour ago",
tags: ["promotion"],
},
{ id: "4", subject: "GitLab - New Comment", from: "Rob Knecht", date: "1 day ago" },
{ id: "5", subject: "Out of Office", from: "Johnnie Lappen", date: "3 days ago" },
];
let selectedIds = $state(new Set<string>(["2"]));
function toggleRow(id: string) {
const next = new Set(selectedIds);
if (next.has(id)) next.delete(id);
else next.add(id);
selectedIds = next;
}
</script>
<LayerCard.Root class="w-full overflow-x-auto p-0">
<Table.Root layout="fixed">
<colgroup>
<col />
<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} />
<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>
{#each emailData as row (row.id)}
<Table.Row variant={selectedIds.has(row.id) ? "selected" : "default"}>
<Table.CheckCell checked={selectedIds.has(row.id)} onCheckedChange={() => toggleRow(row.id)} />
<Table.Cell>
<div class="flex items-center gap-2">
<EnvelopeSimpleIcon size={16} />
<span class="truncate">{row.subject}</span>
{#if row.tags}
<div class="ml-2 inline-flex gap-1">
{#each row.tags as tag}<Badge>{tag}</Badge>{/each}
</div>
{/if}
</div>
</Table.Cell>
<Table.Cell><span class="truncate">{row.from}</span></Table.Cell>
<Table.Cell><span class="truncate">{row.date}</span></Table.Cell>
<Table.Cell class="text-right">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="sm" shape="square" aria-label="More options" icon={DotsThreeIcon} />
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>
{#snippet icon()}<EyeIcon size={16} />{/snippet}
View
</DropdownMenu.Item>
<DropdownMenu.Item>
{#snippet icon()}<PencilSimpleIcon size={16} />{/snippet}
Edit
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item variant="danger">
{#snippet icon()}<TrashIcon size={16} />{/snippet}
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</LayerCard.Root>
API Reference
Table
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| layout | KumoTableLayout | KUMO_TABLE_DEFAULT_VARIANTS.layout |
Table.Body
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
Table.Cell
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| sticky | KumoTableStickyColumn | — |
Table.CheckCell
| Prop | Type | Default |
|---|---|---|
| checked | boolean | false |
| class | string | — |
| disabled | boolean | false |
| indeterminate | boolean | false |
| label | string | — |
| onCheckedChange | (checked: boolean, eventDetails?: CheckboxChangeEventDetails) => void | — |
| onValueChange | (checked: boolean) => void | — |
Table.CheckHead
| Prop | Type | Default |
|---|---|---|
| checked | boolean | false |
| class | string | — |
| disabled | boolean | false |
| indeterminate | boolean | false |
| label | string | — |
| onCheckedChange | (checked: boolean, eventDetails?: CheckboxChangeEventDetails) => void | — |
| onValueChange | (checked: boolean) => void | — |
Table.Footer
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
Table.Head
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| sticky | KumoTableStickyColumn | — |
Table.Header
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| sticky | boolean | false |
| variant | "default" | "compact" | "default" |
Table.ResizeHandle
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Table.Row
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| variant | KumoTableRowVariant | KUMO_TABLE_DEFAULT_VARIANTS.variant |
TanStack Table Integration
For advanced features like sorting, filtering, and resizable columns, integrate with TanStack Table. The Table components are semantic wrappers that work with headless table APIs.