Styled table
Five table treatments: striped, bordered, hover glow, compact and card rows. Each is sized by density, token-driven and renders a real semantic table.
Installation
The styled layer is free and installs like any other ai2 component.
Run the following command
npx shadcn@latest add @ai2/table-styledDependencies, the @ai2/tokens theme and the component file are installed together.
Copy the source
components/ui/table-styled.tsx"use client"
import type * as React from "react"
import { cn } from "@/lib/utils"
/* Styled data table family: 5 decorative variations that build a real <table> from
columns + rows. Color comes ONLY from semantic tokens (via alpha color-mix, never
var(--x)/0.4), and size sets the cell density (padding + text). Deterministic,
renders with no props too (3 columns / 4 rows of data by default). No animation,
pure CSS. */
export type StyledSize = "sm" | "md" | "lg" | "xl"
type Column = { key: string; label: React.ReactNode }
type Row = Record<string, React.ReactNode>
type TableProps = {
className?: string
size?: StyledSize
columns?: Column[]
rows?: Row[]
}
/* A sensible default data set so it renders without props. */
const defaultColumns: Column[] = [
{ key: "name", label: "Name" },
{ key: "role", label: "Role" },
{ key: "status", label: "Status" },
]
const defaultRows: Row[] = [
{ name: "Ada Lovelace", role: "Engineer", status: "Active" },
{ name: "Alan Turing", role: "Researcher", status: "Active" },
{ name: "Grace Hopper", role: "Architect", status: "Away" },
{ name: "Katherine Johnson", role: "Analyst", status: "Active" },
]
/* Hucre yogunlugu: padding + metin olcegi. */
const cell: Record<StyledSize, string> = {
sm: "px-2.5 py-1.5 text-xs",
md: "px-3 py-2 text-sm",
lg: "px-4 py-2.5 text-sm",
xl: "px-5 py-3 text-base",
}
/* Header cell density (the same horizontally as the body, slightly tightened vertically). */
const headCell: Record<StyledSize, string> = {
sm: "px-2.5 py-1.5 text-xs",
md: "px-3 py-2 text-xs",
lg: "px-4 py-2.5 text-sm",
xl: "px-5 py-3 text-sm",
}
const tableBase = "w-full border-collapse text-left align-middle text-foreground"
const headBase = "font-medium text-muted-foreground"
function resolve(columns?: Column[], rows?: Row[]) {
return {
cols: columns && columns.length > 0 ? columns : defaultColumns,
data: rows && rows.length > 0 ? rows : defaultRows,
}
}
/* Striped: alternatif satirlarda token yuzey tonuyla zebra deseni. */
export function StripedTable({ className, size = "md", columns, rows }: TableProps) {
const { cols, data } = resolve(columns, rows)
return (
<div
data-slot="styled-table"
className={cn("w-full overflow-x-auto rounded-lg border border-border", className)}
>
<table className={tableBase}>
<thead>
<tr className="border-b border-border bg-muted">
{cols.map((col) => (
<th key={col.key} scope="col" className={cn(headBase, headCell[size])}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr
key={i}
className={cn(
"border-b border-border/60 last:border-b-0",
i % 2 === 1 && "bg-[color-mix(in_oklab,var(--surface-2)_60%,transparent)]"
)}
>
{cols.map((col) => (
<td key={col.key} className={cell[size]}>
{row[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
/* Bordered: tum hucrelerde token kenarlik - tam izgara / grid cizgileri. */
export function BorderedTable({ className, size = "md", columns, rows }: TableProps) {
const { cols, data } = resolve(columns, rows)
return (
<div data-slot="styled-table" className={cn("w-full overflow-x-auto rounded-lg", className)}>
<table className={cn(tableBase, "border border-border")}>
<thead>
<tr className="bg-muted">
{cols.map((col) => (
<th
key={col.key}
scope="col"
className={cn(headBase, headCell[size], "border border-border")}
>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i}>
{cols.map((col) => (
<td key={col.key} className={cn(cell[size], "border border-border")}>
{row[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
/* HoverGlow: borderless rows that gain a token tone plus a light glow on hover. */
export function HoverGlowTable({ className, size = "md", columns, rows }: TableProps) {
const { cols, data } = resolve(columns, rows)
return (
<div data-slot="styled-table" className={cn("w-full overflow-x-auto rounded-lg", className)}>
<table className={tableBase}>
<thead>
<tr>
{cols.map((col) => (
<th key={col.key} scope="col" className={cn(headBase, headCell[size])}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr
key={i}
className="transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-[color-mix(in_oklab,var(--accent)_70%,transparent)] hover:shadow-[0_0_18px_-6px_color-mix(in_oklab,var(--accent-foreground)_35%,transparent)]"
>
{cols.map((col) => (
<td key={col.key} className={cell[size]}>
{row[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
/* Compact: dense padding, small text, minimal chrome (bottom rules only). */
export function CompactTable({ className, size = "sm", columns, rows }: TableProps) {
const { cols, data } = resolve(columns, rows)
return (
<div data-slot="styled-table" className={cn("w-full overflow-x-auto", className)}>
<table className={cn(tableBase, "text-xs")}>
<thead>
<tr className="border-b border-border">
{cols.map((col) => (
<th
key={col.key}
scope="col"
className={cn(headBase, "px-2 py-1 text-xs")}
>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i} className="border-b border-border/50 last:border-b-0">
{cols.map((col) => (
<td key={col.key} className="px-2 py-1">
{row[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
/* Card: every row reads as a rounded, separate token card (with gaps). */
export function CardTable({ className, size = "md", columns, rows }: TableProps) {
const { cols, data } = resolve(columns, rows)
return (
<div data-slot="styled-table" className={cn("w-full overflow-x-auto", className)}>
<table className={cn(tableBase, "border-separate border-spacing-y-2")}>
<thead>
<tr>
{cols.map((col) => (
<th key={col.key} scope="col" className={cn(headBase, headCell[size])}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i} className="bg-card text-card-foreground shadow-sm">
{cols.map((col, j) => (
<td
key={col.key}
className={cn(
cell[size],
"border-y border-border first:border-l last:border-r",
j === 0 && "rounded-l-lg",
j === cols.length - 1 && "rounded-r-lg"
)}
>
{row[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}Manual installs skip the @ai2/tokens theme, so add the token CSS from the theming guide or the tone colors will be missing.
Variations
5 takes on the same idea. Each is its own export, and every one accepts a size prop (sm, md, lg, xl) aligned to the base Button scale.
Striped
Zebra rows via a token surface tint.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { StripedTable } from "@/components/ui/table-styled"
<StripedTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Bordered
Full token cell borders and grid lines.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { BorderedTable } from "@/components/ui/table-styled"
<BorderedTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Hover glow
Rows get a token tint and glow on hover.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { HoverGlowTable } from "@/components/ui/table-styled"
<HoverGlowTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Compact
Dense padding and minimal chrome.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { CompactTable } from "@/components/ui/table-styled"
<CompactTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
Card
Each row reads as a separated token card.
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
import { CardTable } from "@/components/ui/table-styled"
<CardTable />| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
| Name | Role | Status |
|---|---|---|
| Ada Lovelace | Engineer | Active |
| Alan Turing | Researcher | Active |
| Grace Hopper | Architect | Away |
| Katherine Johnson | Analyst | Active |
ai2 Styled table: 5 styled variations on the token system
The ai2 Styled table are a set of 5 decorative button variations from the styled layer of the @ai2 design system, built around data table styles. They are free and MIT licensed, and every color comes from a semantic token, so they theme with the rest of ai2 in light and dark.
Motion runs on framer-motion: row hover states run on token CSS transitions. The styled layer is opt-in, so the dependency only lands if you use it; the base components stay lean. Under reduced motion, the hover transitions are disabled and rows stay static.
What is in the ai2 Styled table?
5 exports in one file: Striped, Bordered, Hover glow, Compact and Card. Each renders a native button and takes a size prop (sm, md, lg, xl) aligned to the base Button. They are separate from the base Button on purpose: the base keeps its clean variant, tone and size axes, while the styled layer carries the effects.
You own the file. Copy the one category file and you have all 5 variations, with no runtime dependency on ai2 itself.
Why use it
- On-system by construction: Every color resolves to an ai2 semantic token, so the buttons follow your theme in light and dark with no extra work.
- Effect without the sprawl: The decorations live in a dedicated styled file, so the base Button keeps its clean, predictable API.
- Accessible and honest: Each renders a real button element, keeps a visible focus ring, and respects prefers-reduced-motion.
Features
- Token-driven color: No hardcoded hex or oklch; the look recolors with your theme tokens.
- framer-motion: row hover states run on token CSS transitions.
- Reduced-motion aware: Under prefers-reduced-motion, the hover transitions are disabled and rows stay static.
- Size aligned to the base: Every variation takes sm, md, lg and xl matching the base Button height scale, so styled and base buttons line up in a row.
Production tips
- Use it for emphasis, not everywhere: Styled buttons draw the eye. Reserve them for the one action you want people to take on a screen, and use the base Button for the rest.
- Keep labels as verbs: The decoration adds weight, so a clear action label keeps the button scannable.
- Pick one variation per surface: The variations share a family; using two different ones in the same view competes for attention.
Works with the rest of ai2
The Styled table sit alongside the base Button and the rest of the @ai2 registry. They share the same token file, so a styled action next to a base button or a badge stays visually consistent in both modes.