Saltar al contenido
ssntl/ui

Table

Una tabla para consulta exacta: números alineados a la derecha con dígitos tabulares y un título con periodo y fuente.

Sales by storeOctober 2026 · Source: POS
StoreOrdersRevenue (Q)Change
Zona 101,28448,210.00+12.4%
Cayalá96237,905.50+4.1%
Antigua41815,320.00−3.8%
Total2,664101,435.50+6.3%

Instalación

$ pnpm dlx shadcn@latest add @ssntl/table

Uso

tsx
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table>
  <TableCaption title="Ventas por tienda">Octubre 2026 · Fuente: POS</TableCaption>
  <TableHeader>
    <TableRow><TableHead>Tienda</TableHead><TableHead numeric>Ingresos</TableHead></TableRow>
  </TableHeader>
  <TableBody>
    <TableRow><TableHead scope="row">Zona 10</TableHead><TableCell numeric>48,210.00</TableCell></TableRow>
  </TableBody>
</Table>
  • Marca las columnas numéricas con numeric en el encabezado y en cada celda: se alinean a la derecha.
  • Dígitos tabulares, la misma precisión en toda la columna y un signo menos real (−).
  • Las etiquetas de fila son th scope="row"; el título da el nombre, luego periodo y fuente.
  • keyRow tiñe la fila que el lector debe notar, solo cuando los datos lo justifican.

Código

components/ui/table.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

// Essential Design `es-table`: for exact lookup. Numbers align right in the
// header and every cell (`numeric`), with tabular figures only there (years
// and labels keep their normal numerals) and the same precision down a column. Text aligns left. One `is-key` row at most.
function Table({ className, ...props }: React.ComponentProps<"table">) {
  return (
    <div data-slot="table-wrap" className="min-w-0 overflow-x-auto">
      <table
        data-slot="table"
        className={cn(
          "w-full border-collapse text-label font-normal",
          className
        )}
        {...props}
      />
    </div>
  )
}

/** A bold title, then period and source in muted text. */
function TableCaption({
  title,
  className,
  children,
  ...props
}: React.ComponentProps<"caption"> & { title: React.ReactNode }) {
  return (
    <caption
      data-slot="table-caption"
      className={cn(
        "pb-3 text-start text-label font-normal text-muted-foreground",
        className
      )}
      {...props}
    >
      <b className="block text-body font-medium text-foreground">{title}</b>
      {children}
    </caption>
  )
}

function TableHeader(props: React.ComponentProps<"thead">) {
  return <thead data-slot="table-header" {...props} />
}

function TableBody(props: React.ComponentProps<"tbody">) {
  return <tbody data-slot="table-body" {...props} />
}

function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
  return (
    <tfoot
      data-slot="table-footer"
      className={cn(
        "font-medium [&_td]:border-b-0 [&_th]:border-b-0",
        className
      )}
      {...props}
    />
  )
}

function TableRow({
  keyRow = false,
  className,
  ...props
}: React.ComponentProps<"tr"> & {
  /** Tints the one row the reader should notice, only when the data supports it. */
  keyRow?: boolean
}) {
  return (
    <tr
      data-slot="table-row"
      data-key={keyRow || undefined}
      className={cn("data-key:*:bg-muted", className)}
      {...props}
    />
  )
}

const cell = (numeric?: boolean) =>
  cn(
    "border-b border-border px-4 py-3 align-baseline first:pl-0 last:pr-0",
    numeric ? "text-end tabular-nums" : "text-start"
  )

function TableHead({
  numeric,
  className,
  scope = "col",
  ...props
}: React.ComponentProps<"th"> & { numeric?: boolean }) {
  return (
    <th
      data-slot="table-head"
      scope={scope}
      className={cn(
        cell(numeric),
        scope === "col"
          ? "border-input align-bottom text-caption font-medium text-muted-foreground"
          : "font-medium whitespace-nowrap",
        className
      )}
      {...props}
    />
  )
}

function TableCell({
  numeric,
  className,
  ...props
}: React.ComponentProps<"td"> & { numeric?: boolean }) {
  return (
    <td
      data-slot="table-cell"
      className={cn(cell(numeric), className)}
      {...props}
    />
  )
}

export {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
}