Table
A semantic table with aligned cells and helpers for media, captions, trends and row actions.
npx shadcn@latest add @velaui/tablepnpm dlx velaui add @velaui/tableyarn dlx shadcn@latest add @velaui/tablebunx --bun shadcn@latest add @velaui/tableveni add tableInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/tablepnpm dlx velaui add @velaui/tableyarn dlx shadcn@latest add @velaui/tablebunx --bun shadcn@latest add @velaui/tableExamples
- Default
- Bordered
Usage
Props
Table
| Prop | Type | Default |
|---|---|---|
bordered | boolean | false |
Takes these and every prop of TableProps.
TableHeader
Takes every prop of React.ComponentProps<"thead">.
TableBody
Takes every prop of React.ComponentProps<"tbody">.
TableFooter
Takes every prop of React.ComponentProps<"tfoot">.
TableRow
Takes every prop of React.ComponentProps<"tr">.
TableHead
| Prop | Type | Default |
|---|---|---|
align | "left" | "right" | "left" |
Takes these and every prop of TableHeadProps.
TableCell
| Prop | Type | Default |
|---|---|---|
align | "left" | "right" | "left" |
Takes these and every prop of TableCellProps.
TableCaption
Takes every prop of React.ComponentProps<"caption">.
TableCellMedia
Takes every prop of React.ComponentProps<"div">.
TableCellText
| Prop | Type | Default |
|---|---|---|
bold | boolean | — |
Takes these and every prop of TableCellTextProps.
TableCellCaption
Takes every prop of React.ComponentProps<"span">.
TableCellTrend
| Prop | Type | Default |
|---|---|---|
trend | "up" | "down" | "up" |
Takes these and every prop of TableCellTrendProps.
TableCellActions
Takes every prop of React.ComponentProps<"div">.
Dependencies
- npm
class-variance-authority@0.7.1- Installs with it
- 2 icons
Source
components/ui/table.tsx
/* eslint-disable react-refresh/only-export-components */
"use client";
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronDown } from "@/components/icons/chevron-down";
import { ChevronUp } from "@/components/icons/chevron-up";
import { cn } from "@/lib/utils";
/* -------------------------------------------------------------------------- */
/* Shell */
/* -------------------------------------------------------------------------- */
export type TableProps = React.ComponentProps<"table"> & {
/**
* Frame the table in a rounded outline. The design runs the table flush with
* its column edges, so the frame also restores the outer padding the first
* and last columns drop, and folds the last row's rule into the outline.
*/
bordered?: boolean;
};
function Table({ className, bordered = false, ...props }: TableProps) {
return (
<div
data-slot="table-container"
data-bordered={bordered || undefined}
className={cn(
"relative w-full overflow-x-auto",
bordered &&
"rounded-lg border border-border-default [&_tbody_tr:last-child>td]:border-b-0 [&_td:first-child]:pl-3 [&_td:last-child]:pr-3 [&_th:first-child]:pl-3 [&_th:last-child]:pr-3",
)}
>
<table
data-slot="table"
className={cn(
"w-full caption-bottom border-collapse text-base leading-6 text-text-strong",
className,
)}
{...props}
/>
</div>
);
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return <thead data-slot="table-header" className={cn(className)} {...props} />;
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return <tbody data-slot="table-body" className={cn(className)} {...props} />;
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn("[&>tr:last-child]:border-b-0", className)}
{...props}
/>
);
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return (
<tr
data-slot="table-row"
className={cn("group/table-row", className)}
{...props}
/>
);
}
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
return (
<caption
data-slot="table-caption"
className={cn("mt-4 text-sm text-text-note", className)}
{...props}
/>
);
}
/* -------------------------------------------------------------------------- */
/* Cells */
/* -------------------------------------------------------------------------- */
// Column position is positional, so it stays in CSS: the outer edges of the
// row lose their outside padding, every inner edge keeps px-3.
const tableHeadVariants = cva(
"h-10 border-b border-border-default px-3 py-3 align-middle text-xs leading-[1.3] font-medium whitespace-nowrap text-text-muted transition-colors first:pl-0 last:pr-0 hover:text-text-strong",
{
variants: {
align: {
left: "text-left",
right: "text-right",
},
},
defaultVariants: {
align: "left",
},
},
);
const tableCellVariants = cva(
// Focus draws a 2px stroke in the design — rendered as an inset ring so
// entering the state cannot resize the cell (and shove the column).
"h-12 border-b border-border-default p-3 align-middle text-base leading-6 tracking-[-0.24px] text-text-strong outline-none transition-colors first:pl-0 last:pr-0 hover:bg-bg-surface-1 focus-visible:bg-bg-surface-1 focus-visible:ring-2 focus-visible:ring-bg-surface-2 focus-visible:ring-inset data-[state=selected]:bg-bg-surface-1 group-data-[state=selected]/table-row:bg-bg-surface-1",
{
variants: {
align: {
left: "text-left",
right: "text-right",
},
},
defaultVariants: {
align: "left",
},
},
);
export type TableHeadProps = React.ComponentProps<"th"> &
VariantProps<typeof tableHeadVariants>;
function TableHead({ className, align = "left", ...props }: TableHeadProps) {
return (
<th
data-slot="table-head"
data-align={align}
className={cn(tableHeadVariants({ align }), className)}
{...props}
/>
);
}
export type TableCellProps = React.ComponentProps<"td"> &
VariantProps<typeof tableCellVariants>;
function TableCell({ className, align = "left", ...props }: TableCellProps) {
return (
<td
data-slot="table-cell"
data-align={align}
className={cn(tableCellVariants({ align }), className)}
{...props}
/>
);
}
/* -------------------------------------------------------------------------- */
/* Cell contents */
/* -------------------------------------------------------------------------- */
/**
* Leading media (avatar, photo, icon, circle icon) beside the cell's text
* stack — the design gives every one of those types the same 12px gap.
*/
function TableCellMedia({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="table-cell-media"
className={cn(
"flex items-center gap-3 [&_svg]:size-6 [&_svg]:shrink-0 [&_svg]:text-icon-strong",
className,
)}
{...props}
/>
);
}
const tableCellTextVariants = cva(
"block text-base leading-6 tracking-[-0.24px] text-text-strong",
{
variants: {
bold: {
true: "font-medium",
false: "font-normal",
},
},
defaultVariants: {
bold: false,
},
},
);
export type TableCellTextProps = React.ComponentProps<"span"> &
VariantProps<typeof tableCellTextVariants>;
function TableCellText({ className, bold, ...props }: TableCellTextProps) {
return (
<span
data-slot="table-cell-text"
className={cn(tableCellTextVariants({ bold }), className)}
{...props}
/>
);
}
function TableCellCaption({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="table-cell-caption"
className={cn("block text-xs leading-4 text-text-note", className)}
{...props}
/>
);
}
const tableCellTrendVariants = cva(
"inline-flex items-center gap-1 text-xs leading-[1.3] font-medium [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
trend: {
up: "text-success-middle",
down: "text-negative-middle",
},
},
defaultVariants: {
trend: "up",
},
},
);
export type TableCellTrendProps = React.ComponentProps<"span"> &
VariantProps<typeof tableCellTrendVariants>;
function TableCellTrend({
className,
children,
trend = "up",
...props
}: TableCellTrendProps) {
const TrendIcon = trend === "down" ? ChevronDown : ChevronUp;
return (
<span
data-slot="table-cell-trend"
data-trend={trend}
className={cn(tableCellTrendVariants({ trend }), className)}
{...props}
>
{children}
<TrendIcon />
</span>
);
}
/** Trailing controls of a row — bare buttons, icon buttons, or both. */
function TableCellActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="table-cell-actions"
className={cn("flex items-center gap-2", className)}
{...props}
/>
);
}
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableRow,
TableHead,
TableCell,
TableCaption,
TableCellMedia,
TableCellText,
TableCellCaption,
TableCellTrend,
TableCellActions,
tableHeadVariants,
tableCellVariants,
tableCellTextVariants,
tableCellTrendVariants,
};