{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table",
  "title": "Table",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A semantic table in a focusable, named scroll region, with sticky header, striping, three densities, aria-sort headers and numeric columns.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/table.tsx",
      "content": "// Ballmac UI: Table. https://ui.ballmac.com/components/table\n// Based on shadcn/ui Table (MIT, Copyright (c) 2023 shadcn), adding a focusable scroll region, sticky header, striping, density, and aria-sort.\nimport * as React from \"react\";\nimport { ArrowDown, ArrowUp, ChevronsUpDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ntype TableProps = React.ComponentProps<\"table\"> & {\n  /** Accessible name of the scroll region that wraps the table. Recommended whenever the table can scroll. */\n  label?: string;\n  /** Keep the header row visible while the body scrolls (give the table a max height with `containerClassName`). */\n  stickyHeader?: boolean;\n  /** Shade every second body row. */\n  striped?: boolean;\n  /** Row height. */\n  density?: \"compact\" | \"default\" | \"comfortable\";\n  /** Classes for the scroll container, for example `max-h-80`. */\n  containerClassName?: string;\n};\nfunction Table({\n  className,\n  containerClassName,\n  label,\n  stickyHeader = false,\n  striped = false,\n  density = \"default\",\n  ...props\n}: TableProps) {\n  return (\n    <div\n      data-slot=\"table-container\"\n      role=\"region\"\n      aria-label={label}\n      tabIndex={0}\n      className={cn(\n        \"relative w-full overflow-auto rounded-xl border bg-card outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        containerClassName,\n      )}\n    >\n      <table\n        data-slot=\"table\"\n        data-sticky={stickyHeader ? \"\" : undefined}\n        data-striped={striped ? \"\" : undefined}\n        data-density={density}\n        className={cn(\n          \"group/table w-full caption-bottom border-separate border-spacing-0 text-sm\",\n          className,\n        )}\n        {...props}\n      />\n    </div>\n  );\n}\n\ntype TableHeaderProps = React.ComponentProps<\"thead\">;\nfunction TableHeader({ className, ...props }: TableHeaderProps) {\n  return (\n    <thead\n      data-slot=\"table-header\"\n      className={cn(\n        \"bg-muted/50 group-data-[sticky]/table:sticky group-data-[sticky]/table:top-0 group-data-[sticky]/table:z-10 group-data-[sticky]/table:bg-muted\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype TableBodyProps = React.ComponentProps<\"tbody\">;\nfunction TableBody({ className, ...props }: TableBodyProps) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn(\"[&_tr:last-child_td]:border-b-0\", className)}\n      {...props}\n    />\n  );\n}\n\ntype TableFooterProps = React.ComponentProps<\"tfoot\">;\nfunction TableFooter({ className, ...props }: TableFooterProps) {\n  return (\n    <tfoot\n      data-slot=\"table-footer\"\n      className={cn(\"bg-muted/50 font-medium [&_td]:border-t [&_td]:border-b-0\", className)}\n      {...props}\n    />\n  );\n}\n\ntype TableRowProps = React.ComponentProps<\"tr\"> & {\n  /** Mark the row as selected (also sets `aria-selected`). */\n  selected?: boolean;\n};\nfunction TableRow({ className, selected, ...props }: TableRowProps) {\n  return (\n    <tr\n      data-slot=\"table-row\"\n      data-state={selected ? \"selected\" : undefined}\n      aria-selected={selected || undefined}\n      className={cn(\n        \"transition-colors hover:bg-muted/50 group-data-[striped]/table:even:bg-muted/30 data-[state=selected]:bg-accent\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype TableHeadProps = React.ComponentProps<\"th\"> & {\n  /** Sort state of this column. Sets `aria-sort` and shows a direction icon; wrap the label in a button to make it interactive. */\n  sort?: \"ascending\" | \"descending\" | \"none\";\n  /** Right-align and use tabular numbers. */\n  numeric?: boolean;\n  /** Draw the direction icon when `sort` is set. Turn off when your own header button already shows one. */\n  sortIcon?: boolean;\n};\nfunction TableHead({\n  className,\n  sort,\n  sortIcon = true,\n  numeric,\n  children,\n  scope = \"col\",\n  ...props\n}: TableHeadProps) {\n  const Icon =\n    sort === \"ascending\" ? ArrowUp : sort === \"descending\" ? ArrowDown : ChevronsUpDown;\n  return (\n    <th\n      data-slot=\"table-head\"\n      scope={scope}\n      aria-sort={sort}\n      className={cn(\n        \"h-10 border-b px-3 text-start align-middle text-xs font-medium whitespace-nowrap text-muted-foreground\",\n        numeric && \"text-end tabular-nums\",\n        className,\n      )}\n      {...props}\n    >\n      {sort && sortIcon ? (\n        <span\n          className={cn(\n            \"inline-flex items-center gap-1.5\",\n            numeric && \"flex-row-reverse\",\n          )}\n        >\n          {children}\n          <Icon aria-hidden=\"true\" className=\"size-3.5 opacity-70\" />\n        </span>\n      ) : (\n        children\n      )}\n    </th>\n  );\n}\n\ntype TableCellProps = React.ComponentProps<\"td\"> & {\n  /** Right-align and use tabular numbers. */\n  numeric?: boolean;\n};\nfunction TableCell({ className, numeric, ...props }: TableCellProps) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      className={cn(\n        \"border-b px-3 align-middle group-data-[density=compact]/table:h-9 group-data-[density=default]/table:h-12 group-data-[density=comfortable]/table:h-16\",\n        numeric && \"text-end tabular-nums\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype TableCaptionProps = React.ComponentProps<\"caption\">;\nfunction TableCaption({ className, ...props }: TableCaptionProps) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn(\"mt-3 mb-3 text-xs text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableRow,\n  TableHead,\n  TableCell,\n  TableCaption,\n  type TableProps,\n  type TableHeaderProps,\n  type TableBodyProps,\n  type TableFooterProps,\n  type TableRowProps,\n  type TableHeadProps,\n  type TableCellProps,\n  type TableCaptionProps,\n};\n",
      "type": "registry:ui",
      "target": "@components/ballmac/table.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "table",
      "data",
      "semantic",
      "responsive"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "Static table parts. The wrapper scrolls on narrow screens and is keyboard focusable; pass label to name it and sort on TableHead for aria-sort.",
      "whenToUse": [
        "Invoices, logs and lists of records",
        "Read-only or lightly interactive data"
      ],
      "whenNotToUse": [
        "Sorting, filtering and pagination logic; build on data-table",
        "Key-value pairs; use description-list"
      ],
      "composesWith": [
        "badge",
        "pagination"
      ],
      "a11y": [
        {
          "keys": "Tab",
          "action": "Focuses the scroll region, then any controls inside it"
        },
        {
          "keys": "Arrow keys",
          "action": "Scroll the focused region"
        }
      ],
      "customization": [
        "stickyHeader with containerClassName max height",
        "striped",
        "density: compact | default | comfortable",
        "numeric on TableHead and TableCell"
      ]
    },
    "source": {
      "name": "shadcn/ui Table",
      "url": "https://github.com/shadcn-ui/ui",
      "license": "MIT",
      "copyright": "Copyright (c) 2023 shadcn",
      "modified": true
    },
    "examples": [
      "table-demo",
      "table-states"
    ],
    "url": "https://ui.ballmac.com/components/table"
  },
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}