Saltar al contenido

Tabla

Una tabla HTML semántica para facturas, listas administrativas y tablas de precios: filas con bordes, encabezados en negrita y una sombra marcada de estilo neobrutalista.

import {
  Table,
  TableBody,

La tabla estiliza los elementos de tabla nativos de HTML —<table>, <thead>, <tbody>, <th>, <td>, <caption>— sin biblioteca de primitivas debajo; las variantes Radix y Base comparten el mismo archivo. La receta neobrutalista: un borde grueso y una sombra marcada alrededor de toda la tabla, celdas de encabezado con la fuente de los títulos y divisores de fila de 2px.

Recurre a este componente cuando:

  • Facturas y pedidos — líneas de detalle con una fila TableFooter para el total.
  • Listas de administración — usuarios, claves de API, suscripciones, con un menú desplegable de acciones por fila.
  • Cuadrículas de precios y comparativas — matrices de funcionalidades donde los encabezados de columna llevan el peso del significado.

Instalación

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/table.json
npx shadcn@latest add https://neobrutalism.com/r/base/table.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/table.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/table.json

Copia y pega el siguiente código en tu proyecto.

components/ui/table.tsx
"use client"

import * as React from "react"

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

function Table({ className, ...props }: React.ComponentProps<"table">) {
  return (
    <div
      data-slot="table-container"
      className="relative w-full overflow-x-auto rounded border-2 shadow-md"
    >
      <table
        data-slot="table"
        className={cn("w-full caption-bottom text-sm", className)}
        {...props}
      />
    </div>
  )
}

function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
  return (
    <thead
      data-slot="table-header"
      className={cn("[&_tr]:border-b-2", className)}
      {...props}
    />
  )
}

function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
  return (
    <tbody
      data-slot="table-body"
      className={cn("[&_tr:last-child]:border-0", className)}
      {...props}
    />
  )
}

function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
  return (
    <tfoot
      data-slot="table-footer"
      className={cn(
        "border-t-2 bg-muted/50 font-medium [&>tr]:last:border-b-0",
        className
      )}
      {...props}
    />
  )
}

function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
  return (
    <tr
      data-slot="table-row"
      className={cn(
        "border-b-2 transition-colors hover:bg-accent has-aria-expanded:bg-accent data-[state=selected]:bg-accent",
        className
      )}
      {...props}
    />
  )
}

function TableHead({ className, ...props }: React.ComponentProps<"th">) {
  return (
    <th
      data-slot="table-head"
      className={cn(
        "h-10 bg-muted px-2 text-left align-middle font-head font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
        className
      )}
      {...props}
    />
  )
}

function TableCell({ className, ...props }: React.ComponentProps<"td">) {
  return (
    <td
      data-slot="table-cell"
      className={cn(
        "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
        className
      )}
      {...props}
    />
  )
}

function TableCaption({
  className,
  ...props
}: React.ComponentProps<"caption">) {
  return (
    <caption
      data-slot="table-caption"
      className={cn("mt-4 text-sm text-muted-foreground", className)}
      {...props}
    />
  )
}

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

Ajusta las rutas de importación según la configuración de tu proyecto.

Uso

import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"
<Table>
  <TableCaption>A list of your recent invoices.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead className="w-[100px]">Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead>Method</TableHead>
      <TableHead className="text-right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell className="font-medium">INV001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell>Credit Card</TableCell>
      <TableCell className="text-right">$250.00</TableCell>
    </TableRow>
  </TableBody>
</Table>

Composición

Usa la siguiente composición para construir un Table:

Table
├── TableCaption
├── TableHeader
│   └── TableRow
│       ├── TableHead
│       ├── TableHead
│       ├── TableHead
│       └── TableHead
├── TableBody
│   ├── TableRow
│   │   ├── TableCell
│   │   ├── TableCell
│   │   ├── TableCell
│   │   └── TableCell
│   └── TableRow
│       ├── TableCell
│       ├── TableCell
│       ├── TableCell
│       └── TableCell
└── TableFooter

Ejemplos

Pie de tabla

Usa el componente <TableFooter /> para añadir un pie de tabla.

import {
  Table,
  TableBody,

Acciones

Una tabla que muestra acciones para cada fila mediante un componente <DropdownMenu />.

import { MoreHorizontalIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

Data Table

Puedes usar el componente <Table /> para construir tablas de datos más complejas. Combínalo con @tanstack/react-table para crear tablas con ordenación, filtrado y paginación.

Consulta la documentación de Data Table para más información.

También puedes ver un ejemplo de tabla de datos en la demo de Tasks.

RTL

Para activar el soporte RTL en Neobrutalism, consulta la guía de configuración RTL.

"use client"

import * as React from "react"

Accesibilidad

Renderizar elementos de tabla nativos da a los lectores de pantalla el anuncio de tabla, la navegación por filas y columnas y la asociación de encabezados sin cableado ARIA alguno. Da nombre a cada tabla con TableCaption y reserva TableHead para las celdas que de verdad son encabezados. El contenedor de desplazamiento no es enfocable, así que quienes solo usan teclado no pueden desplazar una tabla que desborda: si prevés tablas anchas, añade tabIndex={0}, role="region" y un aria-label al div contenedor en tu copia del componente.