Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

Table

Các hàng dữ liệu so sánh theo cột: thành viên, hóa đơn, triển khai. Hàng chạy sát hai mép, có đường chia dưới mỗi hàng, chữ header là secondary, và hàng body nhận overlay hover trung tính. Cell chứa bất kỳ component nào: một Avatar và một tên, một Badge, một Checkbox để chọn hàng, một DropdownMenu các hành động của hàng. Header có thể sắp xếp, một TableToolbar phía trên và phía dưới chứa tìm kiếm, hành động, số lượng và Pagination, và TableEmpty nói khi không có hàng. TableSelection thêm việc chọn hàng: một checkbox mỗi hàng, một checkbox ở header theo các hàng đó, và một thanh đếm rồi nêu tên các hàng đã chọn.

  • npm: 2 trực tiếp, 6 tổng
  • registry: 4 trực tiếp, 6 tổng

Xem trước

Hóa đơn cho năm 2026.
Trạng tháiSố tiềnThao tác
INV-001Đã thanh toán$250.00
INV-002Đến hạn$1,200.00

Props

  • align
  • numeric
  • fit
  • sortDirection
  • selected
  • defaultValue
  • value
  • label
  • max
  • pending
  • pendingLabel
import { IconDots } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Hóa đơn</TableHead>
      <TableHead>Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
      <TableHead>
        <span className="sr-only">Thao tác</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Đã thanh toán</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-001">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Đến hạn</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-002">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

Cách dùng

Khi nào dùng

  • Các hàng dữ liệu người dùng so sánh theo cột: thành viên, hóa đơn, triển khai.
  • Các số thẳng hàng xuống một cột, chẳng hạn số tiền hoặc số đếm.
  • Các hàng người dùng chọn rồi tác động cùng lúc, chẳng hạn xóa vài thành viên, với TableSelection.

Khi nào không dùng

  • Một danh sách đơn, mỗi mục một hoặc hai dòng. Dùng một danh sách các hàng.
  • Một lưới card, chẳng hạn dự án có ảnh. Dùng Card.
  • Layout. Dùng class flex hoặc grid.

Biến thể

align

Đặt trên TableHead và TableCell. Vị trí nội dung trong cell. end cho số, center chỉ cho dấu trạng thái ngắn.

  • Hóa đơn cho năm 2026.
    Trạng tháiSố tiềnThao tác
    INV-001Đã thanh toán$250.00
    INV-002Đến hạn$1,200.00

    startmặc định

  • Hóa đơn cho năm 2026.
    Trạng tháiSố tiềnThao tác
    INV-001Đã thanh toán$250.00
    INV-002Đến hạn$1,200.00

    center

  • Hóa đơn cho năm 2026.
    Trạng tháiSố tiềnThao tác
    INV-001Đã thanh toán$250.00
    INV-002Đến hạn$1,200.00

    end

import { IconDots } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Hóa đơn</TableHead>
      <TableHead>Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
      <TableHead>
        <span className="sr-only">Thao tác</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Đã thanh toán</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-001">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Đến hạn</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-002">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Hóa đơn</TableHead>
      <TableHead align="center">Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
      <TableHead>
        <span className="sr-only">Thao tác</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell align="center">Đã thanh toán</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-001">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell align="center">Đến hạn</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-002">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Hóa đơn</TableHead>
      <TableHead align="end">Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
      <TableHead>
        <span className="sr-only">Thao tác</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell align="end">Đã thanh toán</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-001">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell align="end">Đến hạn</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-002">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

sortDirection

Đặt trên TableHead có thể sắp xếp. Cách table đang sắp xếp theo cột này; đặt aria-sort và caret.

  • Hóa đơn cho năm 2026.
    Trạng tháiSố tiềnThao tác
    INV-001Đã thanh toán$250.00
    INV-002Đến hạn$1,200.00

    nonemặc định

  • Hóa đơn cho năm 2026.
    Trạng tháiSố tiềnThao tác
    INV-001Đã thanh toán$250.00
    INV-002Đến hạn$1,200.00

    ascending

  • Hóa đơn cho năm 2026.
    Trạng tháiSố tiềnThao tác
    INV-001Đã thanh toán$250.00
    INV-002Đến hạn$1,200.00

    descending

import { IconDots } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Hóa đơn</TableHead>
      <TableHead>Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
      <TableHead>
        <span className="sr-only">Thao tác</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Đã thanh toán</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-001">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Đến hạn</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-002">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead sortDirection="ascending" onSort={sortBy}>Hóa đơn</TableHead>
      <TableHead>Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
      <TableHead>
        <span className="sr-only">Thao tác</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Đã thanh toán</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-001">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Đến hạn</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-002">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead sortDirection="descending" onSort={sortBy}>Hóa đơn</TableHead>
      <TableHead>Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
      <TableHead>
        <span className="sr-only">Thao tác</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Đã thanh toán</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-001">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Đến hạn</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho INV-002">
          <IconDots size={16} />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

Ví dụ

Hóa đơn

Hóa đơn cho năm 2026.
Hóa đơnTrạng tháiSố tiền
INV-001Đã thanh toán$250.00
INV-002Đến hạn$1,200.00
Tổng$1,450.00
import { Badge } from "@/components/ui/badge"
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableCaption>Hóa đơn cho năm 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>Hóa đơn</TableHead>
      <TableHead>Trạng thái</TableHead>
      <TableHead numeric>Số tiền</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>
        <Badge variant="success">Đã thanh toán</Badge>
      </TableCell>
      <TableCell numeric>$250.00</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>
        <Badge variant="warning">Đến hạn</Badge>
      </TableCell>
      <TableCell numeric>$1,200.00</TableCell>
    </TableRow>
  </TableBody>
  <TableFooter>
    <TableRow>
      <TableCell>Tổng</TableCell>
      <TableCell />
      <TableCell numeric>$1,450.00</TableCell>
    </TableRow>
  </TableFooter>
</Table>

Thành viên

Hàng được chọn bằng TableSelection: checkbox header theo các hàng, và thanh phía trên đếm rồi nêu tên hàng đã chọn, cùng các hành động trên chúng. Tìm kiếm và một hành động phía trên table, tên có thể sắp xếp, một Avatar và một email trong một cell, một Badge, hành động của hàng ở cuối, số lượng cùng Pagination phía dưới.

Vai tròThao tác
Ada Lovelaceada@example.com
Chủ sở hữu
Grace Hoppergrace@example.com
Biên tập
2 of 24 members

import { IconDots, IconSearch } from "@tabler/icons-react"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Pagination } from "@/components/ui/pagination"
import { Table, TableActions, TableBody, TableCell, TableHead, TableHeader, TableRow, TableToolbar } from "@/components/ui/table"
import { TableRowSelect, TableSelectAll, TableSelection, TableSelectionBar } from "@/components/ui/table-selection"

<TableSelection defaultValue={["ada"]} className="min-w-0 max-w-full">
  <TableSelectionBar>
    <Button variant="secondary" size="sm">Xuất</Button>
    <Button variant="destructive-soft" size="sm">Gỡ</Button>
  </TableSelectionBar>
  <TableToolbar>
    <Input aria-label="Tìm kiếm thành viên" placeholder="Tìm kiếm thành viên" leadingIcon={<IconSearch size={16} />} className="max-w-xs" />
    <TableActions>
      <Button>Mời</Button>
    </TableActions>
  </TableToolbar>
  <Table aria-label="Thành viên" className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
    <TableHeader>
      <TableRow>
        <TableHead fit>
          <TableSelectAll aria-label="Chọn tất cả thành viên" />
        </TableHead>
        <TableHead sortDirection="ascending" onSort={sortBy}>Tên</TableHead>
        <TableHead>Vai trò</TableHead>
        <TableHead fit>
          <span className="sr-only">Thao tác</span>
        </TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="ada" label="Ada Lovelace" />
        </TableCell>
        <TableCell>
          <div className="flex items-center gap-sm min-w-0 max-w-full flex-wrap">
            <Avatar alt="" color="blue">AL</Avatar>
            <div className="flex flex-col min-w-0 max-w-full flex-wrap">
              <span className="font-medium">Ada Lovelace</span>
              <span className="text-sm text-text-secondary">ada@example.com</span>
            </div>
          </div>
        </TableCell>
        <TableCell>
          <Badge>Chủ sở hữu</Badge>
        </TableCell>
        <TableCell fit>
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho Ada Lovelace">
                <IconDots size={16} />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem>Đổi vai trò</DropdownMenuItem>
              <DropdownMenuItem destructive>Gỡ khỏi nhóm</DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="grace" label="Grace Hopper" />
        </TableCell>
        <TableCell>
          <div className="flex items-center gap-sm min-w-0 max-w-full flex-wrap">
            <Avatar alt="" color="green">GH</Avatar>
            <div className="flex flex-col min-w-0 max-w-full flex-wrap">
              <span className="font-medium">Grace Hopper</span>
              <span className="text-sm text-text-secondary">grace@example.com</span>
            </div>
          </div>
        </TableCell>
        <TableCell>
          <Badge variant="outline">Biên tập</Badge>
        </TableCell>
        <TableCell fit>
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="ghost" size="sm" iconOnly aria-label="Thao tác cho Grace Hopper">
                <IconDots size={16} />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem>Đổi vai trò</DropdownMenuItem>
              <DropdownMenuItem destructive>Gỡ khỏi nhóm</DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </TableCell>
      </TableRow>
    </TableBody>
  </Table>
  <TableToolbar>
    <span className="text-sm text-text-secondary">2 of 24 members</span>
    <TableActions>
      <Pagination page={1} pageCount={12} onPageChange={goToPage} />
    </TableActions>
  </TableToolbar>
</TableSelection>

Nhiều hàng được chọn

Quá ba tên thì thanh hiện phần còn lại dạng số. max trên TableSelectionBar đổi số tên được hiện.

TênVai trò
Ada LovelaceChủ sở hữu
Grace HopperBiên tập
Alan TuringBiên tập
Katherine JohnsonNgười xem
Linus TorvaldsNgười xem
Margaret HamiltonNgười xem

import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { TableRowSelect, TableSelectAll, TableSelection, TableSelectionBar } from "@/components/ui/table-selection"

<TableSelection defaultValue={["ada", "grace", "alan", "katherine", "linus"]} className="min-w-0 max-w-full">
  <TableSelectionBar>
    <Button variant="destructive-soft" size="sm">Gỡ</Button>
  </TableSelectionBar>
  <Table aria-label="Thành viên" className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
    <TableHeader>
      <TableRow>
        <TableHead fit>
          <TableSelectAll aria-label="Chọn tất cả thành viên" />
        </TableHead>
        <TableHead>Tên</TableHead>
        <TableHead>Vai trò</TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="ada" label="Ada Lovelace" />
        </TableCell>
        <TableCell>Ada Lovelace</TableCell>
        <TableCell>Chủ sở hữu</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="grace" label="Grace Hopper" />
        </TableCell>
        <TableCell>Grace Hopper</TableCell>
        <TableCell>Biên tập</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="alan" label="Alan Turing" />
        </TableCell>
        <TableCell>Alan Turing</TableCell>
        <TableCell>Biên tập</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="katherine" label="Katherine Johnson" />
        </TableCell>
        <TableCell>Katherine Johnson</TableCell>
        <TableCell>Người xem</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="linus" label="Linus Torvalds" />
        </TableCell>
        <TableCell>Linus Torvalds</TableCell>
        <TableCell>Người xem</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="margaret" label="Margaret Hamilton" />
        </TableCell>
        <TableCell>Margaret Hamilton</TableCell>
        <TableCell>Người xem</TableCell>
      </TableRow>
    </TableBody>
  </Table>
</TableSelection>

Không có kết quả

TênVai trò
Không có thành viên nào khớp với tìm kiếm của bạn.
import { Table, TableBody, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table aria-label="Thành viên" className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableHeader>
    <TableRow>
      <TableHead>Tên</TableHead>
      <TableHead>Vai trò</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableEmpty colSpan={2}>Không có thành viên nào khớp với tìm kiếm của bạn.</TableEmpty>
  </TableBody>
</Table>

Đang tải trang tiếp theo

Người dùng yêu cầu trang 2, trang này vẫn đang tải: trang 1 ở lại, mờ và được đánh dấu busy. Trong app, pending là isPending của transition đổi trang.

Loading page 2
Hóa đơnSố tiền
INV-001$250.00
INV-002$150.00
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table pending pendingLabel="Loading page 2" aria-label="Hóa đơn" className="max-w-full [&_th]:whitespace-normal [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:px-sm [&_td]:px-sm">
  <TableHeader>
    <TableRow>
      <TableHead>Hóa đơn</TableHead>
      <TableHead numeric>Số tiền</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell numeric>$250.00</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell numeric>$150.00</TableCell>
    </TableRow>
  </TableBody>
</Table>

Nên và không nên

Nên

  • Dùng numeric trên mọi cell của cột số, cả header.
  • Đặt table trên một surface có mép riêng (một Card, một phần của trang); table không có mép.
  • Đặt tên bằng caption, hoặc aria-label khi heading phía trên đã nói đó là gì.
  • Đặt tìm kiếm và hành động chính trong một TableToolbar phía trên table, và số lượng cùng Pagination trong một toolbar phía dưới.
  • Dùng fit cho cột Checkbox và cột hành động, để các cột dữ liệu lấy phần rộng.
  • Với các hàng người dùng chọn rồi tác động cùng lúc, bọc table trong TableSelection; nó giữ checkbox header, các hàng và thanh khớp nhau.
  • Đặt các hành động trên hàng đã chọn (Xuất, Xóa) trong TableSelectionBar, dạng button sm.
  • Khi một trang, lần sắp xếp hoặc bộ lọc tải từ server, đổi nó trong một transition (useTransition) và truyền isPending làm pending: các hàng hiện tại ở lại đến khi hàng tiếp theo tới. Dùng hàng Skeleton chỉ cho lần tải đầu.

Không nên

  • Đừng bọc mỗi hàng trong một Card; hàng chính là layout.
  • Đừng căn giữa cột chữ; căn start.
  • Đừng dùng table cho layout hoặc cho một danh sách đơn.
  • Đừng tự nối cell Checkbox khi TableSelection đã phù hợp; checkbox header và thanh sẽ lệch.
  • Đừng làm trống table hoặc đổi hàng thành spinner khi trang tiếp theo đang tải; layout nhảy và người dùng mất vị trí.

Props

TênKiểuGiá trịMặc địnhMô tả
alignenumstart | center | endstartĐặt trên TableHead và TableCell. Vị trí nội dung trong cell. end cho số, center chỉ cho dấu trạng thái ngắn.
numericboolean-falseĐặt trên TableHead và TableCell. Cột số: căn end, mono, chữ số tabular, để các chữ số thẳng hàng.
fitboolean-falseĐặt trên TableHead và TableCell. Cột chỉ chiếm chỗ nội dung cần: một Checkbox, các hành động của hàng.
sortDirectionenumnone | ascending | descendingnoneĐặt trên TableHead có thể sắp xếp. Cách table đang sắp xếp theo cột này; đặt aria-sort và caret.
onSortfunction--Đặt trên TableHead. Biến header thành button sắp xếp theo cột. Bạn tự sắp các hàng, rồi đặt sortDirection.
selectedboolean-falseĐặt trên TableRow. Hàng được chọn bằng Checkbox của nó: nền accent muted.
colSpannumber--Đặt trên TableEmpty. Số cột của table, để hàng trải hết các cột đó.
defaultValuestring--Đặt trên TableSelection. Value của các hàng được chọn ở lần render đầu, dạng mảng: ["ada"]. Mỗi value là value của một TableRowSelect.
valuestring--Đặt trên TableSelection: các value đã chọn dạng mảng, khi bạn control chúng. Đi cùng onValueChange. TableRowSelect nhận một value, id của hàng đó.
onValueChangefunction--Đặt trên TableSelection. Được gọi với mảng value đã chọn mới.
labelstring--Đặt trên TableRowSelect, cùng value của nó. Tên của hàng: TableSelectionBar hiện tên đó, và nó đặt tên checkbox "Select <label>".
maxnumber-3Đặt trên TableSelectionBar. Bao nhiêu tên hiện trước khi phần còn lại thành +N.
pendingboolean-falseTable: trang, lần sắp xếp hoặc bộ lọc tiếp theo vẫn đang tải. Đổi nó trong một transition (useTransition) để các hàng hiện tại ở lại, và truyền isPending: các hàng mờ xuống opacity-pending và table được đánh dấu aria-busy thay vì để trống.
pendingLabelstring-LoadingTable: trình đọc màn hình đọc trong một status polite khi pending.

Slot

TênBắt buộcMô tả
toolbarkhôngTableToolbar phía trên table (tìm kiếm, bộ lọc, TableActions với các button) và một cái phía dưới (số lượng, TableActions với Pagination).
headerkhôngTableHeader với một TableRow gồm các cell TableHead.
bodycóTableBody với một TableRow cho mỗi mục.
captionkhôngTableCaption, hoặc aria-label trên Table.
cellskhôngBất kỳ component nào trong TableCell: Avatar kèm tên, Badge, Checkbox trong cell fit, DropdownMenu các hành động của hàng trong cell fit ở cuối.
emptykhôngTableEmpty trong TableBody khi không có hàng.
selectionkhôngTableSelection bao quanh thanh, các toolbar và table, với TableSelectAll trong cell fit đầu của header, một TableRowSelect trong cell fit của mỗi hàng, và TableSelectionBar phía trên toolbar. Children của thanh là hành động của app trên các hàng đã chọn (Xuất, Xóa).

Cấu tạo

PhầnMô tả
rootTable: một table rộng hết chiều ngang, chữ primary 14px, trong một container cuộn ngang.
headerTableHeader: các hàng header. Chúng không nổi khi hover.
rowTableRow: một hàng có đường chia bên dưới và overlay hover trung tính. Hàng body cuối không có đường chia. selected phủ nền accent muted và màu chữ đậm đi kèm.
headTableHead: một cell header, cao bằng control md, chữ secondary medium 14px trên một dòng.
cellTableCell: một cell dữ liệu, px-md py-sm, chứa chữ hoặc bất kỳ component nào. align đặt start, center hoặc end; numeric đặt mono, chữ số tabular và end; fit thu cột về đúng nội dung.
footerTableFooter: dòng tổng trên nền sunken, chữ medium, đường chia phía trên.
captionTableCaption: tên của table, bên dưới table, chữ secondary 14px.
sortKhi có onSort: một button trong cell header với chữ của header và một caret, chữ primary khi hover, vùng nhấn size-target-min.
iconCaret sắp xếp: Tabler ChevronUpDown, CaretUp hoặc CaretDown, 16px, secondary.
emptyTableEmpty: một hàng trải colSpan cột, chữ secondary 14px, căn giữa, py-2xl.
toolbarTableToolbar: một hàng phía trên hoặc phía dưới table, gap-sm, tự xuống dòng: một Input tìm kiếm, bộ lọc, số kết quả.
actionsTableActions: cuối một toolbar, gap-sm: các button tác động lên table, hoặc Pagination.
selectionTableSelection: một cột (gap-md) bao quanh thanh, các toolbar và table. Giữ các hàng đã chọn và thông báo số lượng trong một vùng status.
selectAllTableSelectAll: Checkbox trong cell fit của header. Bật khi mọi hàng trên màn hình được chọn, bật một phần khi chỉ một số hàng được chọn.
rowSelectTableRowSelect: Checkbox trong cell fit của hàng, tên "Select <label>". Checkbox đã check tô hàng bằng accent muted.
selectionBarTableSelectionBar: chỉ hiện khi có hàng được chọn. Số lượng, tối đa max tên rồi +N, Select all, hành động của app, Clear selection. Viền, radius-container, nền surface.
bodyCác hàng body. Khi pending, chúng ở lại trên màn hình và mờ xuống opacity-pending (duration-medium); container được đánh dấu aria-busy, với một status polite bên cạnh.

Khả năng truy cập

Role: table

PhímHành động
TabChuyển qua các button sắp xếp trong header và các control bên trong cell; bản thân table không focus được.
Enter, SpaceTrên header có thể sắp xếp, sắp xếp theo cột đó.
  • Một table gốc: trình đọc màn hình đọc mỗi cell cùng header cột. TableHead đặt scope="col" theo mặc định; đặt scope="row" trên cell header đặt tên cho hàng.
  • Đặt tên table bằng TableCaption, hoặc aria-label hay aria-labelledby khi tên là heading phía trên.
  • Table rộng cuộn ngang trong container thay vì làm rộng trang.
  • Header có thể sắp xếp là một button bên trong cell header; cell mang aria-sort (ascending hoặc descending) chỉ cho cột mà table đang sắp xếp theo.
  • Chọn hàng bằng Checkbox trong cell fit, đặt tên theo hàng (Select Ada Lovelace); một Checkbox header chọn mọi hàng. selected chỉ tô hàng: Checkbox mới báo cho trình đọc màn hình.
  • Đặt tên cho button hành động chỉ có icon, có tên hàng trong đó: Actions for Ada Lovelace.
  • TableEmpty là một hàng trải mọi cột, nên table vẫn có header khi không có gì khớp.
  • TableSelectAll là checkbox mixed: aria-checked là mixed khi một số hàng được chọn. Nhấn lúc đó thì chọn phần còn lại; nhấn khi tất cả đã chọn thì xóa chúng.
  • TableSelection giữ một vùng status đọc số lượng ("2 of 5 rows selected") mỗi khi số đó đổi.
  • Select all và Clear selection biến mất sau khi nhấn, nên focus chuyển trước: tới Clear selection sau Select all, tới checkbox header sau Clear selection.

Cài đặt

npx werewolf-ui add table

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.