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 { LayerCard, Table } from 'kumo-svelte';
</script>

<LayerCard class="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>
      {#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>
</LayerCard>

Installation

Barrel

import { Table, TableBody, TableCell, TableCheckCell, TableCheckHead, TableFooter, TableHead, TableHeader, TableResizeHandle, TableRow } from 'kumo-svelte';

Granular

import { Table, TableBody, TableCell, TableCheckCell, TableCheckHead, TableFooter, TableHead, TableHeader, TableResizeHandle, TableRow } from 'kumo-svelte/components/table';

Usage

<script lang="ts">  import { LayerCard, Table } from 'kumo-svelte';</script><LayerCard class="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>

Examples

With Checkboxes

Add row selection with Table.CheckHead and Table.CheckCell. Both accept onCheckedChange, which matches the underlying Checkbox component's signature.

The older onValueChange prop still works but is deprecated and will be removed in a future major version. Migrate by renaming the prop:

<!-- Before (deprecated) --><Table.CheckCell onValueChange={(checked) => toggleRow(id)} /><!-- After --><Table.CheckCell onCheckedChange={(checked) => toggleRow(id)} />
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 { LayerCard, Table } from 'kumo-svelte';

  const rows = emailData.slice(0, 3);
  let selectedIds = $state<Set<string>>(new Set());

  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 class="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>
      {#each rows as row (row.id)}
        <Table.Row>
          <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>
      {/each}
    </Table.Body>
  </Table>
</LayerCard>

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 { LayerCard, Table } from 'kumo-svelte';
</script>

<LayerCard class="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>
      {#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>
</LayerCard>

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 { LayerCard, Table } from 'kumo-svelte';

  const rows = emailData.slice(0, 3);
  let selectedIds = $state<Set<string>>(new Set(['2']));

  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 class="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>
      {#each rows as row (row.id)}
        <Table.Row 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>
      {/each}
    </Table.Body>
  </Table>
</LayerCard>

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 { LayerCard, Table } from 'kumo-svelte';
</script>

<LayerCard class="p-0">
  <Table 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>
</LayerCard>

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. The component automatically adds an opaque background and gradient fade. Wrap the table in an overflow-x-auto container.

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, Button, DropdownMenu, LayerCard, Table } from 'kumo-svelte';
  import { DotsThree, Eye, PencilSimple, Trash } from 'phosphor-svelte';
</script>

<LayerCard class="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 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}
              &mdash;
            {/if}
          </Table.Cell>
          <Table.Cell sticky="right" class="text-right">
            <DropdownMenu>
              <DropdownMenu.Trigger>
                <Button variant="ghost" size="sm" shape="square" aria-label="More options">
                  <DotsThree weight="bold" size={16} />
                </Button>
              </DropdownMenu.Trigger>
              <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>
      {/each}
    </Table.Body>
  </Table>
</LayerCard>

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, Button, DropdownMenu, LayerCard, Table } from 'kumo-svelte';
  import { DotsThree, Eye, PencilSimple, Trash } from 'phosphor-svelte';
</script>

<LayerCard class="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 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}
              &mdash;
            {/if}
          </Table.Cell>
          <Table.Cell sticky="right" class="text-right">
            <DropdownMenu>
              <DropdownMenu.Trigger>
                <Button variant="ghost" size="sm" shape="square" aria-label="More options">
                  <DotsThree weight="bold" size={16} />
                </Button>
              </DropdownMenu.Trigger>
              <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>
      {/each}
    </Table.Body>
  </Table>
</LayerCard>

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, Button, DropdownMenu, LayerCard, Table } from 'kumo-svelte';
  import { DotsThree, EnvelopeSimple, Eye, PencilSimple, Trash } from 'phosphor-svelte';

  let selectedIds = $state<Set<string>>(new Set(['2']));

  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 === emailData.length ? new Set() : new Set(emailData.map((row) => row.id));
  }
</script>

<LayerCard class="w-full overflow-x-auto p-0">
  <Table 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}
          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>
      {#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)}
            aria-label={`Select ${row.subject}`}
          />
          <Table.Cell>
            <div class="flex items-center gap-2">
              <EnvelopeSimple 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>
              <DropdownMenu.Trigger>
                <Button variant="ghost" size="sm" shape="square" aria-label="More options">
                  <DotsThree weight="bold" size={16} />
                </Button>
              </DropdownMenu.Trigger>
              <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>
      {/each}
    </Table.Body>
  </Table>
</LayerCard>

API Reference

Table

Root table component. Renders a semantic <table> element.

PropTypeDefaultDescription
layout 'auto' | 'fixed'"auto"Table layout algorithm.
class string-Additional classes merged onto the root element.
children Snippet-Table section and row children.

Table.Header

Table header section. Renders <thead>. Set sticky to pin the header row to the top of the scroll container.

Table.Body

Table body section. Renders <tbody>.

Table.Row

Table row. Supports variant="selected" for highlighting.

PropTypeDefaultDescription
variant 'default' | 'selected'"default"Row visual state.

Table.Head

Header cell. Renders <th>. Accepts sticky="left" or sticky="right" to pin the column.

Table.Cell

Body cell. Renders <td>. Accepts sticky="left" or sticky="right" to pin the column.

Table.CheckHead

Header cell with checkbox for "select all" functionality.

PropTypeDefaultDescription
checked booleanfalseChecked state for the select-all checkbox.
indeterminate booleanfalseIndeterminate state for the select-all checkbox.
onCheckedChange (checked: boolean) => void-Called when the checkbox's checked state changes.
onValueChange (checked: boolean) => void-Deprecated. Use onCheckedChange instead.
label string-Accessible label for the checkbox.
disabled booleanfalseDisables the component.

Table.CheckCell

Body cell with checkbox for row selection.

PropTypeDefaultDescription
checked booleanfalseChecked state for the row checkbox.
indeterminate booleanfalseIndeterminate state for the row checkbox.
onCheckedChange (checked: boolean) => void-Called when the checkbox's checked state changes.
onValueChange (checked: boolean) => void-Deprecated. Use onCheckedChange instead.
label string-Accessible label for the checkbox.
disabled booleanfalseDisables the component.

Table.ResizeHandle

Draggable handle for column resizing. Use with TanStack Table or custom resize logic.

TanStack Table Integration

For advanced features like sorting, filtering, and resizable columns, integrate with TanStack Table. The Table component is designed to work with TanStack's headless API.

<script lang="ts">  import { Table } from 'kumo-svelte';</script><Table layout="fixed">  <colgroup>    {#each table.getAllColumns() as column}      <col style:width={`${column.getSize()}px`} />    {/each}  </colgroup>  <Table.Header>    {#each table.getHeaderGroups() as headerGroup}      <Table.Row>        {#each headerGroup.headers as header}          <Table.Head>            {flexRender(header.column.columnDef.header, header.getContext())}            <Table.ResizeHandle              onmousedown={header.getResizeHandler()}              ontouchstart={header.getResizeHandler()}            />          </Table.Head>        {/each}      </Table.Row>    {/each}  </Table.Header>  <Table.Body>    {#each table.getRowModel().rows as row}      <Table.Row>        {#each row.getVisibleCells() as cell}          <Table.Cell>            {flexRender(cell.column.columnDef.cell, cell.getContext())}          </Table.Cell>        {/each}      </Table.Row>    {/each}  </Table.Body></Table>

Accessibility

Semantic HTML

Table uses semantic <table>, <thead>, <tbody>, <th>, and <td> elements for proper screen reader navigation.

Checkbox Labels

Always provide aria-label for Table.CheckHead and Table.CheckCell to describe their purpose.

Keyboard Navigation

Tab moves focus through interactive elements. Checkboxes respond to Space.