Phi
Table
@dicehub/phiv1.0.0-beta.1

Table

A table component with borderless alternating rows, selection, row variants, and column sizing.

<script setup lang="ts">
import { computed } from "vue";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";

const rows = [
  { id: "1", subject: "Phi 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 demoRows = computed(() => rows.slice(0, 3));
</script>

<template>
  <LayerCard style="padding: 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>
        <Table.Row v-for="row in demoRows" :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>
</template>

Installation

Barrel

import { Table } from "@dicehub/phi";

Granular

import { Table } from "@dicehub/phi/components/table";

Usage

<script setup lang="ts">
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";
</script>

<template>
  <LayerCard style="padding: 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>[email protected]</Table.Cell>
          <Table.Cell>Admin</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </LayerCard>
</template>

Examples

With Checkboxes

Add row selection with Table.CheckHead and Table.CheckCell. Both emit @checked-change, which matches the underlying Checkbox state change.

<script setup lang="ts">
import { computed, ref } from "vue";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";

const rows = [
  { id: "1", subject: "Phi 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 selectedIds = ref(new Set<string>(["2"]));
const visibleRows = computed(() => rows.slice(0, 3));
const allSelected = computed(() => visibleRows.value.every((row) => selectedIds.value.has(row.id)));
const someSelected = computed(() => visibleRows.value.some((row) => selectedIds.value.has(row.id)) && !allSelected.value);

const toggleRow = (id: string) => {
  const next = new Set(selectedIds.value);
  if (next.has(id)) {
    next.delete(id);
  } else {
    next.add(id);
  }
  selectedIds.value = next;
};

const toggleVisible = () => {
  if (allSelected.value) {
    selectedIds.value = new Set([...selectedIds.value].filter((id) => !visibleRows.value.some((row) => row.id === id)));
    return;
  }
  selectedIds.value = new Set([...selectedIds.value, ...visibleRows.value.map((row) => row.id)]);
};
</script>

<template>
  <LayerCard style="padding: 0;">
    <Table>
      <Table.Header>
        <Table.Row>
          <Table.CheckHead
            aria-label="Select all rows"
            :checked="allSelected"
            :indeterminate="someSelected"
            @checked-change="toggleVisible"
          />
          <Table.Head>Subject</Table.Head>
          <Table.Head>From</Table.Head>
          <Table.Head>Date</Table.Head>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        <Table.Row v-for="row in visibleRows" :key="row.id">
          <Table.CheckCell
            :aria-label="`Select ${row.subject}`"
            :checked="selectedIds.has(row.id)"
            @checked-change="toggleRow(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>
</template>

Compact Header

Use variant="compact" on Table.Header for a more condensed header style.

<script setup lang="ts">
import { computed } from "vue";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";

const rows = [
  { id: "1", subject: "Phi 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 demoRows = computed(() => rows.slice(0, 3));
</script>

<template>
  <LayerCard style="padding: 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>
        <Table.Row v-for="row in demoRows" :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>
</template>

Selected Row

Use variant="selected" on Table.Row to highlight selected rows.

<script setup lang="ts">
import { computed, ref } from "vue";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";

const rows = [
  { id: "1", subject: "Phi 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 selectedIds = ref(new Set<string>(["2"]));
const visibleRows = computed(() => rows.slice(0, 3));
const allSelected = computed(() => visibleRows.value.every((row) => selectedIds.value.has(row.id)));
const someSelected = computed(() => visibleRows.value.some((row) => selectedIds.value.has(row.id)) && !allSelected.value);

const toggleRow = (id: string) => {
  const next = new Set(selectedIds.value);
  if (next.has(id)) {
    next.delete(id);
  } else {
    next.add(id);
  }
  selectedIds.value = next;
};

const toggleVisible = () => {
  if (allSelected.value) {
    selectedIds.value = new Set([...selectedIds.value].filter((id) => !visibleRows.value.some((row) => row.id === id)));
    return;
  }
  selectedIds.value = new Set([...selectedIds.value, ...visibleRows.value.map((row) => row.id)]);
};
</script>

<template>
  <LayerCard style="padding: 0;">
    <Table>
      <Table.Header>
        <Table.Row>
          <Table.CheckHead
            aria-label="Select all rows"
            :checked="allSelected"
            :indeterminate="someSelected"
            @checked-change="toggleVisible"
          />
          <Table.Head>Subject</Table.Head>
          <Table.Head>From</Table.Head>
          <Table.Head>Date</Table.Head>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        <Table.Row
          v-for="row in visibleRows"
          :key="row.id"
          :variant="selectedIds.has(row.id) ? 'selected' : 'default'"
        >
          <Table.CheckCell
            :aria-label="`Select ${row.subject}`"
            :checked="selectedIds.has(row.id)"
            @checked-change="toggleRow(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>
</template>

Fixed Layout with Column Sizes

For precise control over column widths, set layout="fixed" and use colgroup with col elements.

<script setup lang="ts">
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";

const rows = [
  { id: "1", subject: "Phi 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>

<template>
  <LayerCard style="padding: 0;">
    <Table layout="fixed">
      <colgroup>
        <col />
        <col style="width: 150px;" />
        <col style="width: 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>
        <Table.Row v-for="row in rows" :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>
</template>

Sticky Column

Pin a column to the left or right edge with sticky="left" or sticky="right" on Table.Head and Table.Cell.

<script setup lang="ts">
import { Badge } from "@dicehub/phi/components/badge";
import { Button } from "@dicehub/phi/components/button";
import { DropdownMenu } from "@dicehub/phi/components/dropdown";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";
import { PhDotsThree, PhEye, PhPencilSimple, PhTrash } from "@phosphor-icons/vue";

const rows = [
  { id: "1", subject: "Phi 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>

<template>
  <LayerCard style="width: 100%; max-width: 28rem; overflow-x: auto; padding: 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="phi-sr-only">Actions</span></Table.Head>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        <Table.Row v-for="row in rows" :key="row.id">
          <Table.Cell style="white-space: nowrap;">{{ row.subject }}</Table.Cell>
          <Table.Cell style="white-space: nowrap;">{{ row.from }}</Table.Cell>
          <Table.Cell style="white-space: nowrap;">{{ row.date }}</Table.Cell>
          <Table.Cell style="white-space: nowrap;">
            <span v-if="!row.tags">-</span>
            <span v-else style="display: inline-flex; gap: 0.25rem;">
              <Badge v-for="tag in row.tags" :key="tag">{{ tag }}</Badge>
            </span>
          </Table.Cell>
          <Table.Cell sticky="right" style="text-align: right;">
            <DropdownMenu>
              <DropdownMenu.Trigger>
                <Button shape="square" size="sm" variant="ghost" :icon="PhDotsThree" aria-label="More options" />
              </DropdownMenu.Trigger>
              <DropdownMenu.Content>
                <DropdownMenu.Item value="view" :icon="PhEye">View</DropdownMenu.Item>
                <DropdownMenu.Item value="edit" :icon="PhPencilSimple">Edit</DropdownMenu.Item>
                <DropdownMenu.Separator />
                <DropdownMenu.Item value="delete" :icon="PhTrash" variant="danger">Delete</DropdownMenu.Item>
              </DropdownMenu.Content>
            </DropdownMenu>
          </Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </LayerCard>
</template>

Compact Header with Sticky Column

Combining variant="compact" on Table.Header with sticky columns.

<script setup lang="ts">
import { Badge } from "@dicehub/phi/components/badge";
import { Button } from "@dicehub/phi/components/button";
import { DropdownMenu } from "@dicehub/phi/components/dropdown";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";
import { PhDotsThree, PhEye, PhPencilSimple, PhTrash } from "@phosphor-icons/vue";

const rows = [
  { id: "1", subject: "Phi 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>

<template>
  <LayerCard style="width: 100%; max-width: 28rem; overflow-x: auto; padding: 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="phi-sr-only">Actions</span></Table.Head>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        <Table.Row v-for="row in rows" :key="row.id">
          <Table.Cell style="white-space: nowrap;">{{ row.subject }}</Table.Cell>
          <Table.Cell style="white-space: nowrap;">{{ row.from }}</Table.Cell>
          <Table.Cell style="white-space: nowrap;">{{ row.date }}</Table.Cell>
          <Table.Cell style="white-space: nowrap;">
            <span v-if="!row.tags">-</span>
            <span v-else style="display: inline-flex; gap: 0.25rem;">
              <Badge v-for="tag in row.tags" :key="tag">{{ tag }}</Badge>
            </span>
          </Table.Cell>
          <Table.Cell sticky="right" style="text-align: right;">
            <DropdownMenu>
              <DropdownMenu.Trigger>
                <Button shape="square" size="sm" variant="ghost" :icon="PhDotsThree" aria-label="More options" />
              </DropdownMenu.Trigger>
              <DropdownMenu.Content>
                <DropdownMenu.Item value="view" :icon="PhEye">View</DropdownMenu.Item>
                <DropdownMenu.Item value="edit" :icon="PhPencilSimple">Edit</DropdownMenu.Item>
                <DropdownMenu.Separator />
                <DropdownMenu.Item value="delete" :icon="PhTrash" variant="danger">Delete</DropdownMenu.Item>
              </DropdownMenu.Content>
            </DropdownMenu>
          </Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </LayerCard>
</template>

Full Example

Complete table with checkboxes, badges, action buttons, and fixed column widths.

<script setup lang="ts">
import { ref } from "vue";
import { Badge } from "@dicehub/phi/components/badge";
import { Button } from "@dicehub/phi/components/button";
import { DropdownMenu } from "@dicehub/phi/components/dropdown";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { Table } from "@dicehub/phi/components/table";
import { PhDotsThree, PhEnvelopeSimple, PhEye, PhPencilSimple, PhTrash } from "@phosphor-icons/vue";

const rows = [
  { id: "1", subject: "Phi 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 selectedIds = ref(new Set<string>(["2"]));

const toggleRow = (id: string) => {
  const next = new Set(selectedIds.value);
  if (next.has(id)) {
    next.delete(id);
  } else {
    next.add(id);
  }
  selectedIds.value = next;
};

const toggleAll = () => {
  selectedIds.value = rows.every((row) => selectedIds.value.has(row.id))
    ? new Set()
    : new Set(rows.map((row) => row.id));
};
</script>

<template>
  <LayerCard style="width: 100%; overflow-x: auto; padding: 0;">
    <Table layout="fixed">
      <colgroup>
        <col style="width: 40px;" />
        <col />
        <col style="width: 150px;" />
        <col style="width: 120px;" />
        <col style="width: 50px;" />
      </colgroup>
      <Table.Header>
        <Table.Row>
          <Table.CheckHead
            aria-label="Select all rows"
            :checked="rows.every((row) => selectedIds.has(row.id))"
            :indeterminate="rows.some((row) => selectedIds.has(row.id)) && !rows.every((row) => selectedIds.has(row.id))"
            @checked-change="toggleAll"
          />
          <Table.Head>Subject</Table.Head>
          <Table.Head>From</Table.Head>
          <Table.Head>Date</Table.Head>
          <Table.Head />
        </Table.Row>
      </Table.Header>
      <Table.Body>
        <Table.Row v-for="row in rows" :key="row.id" :variant="selectedIds.has(row.id) ? 'selected' : 'default'">
          <Table.CheckCell
            :aria-label="`Select ${row.subject}`"
            :checked="selectedIds.has(row.id)"
            @checked-change="toggleRow(row.id)"
          />
          <Table.Cell>
            <span style="display: inline-flex; align-items: center; gap: 0.5rem;">
              <PhEnvelopeSimple :size="16" aria-hidden="true" />
              <span>{{ row.subject }}</span>
              <Badge v-for="tag in row.tags" :key="tag">{{ tag }}</Badge>
            </span>
          </Table.Cell>
          <Table.Cell>{{ row.from }}</Table.Cell>
          <Table.Cell>{{ row.date }}</Table.Cell>
          <Table.Cell style="text-align: right;">
            <DropdownMenu>
              <DropdownMenu.Trigger>
                <Button shape="square" size="sm" variant="ghost" :icon="PhDotsThree" aria-label="More options" />
              </DropdownMenu.Trigger>
              <DropdownMenu.Content>
                <DropdownMenu.Item value="view" :icon="PhEye">View</DropdownMenu.Item>
                <DropdownMenu.Item value="edit" :icon="PhPencilSimple">Edit</DropdownMenu.Item>
                <DropdownMenu.Separator />
                <DropdownMenu.Item value="delete" :icon="PhTrash" variant="danger">Delete</DropdownMenu.Item>
              </DropdownMenu.Content>
            </DropdownMenu>
          </Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </LayerCard>
</template>

API Reference

Table

Root table component. Renders a semantic table element.

Prop / EventTypeDefaultDescription
layout"auto" | "fixed""auto"Table layout algorithm.
default slotunknown-Table.Header, Table.Body, Table.Footer, colgroup, and other table children.
classNamestring-Additional CSS classes applied to the table.
$attrsTableHTMLAttributes-Native table attributes.

Table.Header

Table header section. Renders thead.

Prop / EventTypeDefaultDescription
variant"default" | "compact""default"Header density and background style.
stickybooleanfalsePins header cells to the top of the scroll container.
default slotunknown-Table.Row children.
classNamestring-Additional CSS classes applied to the thead.

Table.Body

Table body section. Renders tbody.

Prop / EventTypeDefaultDescription
default slotunknown-Table.Row children.
classNamestring-Additional CSS classes applied to the tbody.

Table.Row

Borderless table row. Default rows alternate between semantic base and elevated backgrounds.

Prop / EventTypeDefaultDescription
variant"default" | "selected""default"Row visual variant. Selected rows always use the semantic tint background.
default slotunknown-Table.Head, Table.Cell, Table.CheckHead, or Table.CheckCell children.
classNamestring-Additional CSS classes applied to the tr.

Table.Head

Header cell. Renders th.

Prop / EventTypeDefaultDescription
sticky"left" | "right"-Pins the header cell to an edge of the horizontal scroll container.
default slotunknown-Header cell content.
classNamestring-Additional CSS classes applied to the th.

Table.Cell

Body cell. Renders td.

Prop / EventTypeDefaultDescription
sticky"left" | "right"-Pins the cell to an edge of the horizontal scroll container.
default slotunknown-Cell content.
classNamestring-Additional CSS classes applied to the td.

Table.CheckHead

Header cell with checkbox for select-all behavior.

Prop / EventTypeDefaultDescription
checkedboolean-Controlled checkbox state.
indeterminateboolean-Renders the mixed checkbox state.
labelstring"Select all rows"Accessible label for the checkbox. Native aria-label is also supported.
disabledboolean-Disables the checkbox.
classNamestring-Additional CSS classes applied to the th.
@update:checked(checked: boolean) => void-v-model:checked update event.
@checked-change(details: TableCheckboxChangeDetails) => void-Emitted when the checkbox state changes.
@value-change(checked: boolean) => void-Deprecated alias for checked changes.

Table.CheckCell

Body cell with checkbox for row selection.

Prop / EventTypeDefaultDescription
checkedboolean-Controlled checkbox state.
indeterminateboolean-Renders the mixed checkbox state.
labelstring"Select row"Accessible label for the checkbox. Native aria-label is also supported.
disabledboolean-Disables the checkbox.
classNamestring-Additional CSS classes applied to the td.
@update:checked(checked: boolean) => void-v-model:checked update event.
@checked-change(details: TableCheckboxChangeDetails) => void-Emitted when the checkbox state changes.
@value-change(checked: boolean) => void-Deprecated alias for checked changes.

Table.ResizeHandle

Button handle for column resizing integrations.

Prop / EventTypeDefaultDescription
classNamestring-Additional CSS classes applied to the button.
$attrsButtonHTMLAttributes-Native button attributes and mouse/touch listeners.

TanStack Table Integration

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

<script setup lang="ts" generic="TData">
import { FlexRender, getCoreRowModel, useVueTable, type ColumnDef } from "@tanstack/vue-table";
import { Table } from "@dicehub/phi";

const props = defineProps<{
  data: TData[];
  columns: ColumnDef<TData>[];
}>();

const table = useVueTable<TData>({
  get data() {
    return props.data;
  },
  get columns() {
    return props.columns;
  },
  getCoreRowModel: getCoreRowModel(),
  columnResizeMode: "onChange",
});
</script>

<template>
  <Table layout="fixed">
    <colgroup>
      <col
        v-for="column in table.getAllColumns()"
        :key="column.id"
        :style="{ width: `${column.getSize()}px` }"
      />
    </colgroup>
    <Table.Header>
      <Table.Row v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
        <Table.Head v-for="header in headerGroup.headers" :key="header.id">
          <FlexRender
            v-if="!header.isPlaceholder"
            :render="header.column.columnDef.header"
            :props="header.getContext()"
          />
          <Table.ResizeHandle
            @mousedown="header.getResizeHandler()($event)"
            @touchstart="header.getResizeHandler()($event)"
          />
        </Table.Head>
      </Table.Row>
    </Table.Header>
    <Table.Body>
      <Table.Row v-for="row in table.getRowModel().rows" :key="row.id">
        <Table.Cell v-for="cell in row.getVisibleCells()" :key="cell.id">
          <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
        </Table.Cell>
      </Table.Row>
    </Table.Body>
  </Table>
</template>

Accessibility

Semantic HTML

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

Checkbox Labels

Always provide aria-label or label for Table.CheckHead and Table.CheckCell.

Keyboard Navigation

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