AniBTUI
✦ 组件@anibt/tableregistry JSON

Table

真正的 table:36 高的弱色表头、至少 48 高的行,行与行之间是缝线;选中的行是粉色淡底,数字用圆体右对齐。

字幕组
本月发布最多的四个组
字幕组语言资源本月状态
喵萌奶茶屋@nekomoe
简中·繁中1,28442已同步
绿茶字幕组@greentea
简中61218同步中
星空字幕组@xksub
简中·繁中2,09357已同步
北宇治字幕组@kitauji
简中·日88030 天未更新
数字为 2026 年 10 月的统计

安装

npx shadcn@latest add @anibt/table

第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。

手动安装
  1. 1. 先装主题组件只读 @anibt/theme 的 token 和配方,没有主题就没有样子。
    npx shadcn@latest add @anibt/theme
  2. 2. 把下面的文件放进项目
    components/ui/table.tsx
    import * as React from "react"
    
    import { cn } from "@/lib/utils"
    
    /**
     * Table — the AniBT 「贴纸手账」 data table, on a real `<table>`.
     *
     * It draws no box of its own: a table sits straight on the panel it is placed
     * in (put it in a `Card`, with the card's padding off). The head row is 36px
     * of 12/600 muted labels; body rows are at least 48px, parted by the dashed
     * stitch; the pointer presses a row to the sunk tone and a selected row
     * (`data-state="selected"`) takes the pink wash. Numbers set `numeric` on the
     * head and the cell: right-aligned, in the round face, tabular.
     *
     * `TableHeader sticky` pins the head to the top of a height-capped container
     * (`containerClassName="max-h-80"`); the pinned cells repeat the card's paper
     * so rows scrolling under them do not show through.
     *
     * `TableIdentity` is the first cell of an entity row: an optional picture, the
     * name on one line and a muted line under it, always 32px tall.
     *
     * The parts and props are shadcn's, plus `containerClassName`, `sticky`,
     * `numeric` and `TableIdentity`.
     */
    function Table({
      className,
      containerClassName,
      ...props
    }: React.ComponentProps<"table"> & { containerClassName?: string }) {
      return (
        <div data-slot="table-container" className={cn("relative w-full overflow-x-auto", containerClassName)}>
          <table
            data-slot="table"
            className={cn("w-full caption-bottom border-separate border-spacing-0 text-[13px]", className)}
            {...props}
          />
        </div>
      )
    }
    
    function TableHeader({
      className,
      sticky = false,
      ...props
    }: React.ComponentProps<"thead"> & { sticky?: boolean }) {
      return (
        <thead
          data-slot="table-header"
          data-sticky={sticky ? "true" : undefined}
          className={cn(
            "[&_th]:bg-(image:--anibt-stitch) [&_th]:bg-size-[8px_1px] [&_th]:bg-bottom [&_th]:bg-repeat-x [&_tr]:h-auto [&_tr]:bg-none [&_tr]:hover:bg-transparent",
            sticky && "[&_th]:sticky [&_th]:top-0 [&_th]:z-10 [&_th]:bg-card",
            className
          )}
          {...props}
        />
      )
    }
    
    function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
      return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:bg-none", className)} {...props} />
    }
    
    function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
      return (
        <tfoot
          data-slot="table-footer"
          className={cn(
            "font-semibold text-foreground [&_td]:bg-muted [&_td]:bg-(image:--anibt-stitch) [&_td]:bg-size-[8px_1px] [&_td]:bg-top [&_td]:bg-repeat-x [&_tr]:bg-none [&_tr]:hover:bg-transparent",
            className
          )}
          {...props}
        />
      )
    }
    
    function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
      return (
        <tr
          data-slot="table-row"
          className={cn(
            "h-12 bg-(image:--anibt-stitch) bg-size-[8px_1px] bg-bottom bg-repeat-x transition-colors duration-150 hover:bg-muted data-[state=selected]:bg-accent",
            className
          )}
          {...props}
        />
      )
    }
    
    const NUMERIC = "data-[numeric=true]:text-right data-[numeric=true]:anibt-num"
    
    function TableHead({ className, numeric, ...props }: React.ComponentProps<"th"> & { numeric?: boolean }) {
      return (
        <th
          data-slot="table-head"
          data-numeric={numeric ? "true" : undefined}
          className={cn(
            "h-9 px-3 text-left align-middle text-[12px] leading-4 font-semibold whitespace-nowrap text-muted-foreground first:pl-4 last:pr-4 [&:has([role=checkbox])]:pr-0",
            NUMERIC,
            className
          )}
          {...props}
        />
      )
    }
    
    function TableCell({ className, numeric, ...props }: React.ComponentProps<"td"> & { numeric?: boolean }) {
      return (
        <td
          data-slot="table-cell"
          data-numeric={numeric ? "true" : undefined}
          className={cn(
            "px-3 py-2 align-middle text-[13px] leading-5 text-anibt-ink2 first:pl-4 last:pr-4 [&:has([role=checkbox])]:pr-0",
            NUMERIC,
            className
          )}
          {...props}
        />
      )
    }
    
    function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
      return (
        <caption data-slot="table-caption" className={cn("mt-3 text-[12px] text-muted-foreground", className)} {...props} />
      )
    }
    
    /** Picture, name and a muted line: the first cell of an entity row. */
    function TableIdentity({
      className,
      media,
      title,
      meta,
      ...props
    }: Omit<React.ComponentProps<"div">, "title"> & {
      media?: React.ReactNode
      title: React.ReactNode
      meta?: React.ReactNode
    }) {
      return (
        <div data-slot="table-identity" className={cn("flex min-w-0 items-center gap-2.5", className)} {...props}>
          {media}
          <div className="flex h-8 min-w-0 flex-col justify-center">
            <span className="anibt-ell text-[14px] leading-4 font-semibold text-foreground">{title}</span>
            {meta ? <span className="anibt-ell mt-0.5 text-[12px] leading-4 text-muted-foreground">{meta}</span> : null}
          </div>
        </div>
      )
    }
    
    export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, TableIdentity }

用法

用法
import { Card, CardContent } from "@/components/ui/card"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Card className="overflow-hidden py-0">
  <Table>
    <TableHeader>
      <TableRow>
        <TableHead>字幕组</TableHead>
        <TableHead numeric>资源</TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell>喵萌奶茶屋</TableCell>
        <TableCell numeric>1,284</TableCell>
      </TableRow>
    </TableBody>
  </Table>
</Card>

表格自己不画框:它直接坐在所在的面板上,放进 Card 并去掉卡片的内边距即可。表头是 12/600 的弱色字,下面一条缝线;行至少 48 高,行间缝线,指针下压成下沉色,data-state="selected" 的行是粉色淡底。首列和末列多留 4px,和卡片边对齐。

数字列在表头和单元格上都写 numeric:右对齐、圆体、等宽数字,竖着扫一列不会跳。

TableIdentity 是实体行的第一格:可选的图片、一行名字、一行弱色说明,始终 32 高,没有说明的行也不会变矮。

吸顶表头

集标题大小做种
01出发380 MB140
02末班车之后417 MB129
03樱花的约定454 MB118
04看不见的站台491 MB107
05夜色的乘客408 MB96
06终点站445 MB85
07回程482 MB74
08雨中399 MB63
09第二次相遇436 MB52
10沉默的车厢473 MB41
11黎明390 MB30
12夜樱427 MB19
合计 12 集5.4 GB—

给表格容器一个最大高度(containerClassName="max-h-72"),TableHeader sticky 就会钉在顶上;钉住的表头用卡片的纸色,下面滚过的行不会透出来。TableFooter 是合计行:下沉色、上缘一条缝线。

属性

Table
containerClassNamestring默认 —滚动容器的 class:限高、圆角
TableHeader
stickyboolean默认 false钉在滚动容器的顶上
TableHead · TableCell
numericboolean默认 false右对齐、圆体、等宽数字
TableIdentity
titleReactNode默认 —必填:一行名字
metaReactNode默认 —名字下的弱色说明
mediaReactNode默认 —左边的图片,常用 EntityAvatar

其余部分与 shadcn 同名,属性透传给对应的表格元素。